feat(unic-spec-review): gate dedup posts when comparison is incomplete#241
Conversation
#238) dedup-matcher dropped the truncated flag, so a Finding duplicating a comment beyond the pagination cap was decided post with the same authority as a fully checked Finding. Thread completeness structurally instead. Changes: - dedup-matcher CLI main() reads truncated from the comments object and emits a run-level { truncated, results } envelope; matchDedup signature/purity unchanged - review-spec.md Step 10b parses .results; 10a computes COMPARISON_INCOMPLETE = truncated OR (non-auth errors), converging two advisory warnings into one gate - 10c shows [?incomplete] on clean posts; 10d adds a single run-level confirm before the first clean-post write; skip/flag keep per-Finding gates - Add 5 node:test CLI envelope tests (injected { comments, truncated }) Fixes #238 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🔍 Comprehensive PR ReviewPR: #241 — feat(unic-spec-review): gate dedup posts when comparison is incomplete SummaryImplementation is sound. Verdict: ✅
🟡 Medium Issues (Need Decision)1.
|
| # | Issue | Location | Suggestion |
|---|---|---|---|
| 1 | Badge table missing spaces around AND — `'post'`AND`COMPARISON_INCOMPLETE` is ambiguous for LLM parsers |
review-spec.md:457 |
`'post'` AND `COMPARISON_INCOMPLETE` is false |
| 2 | runDedupCli never removes mkdtempSync temp dirs (accumulates on repeated runs) |
dedup-matcher.test.mjs:280-292 |
Wrap spawnSync in try/finally + rmSync(dir, { recursive: true, force: true }) |
| 3 | No symmetric test for --comments-file missing (only --findings-file has an exit-1 test) |
dedup-matcher.test.mjs:350-357 |
Add it('exits 1 when --comments-file is missing', ...) |
| 4 | 10b failure-fallback prose gap: exit-0 with absent results key is not covered |
review-spec.md:429 |
Extend: "non-zero exit, parse error, or parsed.results is not an array" |
| 5 | Test description says "error JSON" but assertion is plain substring includes('Usage') |
dedup-matcher.test.mjs:360 |
Strengthen: JSON.parse(res.stderr) + typeof err.error === 'string' |
✅ What's Good
matchDedupsignature invariant: exported function byte-for-byte unchanged, purity preserved- Backward compatibility:
!Array.isArray(commentsRaw) && commentsRaw?.truncated === truehandles legacy bare-array shape cleanly;undefined→falsecorrectly - COMPARISON_INCOMPLETE architecture: unifying truncation + partial read errors into a single boolean gate is the right abstraction
- Run-level confirm placement: fires once before per-Finding processing, not per-Finding
- 5 new CLI envelope tests: cover all envelope shapes + dedup + exit-1; no live services; pass on macOS/ubuntu/windows × Node 22/24
- JSDoc on
main(): accurately describes input duality, strict=== truecheck, and backward-compat rule - "do not re-read" note in Step 10b: valuable "don't do the obvious thing" guard against dual-source confusion
- CHANGELOG format:
## [0.1.9] — 2026-06-09✅; all three version files consistent ✅ - Zero new runtime dependencies ✅
Reviewed by Archon comprehensive-pr-review workflow — 4 agents
Artifacts: .archon/workspaces/unic/unic-agents-plugins/artifacts/runs/181b1dd9865e5ad343ef34f2a67ebfb0/review/
- Split SKIP_CLEAN_POSTS branch so approved skip/flag overrides bypass the run-level gate (code-review MEDIUM: prose guarantee was violated) - Mark ADR-0005 implemented in docs/adr/README.md (docs-impact MEDIUM) - Fix badge table spacing: `'post'` AND `COMPARISON_INCOMPLETE` (LOW) - Extend 10b fallback to cover exit-0 with absent results key (LOW) - Wrap runDedupCli spawnSync in try/finally + rmSync cleanup (LOW) - Strengthen error-JSON assertion to JSON.parse + typeof check (LOW) - Add symmetric test for --comments-file missing (LOW) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
⚡ Self-Fix Report (Aggressive)Status: COMPLETE Fixes Applied (7 total)
View all fixes
Tests Added
Skipped (0)(none — all findings addressed) Validation✅ Type check | ✅ Lint | ✅ Tests (373 passed, 6 in CLI envelope suite) Self-fix by Archon · aggressive mode · fixes pushed to |
…n CLI error tests The dedup-matcher CLI validates both --findings-file and --comments-file flags before attempting to read any file; a dummy path triggers the Usage error without needing real file content. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- review-spec.md 10c: rewrite the empty dedup-badge bullet as "(no badge)"
with spaced operators; the old leading empty code span rendered ambiguously
and fought Prettier normalisation.
- dedup-matcher.mjs: comment why the CLI envelope's `truncated === true` is a
deliberate soft default (Step 10a is the authoritative COMPARISON_INCOMPLETE
source), so a future envelope consumer is not misled.
- dedup-matcher.test.mjs: cover the realistic `{ comments }`-without-`truncated`
object shape (gating path), closing the gap between explicit true/false and
the bare-array legacy case.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Human-side review pass (review-spec workflow) — completeRan the full PR-review workflow on top of Archon's self-review. Confirmed the Archon run had finished (no active run, no unresolved inline threads) before touching the branch. Verified against ISSUE #238 acceptance criteria + ADR-0005 (all met):
Ran 4 review agents (code / tests / silent-failure / comments) — 0 critical, 0 blocking. Acted on three in-scope, low-risk findings:
Rejected as out-of-scope (locked by ADR-0005, not #238): folding the
|
Why
The
dedup-matcherCLI dropped thetruncatedflag from the comments object, emitting a bareDedupResult[]. A Finding that duplicated a comment living beyond the pagination cap was therefore decidedpostwith the same apparent authority as a fully-checked Finding — silently breaking ADR-0002's premise (similarity against all existing comments). This was the silent-failure-hunter CRITICAL finding from PR #237, triaged to #238.What changed
Threads comparison completeness structurally, per the locked design in ADR-0005 (grilled 2026-06-09):
scripts/lib/dedup-matcher.mjs— CLImain()readstruncatedfrom the comments object and emits a run-level{ truncated, results }envelope.matchDedup's signature and purity are unchanged — the completeness axis lives in the CLI and command, not in the pure matcher.commands/review-spec.md— Step 10b parses.resultsoff the envelope (failure fallback still treats every decision aspost); Step 10a computesCOMPARISON_INCOMPLETE = truncated OR (non-auth errors), converging two advisory warnings into one gate; Step 10c shows[?incomplete]on clean posts + a warning preamble; Step 10d adds a single run-level confirmation before the first clean-post write.skip/flagkeep their existing per-Finding gates.tests/dedup-matcher.test.mjs— 5 newnode:testCLI envelope tests with injected{ comments, truncated }(no live services).CHANGELOG.md+ patch bump → 0.1.9.Write-safety invariant preserved: bare
/review-specstays read-only;--postcancellable at every step.MAX_PAGESleft at 50 (out of scope per ADR-0005 §5).Verification
pnpm --filter unic-spec-review typecheckpnpm --filter unic-spec-review testpnpm --filter unic-spec-review verify:changelogpnpm checkCloses #238.
🤖 Generated with Claude Code