Skip to content

feat(unic-spec-review): skeleton tracer bullet (classify URL, fetch page, Gaps agent, report)#210

Merged
orioltf merged 9 commits into
developfrom
archon/task-feature-unic-spec-review-202-skeleton-tracer-bulle
Jun 5, 2026
Merged

feat(unic-spec-review): skeleton tracer bullet (classify URL, fetch page, Gaps agent, report)#210
orioltf merged 9 commits into
developfrom
archon/task-feature-unic-spec-review-202-skeleton-tracer-bulle

Conversation

@orioltf
Copy link
Copy Markdown
Member

@orioltf orioltf commented Jun 5, 2026

Why

unic-spec-review was scaffold-only: stub commands, no .mjs scripts, no test harness, no real Confluence fetch or review logic. This slice (S1) wires the first end-to-end /review-spec path so a real review can run and serve as the foundation every later slice builds on. Closes #202 (Epic #200).

What

The deliberately minimal tracer bullet: one source, one page, one agent. No traversal, comments, Figma, live-system, or posting.

  • Vendor atlassian-fetch.mjs and credentials.mjs from unic-pr-review (ADR-0001 self-containment). Copied verbatim; the only change is the missing-creds message pointing at /unic-spec-review:setup-confluence. Nothing is cross-imported at runtime.
  • New pure modules:
    • args — parse /review-spec arguments (URL list + --post recognition; --post is inert in S1)
    • link-classifier — route a URL to confluence / figma-page / figma-frame / live / unknown and extract the Confluence page id
    • report-renderer — render a timestamped markdown report and write it under .spec-review/ (gitignored)
  • gaps-agent — Gaps/Completeness dimension agent prompt; registered in plugin.json.
  • /review-spec S1 orchestration — classify URL → fetch one Confluence page → run Gaps agent → print findings → write report. Read-only.
  • Restored test harnesstest/typecheck scripts, tsconfig.json, scripts/ + tests/ dirs; node:test unit tests cover every pure module with injected fetch/homedir/env/fs deps. The agent prompt and thin command orchestrator are not unit-tested (per AC).
  • Version bumped 0.1.0 → 0.1.1 via pnpm bump patch; CHANGELOG entry added.

Validation

Check Result
pnpm --filter unic-spec-review typecheck ✅ exit 0
pnpm --filter unic-spec-review test ✅ 54 tests, 0 fail
pnpm --filter unic-spec-review verify:changelog ✅ 0.1.1
pnpm check (root Biome + Prettier) ✅ clean

Notes

  • No LICENSE files were created or deleted (maintainer-managed).
  • The read-only invariant holds: zero writes to any external service.

🤖 Generated with Claude Code

orioltf and others added 2 commits June 5, 2026 14:41
…age, Gaps agent, report)

Wire the first end-to-end /review-spec path so a real Confluence review can run
and serve as the foundation for later slices. The plugin was scaffold-only with
stub commands and no test harness.

Changes:
- Vendor atlassian-fetch.mjs and credentials.mjs from unic-pr-review (ADR-0001
  self-containment); only the missing-creds plugin name is changed
- Add pure modules: args (URL + --post parsing), link-classifier (URL routing +
  page id extraction), report-renderer (timestamped .spec-review/ report)
- Add gaps-agent (Gaps/Completeness dimension agent prompt)
- Implement /review-spec S1 orchestration and register the agent in plugin.json
- Restore the test harness: test/typecheck scripts, tsconfig.json, scripts/ and
  tests/ dirs; node:test unit tests cover every pure module with injected deps

Fixes #202

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(Windows compat)

path.split('/') is not cross-platform; Windows paths use backslash, so the
full path was returned unchanged, failing the regex assertion on Windows CI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@orioltf
Copy link
Copy Markdown
Member Author

orioltf commented Jun 5, 2026

🔍 Comprehensive PR Review

PR: #210feat(unic-spec-review): skeleton tracer bullet
Reviewed by: 5 specialized agents (code-review, error-handling, test-coverage, comment-quality, docs-impact)
Date: 2026-06-05


