Repository navigation
scan-annotations: fix five silent link losses, and let @tested read in both directions - #1
Merged
Merged
Conversation
scan-annotations is the only thing that turns a comment into a graph edge. Everything it drops is, downstream, a link that never existed — and none of these five failures reported anything. Each one produced a smaller report, stated with full confidence. Namespaced ids were the worst of them, because the two scanners failed in opposite directions. Given ZPP-FR-009, the .sh scanner matched the flat id inside it and reported FR-009; the .ps1 scanner matched nothing and reported none. A project prefixes its ids precisely because its FR-009 is not the flat FR-009, so the .sh behaviour is not a lost prefix, it is a different requirement asserted as fact. Both now accept NS-KIND-nnn, and AI-AGT-001 / AI-ETH-001 fall out of the same branch rather than needing their own. Four more, found while verifying that one: - The .ps1 skip list was compared against the whole path, so "build" swallowed builder.ts and "dist" swallowed distance.ts. A skip list names directories, so it now matches path segments. - .ps1 dropped a whole annotation when one id in the list was unrecognised: `@spec FR-093, ADR-058` lost FR-093 as well, silently, because the pattern had to consume the line. It now keeps what it recognises. - .sh could not see past a UTF-8 BOM. Windows editors write one, so every ^-anchored pattern failed on line 1 — which is where a file-level annotation goes. Get-Content strips the BOM, so .ps1 never saw the problem, and neither scanner looked wrong on its own machine. - .sh crashed on a tree with no annotations: under `set -euo pipefail` the grep that matched nothing ended the script before it printed anything. The count it would have printed was a doubled zero, because `grep -c .` both prints 0 and exits non-zero. Two divergences are closed rather than fixed, moving .sh to .ps1's stricter reading: structured annotations now require a comment prefix (it had been counting `const s = "@Req FR-999"`), unstructured references require the ids-only form, and the @tested payload is validated as a test-file reference. The tests cover each failure and add a cross-scanner parity check. That check is the one worth keeping: two halves of one tool that disagree mean a graph built on Windows differs from the same graph built on Linux, and neither is wrong locally. Verified equal on a real 101-file repository — 80 structured annotations, 76 requirement ids, identical sets. Every new test fails against the previous scanners. Existing suites unchanged: scan-annotations, validate-graph 23/23, validate-plan 13/13. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`@tested` understood one form: a test reference on a source file, meaning "this
code is verified by that test". It now also accepts requirement ids on a test
file, meaning "this test verifies these requirements".
// src/generation.ts
// @tested __tests__/generation.test.ts::creates_generation
// __tests__/generation.test.ts
// @tested FR-001, SDD-004
Both assert the same verified_by relation. They differ only in which end the
annotated file is, and a project maintains its traceability from one side or the
other. Understanding only one of them meant a repository that annotates its tests
scanned as having no annotations at all — not a warning, a zero. On the
repository this came from, tests/ read as 0 annotations while holding 69.
Annotating from the test side is often the more durable of the two: the assertion
and the claim that it verifies a requirement live in one file, so deleting the
test takes its claim with it. Nothing about the old form changes.
Structured annotations now carry `form` — test-ref, requirement, or section. It
describes what the payload IS rather than which keyword introduced it, which is
the distinction a consumer actually needs: @Designs already took either a section
or an id, and @tested now takes either a path or ids, so switching on the keyword
alone meant re-parsing the value to discover what you were holding.
Both scanners accept both payloads, and a payload that is neither a path nor an
id is still not an annotation. Parity on a real repository: src/ 80 annotations
and 76 ids, tests/ 69 and 70, identical from either scanner.
Docs updated where the grammar is stated: the annotation table in skills/doc-graph,
the README examples, the @tested check in skills/doc-preflight, and the @tested
description in references/doc-graph-schema.json.
scan-annotations 5/5, validate-graph 23/23, validate-plan 13/13.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI checks the two harness manifests agree, and the previous commit moved only the Claude one. scripts/bump-version.ps1 exists for exactly this reason; I edited the manifest by hand instead and the check caught it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two commits on one branch, because they are the same surface and the second depends on the first's parity test.
scan-annotationsis the only thing that turns a comment into a graph edge, so everything it drops is — downstream — a link that never existed.1.
fix: five failures that reported nothingFound while adopting
rwang:doc-graphv2 in a repo that uses namespaced requirement ids (ZPP-*,RAG-*,TAX-*).The main one: namespaced ids
The two scanners failed in opposite directions on the same input.
.shreported.ps1reported@req ZPP-FR-009FR-009ZPP-FR-009A project prefixes its ids precisely because its
FR-009is not the flatFR-009. So the.shbehaviour is not a lost prefix — it is a different requirement asserted as fact. Both now acceptNS-KIND-nnn;AI-AGT-001/AI-ETH-001fall out of the same branch instead of needing their own enumeration.Four more, found while verifying that one
.ps1skip list matched substrings of the whole path —buildswallowedbuilder.ts,distswalloweddistance.ts. Those files reported nothing and were never listed as skipped. Now compared against path segments..ps1dropped a whole annotation when one id was unrecognised —@spec FR-093, ADR-058lostFR-093too, becauseADRis not an enumerated kind and the pattern had to consume the line..shcould not see past a UTF-8 BOM — Windows editors write one, so every^-anchored pattern failed on line 1, which is where a file-level annotation goes.Get-Contentstrips it, so.ps1never saw the problem and neither scanner looked wrong on its own machine..shcrashed on a tree with no annotations — underset -euo pipefail, the grep that matched nothing ended the script before it printed anything. The count it would have printed was a doubled zero, sincegrep -c .both prints0and exits non-zero.Two divergences also closed, moving
.shto.ps1's stricter reading: structured annotations require a comment prefix (it had been countingconst s = "@req FR-999"), and unstructured references require the ids-only form.2.
feat:@testedreads in both directionsBoth assert the same
verified_byrelation and differ only in which end the annotated file is. A project maintains its traceability from one side or the other, and understanding only one form meant a repository that annotates its tests scanned as having no annotations at all — not a warning, a zero. On the repository this came from,tests/read as 0 while holding 69.Annotating from the test side is often the more durable choice: the assertion and the claim that it verifies a requirement live in one file, so deleting the test takes its claim with it.
Structured annotations now carry
form—test-ref,requirement, orsection. It says what the payload is rather than which keyword introduced it, which is the distinction a consumer needs:@designsalready took either a section or an id.Verification
Every new test fails against the previous scanners. The addition worth keeping is the cross-scanner parity check — two halves of one tool that disagree mean a graph built on Windows differs from the same graph built on Linux, and neither is wrong locally.
Measured on a real repository, both scanners now identical:
src/.ps175/73,.sh80/76tests/scan-annotations5/5 ·validate-graph23/23 ·validate-plan13/13 — existing suites unchanged.Version 1.4.0 (the marketplace update signal).
🤖 Generated with Claude Code