T-RIOC-assert-claude-md-import-resolves
Status: closed/superseded · Impact: medium · Complexity: small
AUTO-DEFINED: this spec was best-effort machine-authored by /sdlc:task-auto-define on 2026-07-04 because the task is autonomy: autonomous/pr. Review the Goal, Approach, Today, Files-to-touch, and Acceptance-criteria carefully before trusting it.
Docs-extraction tasks that move content out of CLAUDE.md into an @-imported
file leave a standing manual human spot-check, because /sdlc:task-work cannot
verify — from inside a worktree — that Claude Code’s @path CLAUDE.md import
actually resurfaces the moved text in agent context. This follow-up, originating
from T-NCC9-conventions-md-extraction in
git@github.com:sksizer/markdown-contract.git, asks this repo (the sdlc plugin)
to add an affordance to task-work (or a helper it invokes) that asserts @path
CLAUDE.md imports resolve, so @import-based extractions no longer leave a
standing manual verification step.
Docs-extraction tasks that move content out of CLAUDE.md into an @-imported file (e.g. CONVENTIONS.md) leave a standing manual human spot-check because task-work cannot verify, from inside a worktree, that Claude Code’s @path CLAUDE.md import actually resurfaces the moved text in agent context — a live
claude /memoryresolution is not reachable from a worktree. Add an affordance to task-work (or a helper it invokes) that asserts @path CLAUDE.md imports resolve, so @import-based extractions no longer leave a standing manual verification step.—
T-NCC9-conventions-md-extraction
| Location | Role today |
|---|---|
plugin/skills/task-work/preflight_permissions.ts | A self-contained bun pre-flight helper task-work already invokes (Step 3b). Establishes the pattern the new assertion follows: a top-of-file docstring, argv in, findings on stdout, documented exit codes, no shared state. |
plugin/skills/task-work/SKILL.md | task-work’s ordered steps. Step 7 runs the project’s declared quality checks; no step asserts that the @path imports in a CLAUDE.md the branch modified still resolve to existing files. |
plugin/skills/task-work/tests/ | Peer bun:test suites for the task-work helpers (preflight_permissions.test.ts, start_task.test.ts, and siblings). None covers CLAUDE.md import resolution. |
Proposed
Section titled “Proposed”Add a small, self-contained assertion helper beside the other task-work
helpers — assert_claude_md_imports.ts — that parses the @path memory
imports out of one or more CLAUDE.md files and checks that each resolves to an
existing file on disk, then wire task-work’s quality phase to run it against any
CLAUDE.md the branch’s diff touched. A docs-extraction task that moves prose out
of CLAUDE.md into an @-imported file (e.g. CONVENTIONS.md) then gets a
deterministic, worktree-local assertion that the import still points at the
moved file — replacing the manual claude /memory spot-check that a worktree
cannot run.
Approach
Section titled “Approach”- Add
plugin/skills/task-work/assert_claude_md_imports.ts, a#!/usr/bin/env bunhelper modeled onpreflight_permissions.ts— argv in, findings on stdout, documented exit codes, no shared state. Argv is one or more CLAUDE.md file paths. - Parse each input file for
@pathmemory imports using Claude Code’s rule: an@-token whose path is NOT inside a fenced code block or an inline-code span (reuse thecodeBlockLinesand inline-code masking already used by the claim resolvers underplugin/lib/model/entities/task/claims/, so a discussed@examplein prose is not treated as an import), and that carries a real path (skip bare@word/ email-shaped tokens with no path separator or file extension). - Resolve each parsed import against the filesystem: a leading
~/expands to the home directory, an absolute path is used as-is, and any other path resolves relative to the directory of the importing CLAUDE.md. An import passes when its resolved target exists and is a readable file. - Emit one finding line per UNresolved import on stdout in the shape
path:line: unresolved import @target, and exit 1; exit 0 with no stdout when every parsed import resolves; exit 2 on bad arguments (an argv entry that is not a readable file). Print the resolved-import count to stderr so a passing run stays observable. - Add a peer test
plugin/skills/task-work/tests/assert_claude_md_imports.test.tscovering: a CLAUDE.md whose@present.mdtarget exists (exit 0); a CLAUDE.md whose@absent.mdtarget is missing (exit 1 plus the finding line); an@-token inside a fenced code block (ignored); and a~/-prefixed import. - Wire the assertion into
plugin/skills/task-work/SKILL.mdStep 7: compute the set of CLAUDE.md files the branch changed viagit diff --name-only origin/main...HEADfiltered to aCLAUDE.mdbasename; when that set is non-empty, run the helper against it and treat a non-zero exit as a gate failure (same disposition as a failing quality-check verb). An empty set is a silent no-op.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/skills/task-work/assert_claude_md_imports.ts | new | The assertion helper: parse @path imports from the given CLAUDE.md files, resolve each on disk, exit non-zero on any unresolved import. |
plugin/skills/task-work/tests/assert_claude_md_imports.test.ts | new | Peer bun:test coverage: resolves-existing (exit 0), missing-target (exit 1 plus finding), fenced-@ ignored, ~/ expansion. |
plugin/skills/task-work/SKILL.md | modify | Add the Step 7 sub-step that runs the helper against CLAUDE.md files the branch diff touched and gates on its exit code. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
plugin/skills/task-work/assert_claude_md_imports.tsexists, runs underbun run, and its top-of-file docstring names exit 0 (imports resolve), exit 1 (an unresolved import), and exit 2 (bad arguments). - AC-2: Run against a CLAUDE.md containing
@present.mdwherepresent.mdexists in the same directory: the helper exits 0 and writes nothing to stdout. - AC-3: Run against a CLAUDE.md containing
@absent.mdwhereabsent.mddoes not exist: the helper exits 1 and stdout carries a line endingunresolved import @absent.md. - AC-4: An
@ignored.mdtoken inside a fenced code block in the CLAUDE.md is not reported, even whenignored.mdis absent (exit 0). - AC-5:
plugin/skills/task-work/tests/assert_claude_md_imports.test.tsexists and its cases pass underbun test. - AC-6:
plugin/skills/task-work/SKILL.mdStep 7 documents the CLAUDE.md import assertion — it runs the helper against the CLAUDE.md files reported bygit diff --name-only origin/main...HEAD, and a non-zero exit fails the quality gate.
Out of scope
Section titled “Out of scope”- Running a live
claude /memoryresolution or reproducing Claude Code’s full recursive import expansion (depth limits, transitive@chains) — the helper checks one-hop on-disk existence of directly-cited imports, not the assembled memory tree. - Asserting the content moved correctly (that the imported file carries the extracted prose) — the helper checks that the import target exists and is readable, not what it contains.
- Auto-fixing or rewriting broken
@pathimports — the helper reports; it does not edit CLAUDE.md. - Scanning CLAUDE.md files the branch did not modify, or walking the whole repo on every task-work run — the wiring is scoped to CLAUDE.md files in the branch diff.
Dependencies
Section titled “Dependencies”- Originates from
T-NCC9-conventions-md-extractioningit@github.com:sksizer/markdown-contract.git(recorded inrelated:). That task motivates the affordance but does not block it — this task can land independently and be adopted by future docs-extraction tasks. - No blocking
depends_on. The helper reuses thebunruntime and the fenced-code / inline-code masking already present in the task claim resolvers; no new runtime dependency is introduced.
Discovery context
Section titled “Discovery context”Spawned by /sdlc:spawn-task-pr on 2026-07-04 UTC from
T-NCC9-conventions-md-extraction in
git@github.com:sksizer/markdown-contract.git.