Summary

The S1 skeleton is architecturally clean: ADR-0001 self-containment respected, injectable-deps pattern applied consistently, vendor copies verified, 54 tests green on macOS/Ubuntu/Windows. No blockers. The action items are a documentation refresh (three stale "scaffolding" banners), two defensive additions to report-renderer.mjs, three missing edge-case tests, and a module-docstring update in the vendored atlassian-fetch.mjs.

Verdict: REQUEST_CHANGES — easy fixes, none block correctness on happy paths.

Severity Count
🔴 CRITICAL 0
🟠 HIGH 1
🟡 MEDIUM 7
🟢 LOW 10

🟠 HIGH Issues

H1 — AGENTS.md says "scaffolding only — not yet implemented"

📍 apps/claude-code/unic-spec-review/AGENTS.md:9

The status banner still reads "not yet implemented". S1 is live. Any agent/dev reading this will believe the plugin is a stub.

Fix (one line):

> Status: S1 skeleton implemented (URL classify → Confluence fetch → Gaps agent → report). Full design (traversal, Figma, live-system, Approval Loop, de-dup) is specified in the [PRD](docs/issues/unic-spec-review/PRD.md) and lands in later slices.

🟡 MEDIUM Issues

M1 — Single-quote shell wrapping breaks for pages with apostrophes

📍 commands/review-spec.md:117 (Step 6)

REPORT_JSON='<REPORT_JSON>' in bash: if the Confluence page title contains a ' (e.g. "Developer's Guide"), the substituted command breaks with a shell syntax error and the report is silently not written.

View fix (temp-file approach, mirrors unic-pr-review pattern)

Write JSON to a temp file first:

node -e "const{writeFileSync}=require('node:fs');writeFileSync(process.argv[1],process.argv[2])" /tmp/spec-review-report.json '<REPORT_JSON>'
REPORT_OUTPUT_DIR=".spec-review" node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/report-renderer.mjs" /tmp/spec-review-report.json

Also update report-renderer.mjs to accept an optional process.argv[2] file path, falling back to REPORT_JSON env var.


M2 — renderReport call not guarded against filesystem errors

📍 scripts/lib/report-renderer.mjs:121

JSON.parse has a try/catch but the renderReport(input, outputDir) call right after does not. mkdirSync/writeFileSync can throw EPERM/EACCES/ENOSPC → raw stack trace, no report path on stdout, confusing command failure.

View fix (~6 lines)
const outputDir = process.env.REPORT_OUTPUT_DIR ?? '.spec-review'
let result
try {
    result = renderReport(input, outputDir)
} catch (err) {
    process.stderr.write(
        `report-renderer: could not write report to ${outputDir}: ${err instanceof Error ? err.message : String(err)}\n`
    )
    process.exit(1)
}
process.stdout.write(`${result.path}\n`)

M3 — Parsed input not shape-validated before renderReport

📍 scripts/lib/report-renderer.mjs:121

