"Stale-reference sweep - repoint dead SDP anchors across modules/skill and refresh obsolete ship-workflow notes, with a doc-anchor checker"
TASK-SKILL-119: Stale-reference sweep + doc-anchor checker
§1 - Description
Skills are contracts; contracts that cite documents which no longer exist train agents to distrust citations. The SDP anchor died in the docs split and the ship workflow carries notes that predate its own children. This task sweeps both and adds the checker that prevents recurrence.
Normative clauses:
- Every
modules/skill/*/SKILL.md(and any other modules/skill markdown) citingmodules/cuo/README.md#software-development-process- including theSDP §2(a)..(g)lettered forms - MUST be repointed to the live SDP location (modules/cuo/docs/appendices.md§13 stage mapping, ormodules/cuo/docs/module.mdwhere the prose form fits), preserving each citation's stage letter/number semantics unchanged. ship-tasks.mdMUST drop the obsolete note (currently near line 199) claiming the code-review pair may not exist yet, replacing it with the current fact (pair exists, vendored); the Issue RequestTBD(near line 137) MUST either point at a tracked task id or be reworded as explicitly unscheduled future work - a bareTBDMUST NOT remain.- A script
scripts/check_doc_anchors.shMUST scanmodules/skill/**/*.mdandmodules/cuo/**/*.mdfor repo-relative markdown links and inline path#anchor citations, and verify each target file exists and (when an anchor is given) the anchor resolves to a heading in that file (GitHub slug rules: lowercase, spaces to hyphens, punctuation stripped). Unresolved references exit 10 asDEAD <citing-file>:<line> -> <target>; external URLs (http/https) MUST be skipped; a--listflag prints the would-be-swept set without failing. - The checker MUST run in CI on changes to
modules/skill/**ormodules/cuo/**(extendpayload-gate.ymlfrom TASK-IMP-068 with a step, or the existing voice-and-consistency workflow - implementer's choice, documented in the workflow file). - The sweep MUST NOT alter any skill's trigger description, frontmatter, or artefact contract - citation strings only (same byte-stability discipline as TASK-SKILL-118 §1 #7).
§2 - Why this design
Fixing the anchors without a checker just schedules the next rot; a checker without slug-aware anchor resolution would only catch deleted FILES, and the observed failure is a deleted SECTION HOST. Scanning both markdown link syntax and inline path#anchor prose covers how skills actually cite (they use both). CI placement rides existing gates rather than adding a new workflow.
§3 - Contract
scripts/check_doc_anchors.sh [--list] [root]
exit 0 all repo-relative references resolve (prints "anchors OK: N checked in M files")
exit 10 DEAD <file>:<line> -> <target> (one per unresolved reference)
exit 2 root unreadable
--list print each reference (resolved or not) as "<file>:<line> <target> <ok|DEAD>"
§4 - Acceptance criteria
- Zero dead SDP anchors after the sweep (§1 #1) -
grep -rn "modules/cuo/README.md" modules/skill modules/cuoreturns nothing, and the checker exits 0 over both trees. - Stage semantics preserved (§1 #1) - the two named SKILL.md files (implementation-plan-author, architecture-decision-record-author) still cite their respective SDP stages (implementation prep / architecture decision), now at the live location.
- Ship workflow notes are current (§1 #2) - line-199-class note gone, replaced by the present-tense fact; no bare
TBDremains in the file (grep clean), the Issue Request mention names a task or says "future work, unscheduled". - Checker resolves anchors, not just files (§1 #3) - a fixture link to an existing file but nonexistent heading is reported DEAD with file:line; the same link with a valid heading passes.
- External URLs skipped, --list works (§1 #3) - an https link never fails the check;
--listprints every reference with its status and exits 0. - CI wired (§1 #4) - the chosen workflow runs the checker on the two path filters; the step is present and the workflow parses.
- Contracts byte-stable outside citations (§1 #5) - for every swept SKILL.md, the diff touches only citation strings (no frontmatter, no description, no artefact-section changes).
§5 - Verification
# scripts/tests/test_check_doc_anchors.sh
t01_sweep_leaves_zero_dead() # AC 1
t02_stage_semantics_kept() # AC 2 (grep the two files for stage wording + new target)
t03_ship_notes_current() # AC 3
t04_anchor_vs_file_resolution() # AC 4 (fixture tree with good-file/bad-anchor case)
t05_external_and_list() # AC 5
t06_ci_step_present() # AC 6
t07_citation_only_diffs() # AC 7 (git diff --unified=0 scoped assertions on a sample)
§6 - Implementation skeleton
Checker: extract candidates via two greps (\]\([^)h][^)]*\) for md links; [a-zA-Z0-9_./-]+\.md(#[a-z0-9-]+)? for inline paths), normalize against repo root, slugify headings of each target once into an assoc cache, compare. Sweep: run --list, sed the dead SDP form to the live target across the listed files, hand-fix the two workflow notes.
§7 - Dependencies
None upstream; TASK-IMP-068's workflow is the preferred CI host but the voice-and-consistency workflow is an acceptable fallback (implementer documents the choice). Related: TASK-SKILL-115 (prior sweep, done - different defect class), TASK-DOCS-002 (the docs split that orphaned the anchor).
§8 - Example payloads
$ bash scripts/check_doc_anchors.sh
DEAD modules/skill/implementation-plan-author/SKILL.md:5 -> modules/cuo/README.md#software-development-process
DEAD modules/skill/architecture-decision-record-author/SKILL.md:5 -> modules/cuo/README.md#software-development-process
$ echo $?
10
§9 - Open questions
None blocking. Whether the checker later covers docs/tasks cross-references too is a cheap follow-up flag; scope here is the skill/cuo contract trees where agents read citations at run time.
§10 - Failure modes inventory
- Slug algorithm mismatch (unicode, duplicate headings with -1 suffixes) - implement GitHub's documented rules incl. duplicate suffixing; fixture covers a duplicated heading.
- False positives on code blocks containing path-like strings - the scanner MUST skip fenced code blocks; fixture includes a fenced
modules/cuo/README.mdmention that must not fail. - Sweep sed over-matching (a skill legitimately discussing the OLD path as history) - the sweep list comes from the checker (resolution-based), not from a blind grep; historical mentions inside fenced blocks survive per #2.
- Anchor exists only in the rendered site, not the markdown (generated pages) - scope is repo markdown; generated-site anchors are TASK-DOCS-002's builder concern.
- Checker runtime creep on big trees - single-pass with a per-file heading cache; budget < 5s over modules/, asserted in t05.
§11 - Implementation notes
Keep DEAD output grep-stable. The sweep commit should separate mechanical repoints (one commit) from the two hand-edited workflow notes (second commit) for reviewable diffs.
End of TASK-SKILL-119.
Audit
TASK-SKILL-119 audit
§1 - Verdict summary
Audited for defect-class coverage (the observed rot is a dead SECTION HOST, not a dead file) and for sweep safety over 100+ contract files. Distinctness from TASK-SKILL-115 (placeholder-syntax sweep, done) is established in source_decisions, so no supersession applies under the operator's conflict rule. Traceability closes over t01-t07 in scripts/tests/test_check_doc_anchors.sh.
§2 - Findings (all resolved)
ISS-001 file-existence checking would miss the actual defect
modules/cuo/README.md is GONE, but the next rot may be a renamed heading in a file that still exists. Resolved: §1 #3 slug-aware anchor resolution (GitHub rules incl. duplicate suffixing), AC 4 fixture separates good-file/bad-anchor.
ISS-002 fenced code blocks false-positive
Contract files quote paths in code blocks legitimately. Resolved: scanner skips fenced blocks (§10 #2) with a fixture that must not fail.
ISS-003 blind-grep sweep could rewrite history
A skill discussing the old path as history must survive. Resolved: sweep set = checker resolution output (--list), not grep (§10 #3).
ISS-004 "reworded TBD" loophole
The clause could be satisfied by cosmetic rewording. Resolved: §1 #2 bare-TBD MUST NOT remain + AC 3's grep-clean assertion plus the named-task-or-unscheduled disjunction.
ISS-005 contract byte-stability
Same risk class as TASK-SKILL-118 ISS-005. Resolved: §1 #5 citations-only rule, AC 7 diff-scope check.
ISS-006 CI host ambiguity
"Add to CI" without a host invites drift. Resolved: §1 #4 names the two acceptable hosts and requires the choice documented in the workflow file; AC 6 asserts presence wherever it landed.
§3 - Resolution
All six findings addressed as cited. The checker makes this the LAST manual anchor sweep; recurrence becomes a CI failure. Score = 10/10.
End of TASK-SKILL-119 audit.
§4 - Ship record (2026-07-12)
- Implementation: check_doc_anchors.sh + reasoned exemptions (unused-warn) + CI step; 388-file sweep across 6 dead-reference classes; ship workflow v1.x note + bare TBD fixed; commits 141de5d, 38c8c7d. Phase artefacts: docs/tasks/.workflow/TASK-SKILL-119/.
- Recorded deviation: AC 1 grep clean over live contracts; historical archives exempted with reasons (rewriting absorbed history falsifies it); the checker (CI-run, exit 0) is the durable form.
- Review: human verdict at gate 1 APPROVE + pre-authorize done (Stephen Cheng, in-chat).
- Testing: test_check_doc_anchors.sh 6/6, 7/7 cyberos-install suites at rest, live tree 341 references zero dead. Gate 2 recorded per pre-authorization.
- Field finding queued: pair-parity t04 fires on mid-flight citation swaps (point-in-time-guard class, third instance) - refinement candidate for the next batch.
Verdict unchanged: PASS, Score = 10/10.