After JSON.parse, input.timestamp and input.findings are used without checking they exist. Missing fields → uncaught TypeError stack trace (compounded by M2's missing catch).

View fix
if (typeof input !== 'object' || input === null) {
    process.stderr.write('report-renderer: REPORT_JSON must be an object\n')
    process.exit(1)
}
if (!Array.isArray(input.findings)) {
    process.stderr.write('report-renderer: REPORT_JSON missing required field: findings\n')
    process.exit(1)
}
if (typeof input.timestamp !== 'string') {
    process.stderr.write('report-renderer: REPORT_JSON missing required field: timestamp\n')
    process.exit(1)
}

M4 — HTTP 403 not tested in fetchConfluencePage

📍 tests/atlassian-fetch.test.mjs:156

fetchJson handles 401 || 403 as one branch; only 401 is tested. A future split of these codes would silently break the 403 path.

View fix (one it-block)
it('throws FetchError with kind auth-error on 403', async () => {
    await assert.rejects(
        () => fetchConfluencePage('https://unic.atlassian.net/wiki/spaces/X/pages/1', CREDS, {
            fetch: fetchStatus(403),
        }),
        (err) => /** @type {any} */ (err).kind === 'auth-error'
    )
})

M5 — collectIntent with unsupported URL not tested

📍 tests/atlassian-fetch.test.mjs

The unsupported error kind (e.g. an ADO link passed as a URL) is not tested. The /review-spec Step 2 guard depends on this kind.

View fix (one it-block)
it('records an unsupported error for an unrecognised URL without throwing', async () => {
    const env = { CONFLUENCE_URL: 'https://x.atlassian.net', CONFLUENCE_USER: 'u', CONFLUENCE_TOKEN: 't' }
    const result = await collectIntent(['https://dev.azure.com/org/proj/_workitems/edit/123'], { env })
    assert.deepEqual(result.items, [])
    assert.equal(result.errors.length, 1)
    assert.equal(result.errors[0].kind, 'unsupported')
})

M6 — collectIntent with malformed creds file not tested

📍 tests/atlassian-fetch.test.mjs

When loadCreds throws (corrupted ~/.unic-confluence.json), collectIntent should return { errors: [{ url: '', kind: 'auth-error' }] }. This path (which Step 3 depends on) is untested.

View fix (one it-block)
it('converts a credential load exception into a global auth-error', async () => {
    const result = await collectIntent(['https://x.atlassian.net/wiki/spaces/X/pages/1'], {
        loadCreds: () => { throw new Error('invalid JSON') },
    })
    assert.equal(result.errors[0].kind, 'auth-error')
    assert.equal(result.errors[0].url, '')
    assert.match(result.errors[0].message, /could not be read/)
})

M7 — atlassian-fetch.mjs module docstring uses wrong plugin vocabulary

📍 scripts/atlassian-fetch.mjs:7-22

Module docstring still says "Pre-PR reviews", "Intent Checker agent", "doctor.mjs", "ADR-0005" — all unic-pr-review artefacts. In this plugin the consumer is the /review-spec command / Gaps agent.

View fix (replace docstring header)
/**
 * atlassian-fetch.mjs — fetch Confluence page content (and optionally Jira
 * issue data) for spec reviews.
 *
 * Pure-function library plus a thin CLI entry point. Given a list of pasted
 * URLs it routes each by path (`/browse/` → Jira, `/wiki/` → Confluence),
 * fetches the linked page / issue via the Atlassian REST APIs using global
 * `fetch` (Node 22+), and normalises the response into structures the
 * `/review-spec` command and Gaps agent can consume.
 *
 * Credentials come from `lib/credentials.mjs` (env vars override the file).
 * Every HTTP call uses Basic auth (email:token) with a hard 15 s timeout
 * (ADR-0001: built-in fetch, no external runtime deps).
 *
 * The fetch helpers accept an injectable `fetch` (via `deps.fetch`) so unit
 * tests can stub HTTP without mocking globalThis.
 */

Also update three inline comments at :81, :506-510, :621-625 that name "Intent Checker agent" / "ADR-0004" → "Gaps agent / /review-spec command".


🟢 Low Issues

View 10 low-priority suggestions
# Issue Location Recommendation
L1 AGENTS.md Commands footnote: "pnpm test/typecheck will be added later" — already added AGENTS.md:44-46 Remove note; add both to Commands table
L2 any type in vendored atlassian-fetch.mjs JSDoc :343,:497 Leave as-is (intentional vendor carryover)
L3 Duplicate extractPageId in link-classifier + atlassian-fetch both files Leave as-is (intentional per ADR-0001)
L4 Jira exports in atlassian-fetch have no tests here :428+ One-liner comment: "Jira path untested in unic-spec-review; coverage in unic-pr-review"
L5 loadAzureCreds untested (dead code in this plugin) credentials.mjs:96 Leave as-is
L6 anchor field in renderFinding not tested report-renderer.mjs:62 One test: assert > Anchor: block rendered when f.anchor truthy
L7 figma.com bare domain not tested in classifyUrl link-classifier.mjs:67 One assertion: classifyUrl('https://figma.com/…')kind: 'figma-page'
L8 Three inline comments still say "Intent Checker agent" / "ADR-0004" :81,:506,:621 Fix alongside M7
L9 CONTEXT.md status banner says "scaffolding — not yet implemented" CONTEXT.md:4 "S1 implemented; terms marked (S1) are active"
L10 README.md status banner says "scaffolding — not yet implemented" README.md:9 "S1 available: single Confluence page review"

✅ What's Good

  • Vendor integrity confirmedatlassian-fetch.mjs differs from unic-pr-review in exactly one line (the setup-confluence reference). credentials.mjs is bit-for-bit identical.
  • New abstractions are genuinely newclassifyUrl, parseReviewSpecArgs, renderReport have no equivalent in unic-pr-review. No primitive duplication introduced.
  • ADR-0001 respected throughout — zero cross-plugin imports; all new files are node:* only.
  • Injectable deps everywhererenderReport, collectIntent, fetchConfluencePage all accept optional deps objects. Fully testable without global mocks.
  • collectIntent never-throws contract is exemplary — all per-URL failures collected into errors[]; global cred errors use the url === '' sentinel.
  • Windows fix applied cleanlypath.split('/').pop()path.basename() in the test; production code unchanged.
  • 54 tests, 0 failures on macOS, Ubuntu, and Windows.
  • CHANGELOG## [0.1.1] — 2026-06-05 ✓ passes verify:changelog.

📋 Suggested Follow-up Issues (if not fixing in this PR)

Issue Title Priority
guard report-renderer CLI against FS errors (M2 + M3) P2
fix Step 6 shell-quoting fragility in review-spec.md (M1) P2
add missing edge-case tests: 403, unsupported URL, bad creds (M4–M6) P2
update atlassian-fetch.mjs vendor docstring for spec-review context (M7 + L8) P3

Reviewed by Archon comprehensive-pr-review workflow · 5 agents · artifacts

Docs (H1, L1, L9, L10):
- AGENTS.md: update status banner to S1-implemented; add pnpm test/typecheck to Commands
- CONTEXT.md: update status banner to S1-implemented
- README.md: replace scaffolding note with S1-available message

Error handling (M2, M3):
- report-renderer.mjs: add shape validation (findings, timestamp) after JSON.parse
- report-renderer.mjs: wrap renderReport call in try/catch for FS errors (EPERM/ENOSPC)

Shell-quoting (M1):
- review-spec.md: use Write tool + argv[2] file-path approach for Step 6
- report-renderer.mjs: accept optional file path as process.argv[2] to avoid single-quote shell issues with apostrophes in page titles

Tests (M4, M5, M6, L6, L7):
- atlassian-fetch.test.mjs: add 403 auth-error test
- atlassian-fetch.test.mjs: add unsupported URL collectIntent test
- atlassian-fetch.test.mjs: add malformed creds collectIntent test
- report-renderer.test.mjs: add anchor quote-block rendering test
- link-classifier.test.mjs: add bare figma.com (no www) test

Comments (M7, L4, L8):
- atlassian-fetch.mjs: update module docstring from unic-pr-review domain vocabulary to spec-review context
- atlassian-fetch.mjs: replace "Intent Checker / ADR-0004" with "Gaps agent / /review-spec command" in 3 inline comments
- atlassian-fetch.mjs: add note that Jira exports are untested in this plugin

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@orioltf
Copy link
Copy Markdown
Member Author

orioltf commented Jun 5, 2026

⚡ Self-Fix Report (Aggressive)

Status: COMPLETE
Pushed: ✅ Changes pushed to archon/task-feature-unic-spec-review-202-skeleton-tracer-bulle
Commit: be45d2e
Philosophy: Fix everything unless clearly a new concern


Fixes Applied (15 total)

Severity Count
🔴 CRITICAL 0
🟠 HIGH 1
🟡 MEDIUM 7
🟢 LOW 7
View all fixes
  • AGENTS.md status banner (AGENTS.md:9) — Updated to "S1 skeleton implemented"; added pnpm test + typecheck commands; removed stale caveat
  • Single-quote shell quoting in Step 6 (commands/review-spec.md:111) — Write tool + file-path argv[2] approach; renderer updated to accept optional file path argument
  • renderReport unguarded against FS errors (scripts/lib/report-renderer.mjs) — try/catch wrapping renderReport call with clean stderr message + exit 1
  • input not shape-validated (scripts/lib/report-renderer.mjs) — Guards for findings array and timestamp string before renderReport call
  • HTTP 403 not tested (tests/atlassian-fetch.test.mjs) — Added it('throws FetchError with kind auth-error on 403', ...)
  • collectIntent unsupported URL not tested (tests/atlassian-fetch.test.mjs) — Added test with ADO URL asserting kind === 'unsupported'
  • collectIntent malformed creds not tested (tests/atlassian-fetch.test.mjs) — Added test with injectable loadCreds that throws; asserts global auth-error
  • Module docstring in wrong plugin domain (scripts/atlassian-fetch.mjs:7-22) — Rewrote: removed "Pre-PR reviews", "Intent Checker agent", "doctor.mjs", "ADR-0005"; updated to spec-review context
  • Three inline "Intent Checker / ADR-0004" comments (scripts/atlassian-fetch.mjs:81,507,623) — Updated to "Gaps agent / /review-spec command"
  • Jira exports note absent (scripts/atlassian-fetch.mjs) — Added "vendored from unic-pr-review, untested here" note to module docstring
  • anchor field not tested (tests/report-renderer.test.mjs) — Added anchor quote-block rendering test
  • figma.com bare domain not tested (tests/link-classifier.test.mjs) — Added routes a bare figma.com URL (no www) to figma-page test
  • CONTEXT.md status banner (CONTEXT.md:4) — Updated to "S1 implemented"
  • README.md status banner (README.md:9) — Replaced scaffolding note with S1-available description
  • AGENTS.md Commands footnote (AGENTS.md:44-46) — Removed "will be added later" caveat; commands now in table

Tests Added

File Test Cases
tests/atlassian-fetch.test.mjs 403 auth-error, unsupported URL, malformed creds (3 tests)
tests/report-renderer.test.mjs anchor quote-block rendering
tests/link-classifier.test.mjs bare figma.com (no www)

Total: 59 tests (was 54)


Skipped (3)

Finding Reason
any type in vendored file Intentional vendor carryover from unic-pr-review; reviewer recommended leave-as-is
Duplicate extractPageId logic Intentional per ADR-0001 self-containment; reviewer recommended leave-as-is
loadAzureCreds untested Dead code in this plugin; reviewer recommended skip

Suggested Follow-up Issues

(none — all non-skipped findings addressed)


Validation

✅ Type check | ✅ Lint | ✅ Tests (59 passed)


Self-fix by Archon · aggressive mode · fixes pushed to archon/task-feature-unic-spec-review-202-skeleton-tracer-bulle

… URL parse pattern

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR turns unic-spec-review from a scaffold into a minimal end-to-end “S1 tracer bullet” for /review-spec: classify one URL, fetch one Confluence page read-only, run a single Gaps/Completeness agent, print findings, and write a durable markdown report under .spec-review/. It also restores the plugin’s local test/typecheck harness so future slices can iterate safely.

Changes:

  • Added core pure modules (args, link-classifier, report-renderer) plus a vendored Confluence fetcher (atlassian-fetch.mjs) and credential loader (credentials.mjs) to support a real S1 run.
  • Added gaps-agent and registered it in the plugin manifest.
  • Restored node:test coverage for the pure modules and the Confluence page-read path, plus TypeScript checkJs typechecking for .mjs.

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pnpm-lock.yaml Adds TypeScript + Node types + shared tsconfig workspace deps for this plugin.
apps/claude-code/unic-spec-review/tsconfig.json Enables repo-standard TS checkJs typechecking over scripts/ and tests/.
apps/claude-code/unic-spec-review/package.json Bumps version, restores test/typecheck, adds devDeps needed for checkJs.
apps/claude-code/unic-spec-review/scripts/atlassian-fetch.mjs Vendored Atlassian read fetcher; provides Confluence page fetch used by S1.
apps/claude-code/unic-spec-review/scripts/lib/credentials.mjs Vendored credential loader for Confluence/Jira (+ ADO creds for future slices).
apps/claude-code/unic-spec-review/scripts/lib/link-classifier.mjs Classifies pasted URLs and extracts Confluence page IDs.
apps/claude-code/unic-spec-review/scripts/lib/args.mjs Parses /review-spec arguments into { urls, post }.
apps/claude-code/unic-spec-review/scripts/lib/report-renderer.mjs Renders + writes timestamped markdown report (default .spec-review/).
apps/claude-code/unic-spec-review/commands/review-spec.md Implements the S1 command flow as a Claude Code command script.
apps/claude-code/unic-spec-review/agents/gaps-agent.md Adds the Gaps/Completeness agent prompt with JSON output contract.
apps/claude-code/unic-spec-review/tests/atlassian-fetch.test.mjs Unit tests for Confluence page-read path + error mapping + credential resolution.
apps/claude-code/unic-spec-review/tests/credentials.test.mjs Unit tests for env/file credential loading and malformed JSON errors.
apps/claude-code/unic-spec-review/tests/link-classifier.test.mjs Unit tests for URL classification behaviors.
apps/claude-code/unic-spec-review/tests/args.test.mjs Unit tests for argument parsing (--post recognition, URL extraction).
apps/claude-code/unic-spec-review/tests/report-renderer.test.mjs Unit tests for report rendering + output path/slug behavior.
apps/claude-code/unic-spec-review/.claude-plugin/plugin.json Registers the new agent and bumps plugin version.
apps/claude-code/unic-spec-review/.claude-plugin/marketplace.json Bumps marketplace version to match plugin/package version.
apps/claude-code/unic-spec-review/CHANGELOG.md Adds the 0.1.1 release entry describing S1 tracer bullet and harness restore.
apps/claude-code/unic-spec-review/README.md Updates status copy to reflect S1 availability and future-slice scope.
apps/claude-code/unic-spec-review/AGENTS.md Updates plugin status + documents restored test/typecheck commands.
apps/claude-code/unic-spec-review/CONTEXT.md Updates vocabulary doc status note to reflect S1 being active.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/claude-code/unic-spec-review/scripts/lib/args.mjs
Comment thread apps/claude-code/unic-spec-review/scripts/lib/link-classifier.mjs
Comment thread apps/claude-code/unic-spec-review/scripts/lib/report-renderer.mjs
orioltf and others added 5 commits June 5, 2026 15:41
…elds

Filter arg parsing and link classification to http(s) only so ftp/file/mailto
tokens are dropped, and guard pageTitle/pageUrl in the report-renderer CLI entry
so missing values no longer render as literal undefined. Adds node:test coverage
for each path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…urface fetch errors

Write scratch report JSON into gitignored .spec-review/ instead of the
POSIX-only /tmp path (breaks on Windows CI), and surface the structured
errors[].kind/message from the fetch script so the real failure cause is shown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ove em dashes

Drop the render-summary.mjs, doctor.mjs, and inaccurate ADR-0001 citations
from code comments, reword the CONTEXT.md status line so it no longer promises
an unused (S1) per-term marking, and replace em dashes with hyphens in authored
comments and messages per the org typography rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Close coverage gaps flagged in PR #210 review: env-over-file
credential precedence and the 800-char Confluence excerpt cap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Biome organizeImports ordering for node:test before node:url.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@orioltf orioltf merged commit 5cfb23b into develop Jun 5, 2026
9 checks passed
@orioltf orioltf deleted the archon/task-feature-unic-spec-review-202-skeleton-tracer-bulle branch June 5, 2026 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

unic-spec-review S1: skeleton tracer bullet — classify one URL, fetch page, one Gaps agent, write report

2 participants