fix(traceability): repair issue-seam defects and land the tracker test harness - #327
Open
dean0x wants to merge 41 commits into
Open
fix(traceability): repair issue-seam defects and land the tracker test harness#327dean0x wants to merge 41 commits into
dean0x wants to merge 41 commits into
Conversation
Five command→agent spawn-key seams (PF-024 class) are fixed: 1. /debug #N: ISSUE_INPUT replaces the undefined ISSUE key; issue path now works for the first time. 2. /plan #N: Gate 0 now fetches issue before discovery (fetch-issue for single ref; fetch-issues-batch for multi-ref) and captures ISSUE_CONTENT, ACCEPTANCE_CRITERIA, ISSUE_REF, ISSUE_ID, ISSUE_URL. 3. git.md fetch-issues-batch: ISSUE_NUMBERS renamed to ISSUE_REFS (plan.mds and operations table aligned); Process rewritten as a single GraphQL alias query (not N+1 gh issue view calls), bounded at 50 with TRUNCATED report. 4. Issue body untrusted containment: fetch-issue and fetch-issues-batch Output blocks now wrap external body content in <untrusted-issue-body> markers; Principle 8 and the no-echo rule extended to all remote-originated bodies. 5. manage-debt D4 block added: degrades named with reason; caller records Tracked = (pending -- TRACEABILITY: DEGRADED) instead of silently losing the backfill. 6. resolve.mds D9 caller matches git.md verbatim (FIXED + commit_sha only; removes the FALSE_POSITIVE/BY_DESIGN divergence). 7. release.md: close-milestone tombstone deleted (ADR-003 end-state). 8. ensure-devflow-init: dead v2 fast-path marker replaced with live v3. Applies PF-024 (spawn-key seam integrity), ADR-003 (end-state not transition). TASK_ID: feat/322-tracker-phase-0
Captures two immutable golden fixtures from the post-A1 tree per P0-S14/S15. tests/fixtures/golden/git-agent.md: byte-equal to src/assets/agents/git.md (951L). tests/fixtures/golden/github-status-lines.txt: 215 lines extracted from the cited line ranges in git.md, code.md, dynamic-build.mds, and resolve.mds. G0.2: cmp passes, all 27 literals grep-verified at their cited source lines. These fixtures are immutable for the remainder of Phase 0-3; a mismatch means the source is wrong, never the fixture.
…n, op corrections
P0-S16: tests/seams/command-agent-input.test.ts — two-sided seam test (3 directions);
forward key check (mode 'sole', exact-match), reverse Input coverage, issue_capture_contract
producer check; per-type non-vacuity (Git+Code); op→section map built once [DR-24]; ≥14
DIST_FILES; known-bad inline `ISSUE: 42` sample proves RED on wrong key (H10, AC-0.1).
P0-S17: tests/helpers.ts — resolveAgentSource, resolveAllAgents (dist-preferred, src-fallback,
ENOENT-tolerant on dist); extractOpSectionFromCorpus with explicit 'sole'/'union' modes, no
default [DR-18]; gitAgentSinkCorpus (git.md ∪ dist/skills/git/references/*.md); parseFences,
isAgentBlock (verbatim from registry-integrity.test.ts:449-456); loadGolden (throw-on-absent);
extractStatusLines (pure function over P0-S15 line ranges, produces 215-line/16245-byte fixture).
tests/guards/agent-source-resolver.test.ts — resolver guard + both DR-18 mode unit cases with
RED proofs.
P0-S18: tests/git-agent.test.ts:359-425 — D11 forward/reverse/bypass guards repointed to
gitAgentSinkCorpus() + extractOpSectionFromCorpus { mode: 'union' }; match count available;
GIT_AGENT_PATH via resolveAgentSource('git'); extractOpSection wrapper for readability (AC-0.8).
P0-S19: tests/git-agent.test.ts — manage-debt: 60000 pin inserted after post-wave-report cap
it, before backlink-shipped-issues (AC-0.12, GAP-21).
P0-S20: tests/git-agent.test.ts REQUIRED_OPS 15→17 (+fetch-issue, +fetch-issues-batch);
tests/registry-integrity.test.ts INTERNAL_OPS removes fetch-issues-batch with SG-11 rationale
comment; Guard 1 agent check routed through resolveAgentSource (AC-0.7, AC-0.11, GAP-49).
P0-S23: tests/goldens/git-agent-golden.test.ts + tests/goldens/github-status-lines.test.ts;
scripts/update-golden.js (named target required; github-status-lines refuses without --unfreeze
per DR-03); package.json test:golden:update script; Phase-0 byte baselines as named exports
in github-status-lines.test.ts (C6).
Tests: 4051 passed / 110 files — zero failures.
…esolver (P0-S21–S25)
New guards (P0-S22):
- tests/guards/retired-wording.test.ts: per-phase allowlist for 5 Phase-0 retired
literals (ISSUE_NUMBERS, ISSUE: {issue, close milestone, may pre-fetch,
issue-first gate); mechanic-2 inline RED proof; corpus = src/assets/ + dist/commands/
- tests/fixtures/numeric-floors.json + tests/guards/numeric-floor-manifest.test.ts:
DR-27a mechanization — 8 pinned numeric floors; pattern-exists check + decrement RED proof
- tests/guards/extended-references.test.ts: P0-b Extended References resolver guard;
references/tracker/ generated-path exception asserted non-empty
Modifications:
- tests/build-mds.test.ts: DIST_FILES constant (requireDistFiles(), 14 entries);
bug-analysis added to SKILL_CHECK_HOSTS; §20 DIST_FILES non-vacuity + compliance_gate
adoption guard (6 hosts); §21 gh-issue scope guard; fix title/body mismatch (P0-S21)
- tests/agent-name-guards.test.ts:~749: resolveAgentSource(agentSlug).path replaces
literal AGENTS_DIR join — AC-0.7 (Phase 1 renames must not break guards)
- tests/shell-hooks.test.ts: non-contiguous v3 ensure-root-gitignore fixture (P0-S24)
- tests/init-logic.test.ts: non-contiguous v3 computeDevflowGitignore fixture (P0-S24)
- .github/workflows/ci.yml: npm run test:integration step added (P0-S25)
Gate G0.3: npm run build EXIT=0; npm test EXIT=0 (4065 tests / 113 files, 14 new);
npm run test:integration EXIT=1 (pre-existing: Simplify agent apply-decisions injection,
commit 9db3d85, not caused by A3b). All 43 other integration tests pass.
Refs #322
…LOG, KB citations (A4) - Add docs/reference/platform-assumptions.md: 5 rows (4 confirmed Claude Code facts + 1 UNMEASURED Bash truncation limit with observable symptom column) - CHANGELOG ### Fixed ×5: debug#42 key, plan fetch-issue, gh-issue scope guard, D9 authority consolidation, release milestone step removal (ACs 0.1/0.3/0.4/0.5/0.14) - dynamic-workflow-engine KB: document DIST_FILES=14 vs ALL_HOSTS=13 permanent divergence (SG-13) at two citation sites; add Deliberate Exceptions section for AC-0.4 guard (gh pr view ×3 and release.md:85 local-file-read exception) - resolve-pipeline KB: update 4 stale citations — test list expanded with 7 Phase-0 guard files, REQUIRED_OPS updated to 17, manage-debt/fetch-issues-batch bounds updated, INTERNAL_OPS corrected (fetch-issues-batch removed, now live from plan.mds) - feature-knowledge-system KB: fix 1 stale citation — ALL_HOSTS=13 MDS-compiled, DIST_FILES=14 with hand-authored release.md (SG-13 cross-reference) Gate G0.4: 4065 tests PASS; integration PASS (hud-git/pack-install/ambient-activation); subagent-skill-preload 1 pre-existing failure (helpers.ts:309 isolation defect, not this branch); golden SHAs verified; negative-grep 0 results; EXIT=0.
Pre-existing isolation defect: getAllSubagentPreloadedSkills scanned ALL
recent sessions for the cwd, so a concurrent pipeline agent (Code/Validate)
whose preload superset contained apply-decisions could satisfy the Simplify
find() predicate and fail the not.toContain('apply-decisions') assertion.
Fix (D33): runClaudeAndWait snapshots UUID session dirs before spawn and
diffs after close/timeout to recover the exact sessionId.
getSessionSubagentPreloadedSkills reads only that session's subagents/
directory — concurrent agents in the same cwd cannot contaminate the result.
A 3 s post-SIGTERM wait is added to the timeout path so the independently
running subagent has time to flush its initialization transcript (skill
preloads appear in the first JSONL lines) before the caller reads them.
Test prompts are hardened to explicit read-only constraints so agents
spawned with --dangerously-skip-permissions cannot write files or commit
(the Git agent ran git commit --allow-empty in an earlier run).
Unit tests for selectTranscriptsBySession are in helpers.test.ts.
… in the preload suite
The previous implementation used a directory-diff snapshot to infer the
session id after spawning: it enumerated UUID-named directories under
~/.claude/projects/<encoded-cwd>/ before the spawn and diffed after.
This was fragile in two ways:
1. A background Claude process (devflow memory worker, or a concurrent
pipeline agent) running in the same cwd could create a new UUID
directory at the same moment, causing the diff to pick the wrong
directory or return null from the tie-break.
2. When the haiku parent answered the prompt directly — without calling
the Agent tool — no subagents/ directory was created at all, and null
was indistinguishable from 'concurrent session won the diff'. The
assertion message said "No session_id in claude output" even though
nothing in the code reads claude's output.
Fix:
- runClaudeAndWait now generates a UUID with randomUUID() and passes
--session-id <uuid> to claude -p. The session identity is known before
the process starts; no diff or directory scan is needed. The directory-
diff snapshot code (existingDirs, findSessionId) is deleted (ADR-003:
end-state, no residue).
- A bounded OUTPUT_TAIL_BYTES rolling buffer captures combined
stdout+stderr. The tail is included in failure messages.
- getSubagentPreloadResult(sessionId) replaces the raw session dir read.
It returns a typed discriminated union: no-session-dir (parent spawned
no subagent), no-transcripts (dir exists but no agent-*.jsonl files),
or ok (transcripts found). This makes the assertion message say what
actually happened.
- buildSubagentsPath(homeDir, cwd, sessionId) is extracted as a pure,
exportable function and unit-tested (PF-043, PF-018).
- The parent prompt is tightened to an explicit imperative ("You MUST
call the Agent tool exactly once…") so haiku cannot answer directly.
- A MAX_SPAWN_ATTEMPTS = 2 bounded retry is added for the no-session-dir
case: LLM non-determinism occasionally causes the parent to answer
directly even with an imperative prompt; one retry is a legitimate
mitigation; the retry count is a named constant and the attempt is
logged.
- The 3 s post-SIGTERM wait is retained: it addresses a separate race
where the spawned subagent is still writing its initialization
transcript after the parent is killed.
Verification: two consecutive npx vitest runs of the preload suite both
returned 7/7; npm run test:integration returned 52/52; npm test returned
4065/4065; npx tsc --noEmit clean.
Five defects found reviewing the Phase-0 harness against its own contract.
No file under src/assets/ or tests/fixtures/golden/ is touched.
P0 — the seam test asserted nothing (AC-0.1).
Compiled fences carry the agent prompt as a quoted, sometimes indented prose
block ('"OPERATION: fetch-issue'), so the bare-line-start anchors matched 0 of
the 18 Git fences in dist/commands/. keysPassedByOp stayed empty and Directions
1 and 2 iterated nothing while every assertion stayed green; the RED proof used
a synthetic shape that occurs nowhere in the corpus (PF-043). Anchors now
tolerate leading whitespace and the opening quote; the known-bad sample is the
verbatim pre-A1 debug.mds text and runs through the same parser as the live
scan, with a GREEN post-A1 counterpart. Verified RED against a simulated pre-A1
dist and GREEN after. Added the assertion that would have caught this: every
prose Git fence mentioning OPERATION: must actually be parsed, plus a floor on
the number of ops with callers. Multi-agent ```js recipe fences are excluded
explicitly (one dist-build fence holds 24 calls across 9 agent types, so
fence-level key attribution is meaningless there) with a live-arm assertion.
D9: is excluded as a decision-ledger annotation, as a literal set so a future
D12: that is a field fails loudly.
P0 — npm test regenerated the frozen golden.
The --unfreeze acceptance test ran update-golden.js against the live fixture,
rewriting tests/fixtures/golden/github-status-lines.txt on every run including
CI. A drifted source would fail once and pass forever after. The script now
takes --out-dir; the test writes to a temp dir and asserts the output equals
both extractStatusLines() and the frozen fixture, with a second test pinning
the fixture's mtime. Fixture mtime is now unchanged across full suite runs.
P1 — the numeric-floor guard missed partial decreases and unenforceable pins.
toBe(14) appears at 3 sites and 60_000 at 21; a presence-only check could not
see one site lowered, and the decrement probe silently no-opped on grouped
numerals (60_000 does not contain 60000) while only ever probing floors[0].
Entries now record occurrences and the guard requires that many matches; the
probe runs over every entry, handles both numeral spellings, and a new
invariant rejects any entry whose pattern does not encode its floor. Added the
six exit-gate floors that were unpinned, including the D11 >= 8 in
git-agent.test.ts (the existing >= 8 entry pins a different floor in
plugins.test.ts). Verified RED by lowering one of the three toBe(14) sites.
P1 — AC-0.3's fetch-issues-batch guards did not exist.
The <=50 bound, TRUNCATED ({n} not processed), the ## Issues Batch header and
the single-GraphQL-query mechanic appeared under tests/ only inside golden
fixtures, which are data. Added four named assertions in Guard 2. The header is
asserted whole-file because extractOpSectionFromCorpus ends a section at the
next '## ' and the header is itself a '## ' line inside the op's Output
template; that scope caveat is now recorded in the KB, which had also claimed a
fetch-issues-batch bound guard that was never written.
P1 — dangling export left by the simplify commit.
getSessionSubagentPreloadedSkills had no consumer after fac739e removed its
sibling; deleted per ADR-003.
Also tightened the seam producer direction to word-boundary matching so
ISSUE_REFS can no longer satisfy ISSUE_REF.
Verification: npm run build clean, npx tsc --noEmit clean, npm test
4076/4076 (was 4065), npm pack --dry-run OK, goldens and src/assets byte-identical.
selectTranscriptsBySession and the TranscriptRecord interface it alone used are dead code: getSubagentPreloadResult (the production path) reads the session directory by exact path and never calls the selector. Remove both, their five unit tests, and their import lines. Closes #322
…e-golden into tsx
… (F2) Implements all misalignments from the alignment-fix pass: - M1 (D9 caller guard): cross-file pin that resolve.mds and dist/commands/resolve.md carry the exact D9 rule fragment from git.md's resolve-review-threads section - M2a (manage-debt D4): explicit it() asserting **Degradation (D4):** clause and (pending — TRACEABILITY: DEGRADED site in manage-debt op section - M2b (remote-I/O D4 loop): derives posting/mutation ops from corpus text (not a hand list, PF-049); accepts **Degradation (D4):** or TRACEABILITY: DEGRADED as equivalent evidence; excludes GraphQL (read-only ops like fetch-issues-batch) - M2c (pending sites): pins 4 (pending sites in resolve.mds and dist/resolve.md, each naming DEGRADED on the same line - M3 (containment guard): file-scoped per-op slices count ops with <untrusted-issue-body> (>= 2; fetch-review-threads uses <external-thread>); negative arm checks summary ops for remote field interpolation - M4 (dist fail-loud): removes silent `if (!distExists) return` skip; replaces with fail-loud expect(distExists).toBe(true) in registry-integrity.test.ts - M8 (DIST_FILES adoption x2): both COMPLIANCE sweep guards iterate DIST_FILES (14 files) instead of ALL_HOSTS (13) so release.md is covered; adds explanatory comments on the two ALL_HOSTS guards that are correctly scoped (compiler-only) - M9 (matchCount + floor comment): calls extractOpSectionFromCorpus directly to surface matchCount; seam test comment notes spec 16 vs corpus 13 floor - M11 (synthetic dist fixture): agent-source-resolver.test.ts gains beforeAll/ afterAll that create ROOT/dist/agents/git.md; removes dead scaffolding (unused imports, vars); switches src-fallback test to 'code' agent - M12a/b/c (non-vacuity fixes): retired-wording and extended-references probes now call the same collector as the main guard; build-mds extracts a named collectGhIssueProseViolations() used by both guard loop and probe - M12d (numeric floors): adds manage-debt-archive-cap and d10-dedup-marker-floor (occurrences=3 after M3 guard contributes a third >= 2 site) entries - M13 (extensionless hooks): retired-wording corpus adds '' to exts so src/assets/scripts/hooks/* extensionless files are scanned; installer-new.test.ts gains a P1 repoint comment on the literal src/assets/agents path - Guard 6 (OPERATION: regex): fixes /^OPERATION: (\S+)/gm → /^[ \t]*"?OPERATION: (\S+)/gm to match compiled MDS fences where lines start with a quoted string All 4086 tests pass; golden shasums unchanged; src/assets/** and tests/fixtures/golden/** were not touched. TASK_ID: feat/322-tracker-phase-0
Replace the beforeAll/afterAll writes to ROOT/dist/agents/git.md with a
hermetic mkdtempSync root. Vitest parallelism meant the sentinel could race
with every other test calling resolveAgentSource('git'), causing nondeterministic
failures and leaving a stale file on crash (ADR-003, PF-043).
Changes:
- resolveAgentSource(name, root = ROOT) and resolveAllAgents(root = ROOT):
injectable root param; all existing call sites unchanged (default = ROOT)
- agent-source-resolver fixture: temp dir with copies of all 16 real agent
files in src/assets/agents/ and a sentinel in dist/agents/git.md only
- New assertions: resolveAllAgents(tmpRoot) covers declared.length agents;
real-tree origin check made conditional on dist/agents/<name>.md presence
(Phase 1 safe); resolveAllAgents(tmpRoot) size uses declared.length (not
literal 16) to keep the numeric-floor-manifest pin at occurrences:1
…body summarisation
Add **Degradation (D4):** clause to both fetch-issue and fetch-issues-batch
ops (MIS-2): gh unauthenticated/absent/rate-limited at fetch time returns
TRACEABILITY: DEGRADED ({reason}), no issue content; /plan proceeds from
task description alone.
Restore {body summary} inside <untrusted-issue-body> for both ops (MIS-4):
the containment fix (4a00484) switched the placeholder from {body summary}
to {body}, adding an undocumented sixth user-visible change; restoring
summarisation keeps the enumeration at five.
Closes #322
…fter D4 fix Re-anchor extractStatusLines() in tests/helpers.ts after D4 degradation additions to fetch-issue (line 268) and fetch-issues-batch (line 314): - Lines < 268: unchanged - 268 <= N < 312: shift +2 - N >= 312: shift +4 Extended fetch-issue range to getLines(git, 268, 290) and fetch-issues-batch to getLines(git, 314, 339) so both D4 clauses appear in the captured corpus. Updated github-status-lines.test.ts: - Added PRE_PHASE0_GIT_MD_CHARS = 59_376 / PRE_PHASE0_GIT_MD_LINES = 938 baseline constants - GIT_MD_CHARS 60_440 -> 61_018, GIT_MD_LINES 959 -> 963 (post-D4) - SKILL_GIT_CHARS 9_236 -> 9_204, SKILL_WORKTREE_CHARS 2_950 -> 2_942 (was untied) - TOTAL_CHARS 72_626 -> 73_164, TOTAL_LINES 1_334 -> 1_338 - FIXTURE_BYTES 16_749 -> 17_379, FIXTURE_NEWLINES 225 -> 233 - Added live-file assertions for SKILL_GIT_* and SKILL_WORKTREE_* constants Updated numeric-floors.json: - git-md-lines floor 959 -> 963 - git-md-chars floor 60_440 -> 61_018
Re-captured with: npx tsx scripts/update-golden.ts git-agent npx tsx scripts/update-golden.ts github-status-lines --unfreeze git-agent.md: 61018 chars (was 60440); cmp with src/assets/agents/git.md exits 0 github-status-lines.txt: 17379 bytes, 233 newlines (was 16749 / 225)
The change-3 'before' clause was false: the pre-Phase-0 Git agent had
zero <untrusted-issue-body> occurrences — it did not wrap even {body}.
Rewrote to match §13 row-3: 'issue title, body, labels, acceptance
criteria, and dependencies reached Design agents unwrapped, with no
<untrusted-issue-body> containment tag of any kind.' (MIS-3)
…oor (MIS-1/MIS-2)
…collector probes, seam input scoping (MIS-5–9)
Captures the agent-source resolver API, goldens lifecycle, guard conventions (non-vacuity, DIST_FILES vs ALL_HOSTS, OPERATION: anchor), seam test three-direction contract, numeric floor manifest, and integration test hazards for the Phase 0 test harness (PR #327).
Removes the two internal seam corrections (gh issue routing and D9 divergence) from the [Unreleased] Fixed section — they stay documented in the PR body. The section now enumerates exactly the five user-visible fixes (AC-0.1, AC-0.3, AC-0.10, AC-0.14, and the pending-state fix). Closes #322
…x-round labels - Item 1 (baselines): retitle github-status-lines constants to "char baselines (JS .length, not bytes)"; rename PRE_PHASE0_GIT_MD_CHARS (wc-c) to PRE_PHASE0_GIT_MD_BYTES=59_376 and add verified PRE_PHASE0_GIT_MD_CHARS=58_903 (.length); delete the units-artifact 472-ch arithmetic sentence; annotate FIXTURE_BYTES/FIXTURE_NEWLINES as Buffer.byteLength bytes, not JS .length; rename describe blocks and error messages from "post-M3" to "Phase-0". - Item 2 (P0-S13 verify): add test in ensure-devflow-init behavioral asserting the fast-path gates on .root-gitignore-configured-v3 with no -v2 reference. RED: e726874 hook has -v2, no -v3. GREEN: current. - Item 3 (fix-round labels): strip MIS-1..9, MIS-5a, MIS-6a, post-M3, M12b, M13 across 8 test files and numeric-floors.json (22 sites). grep -rn 'MIS-|post-M3|M12b|M13\b' tests/ returns 0. - Item 4 (AC-0.10 record): add 4-line comment above containment guard recording the accepted mechanisation (>= 3 ops, one class, negative arm, <external-thread> pre-exists on main). - Item 5 (inaccurate comment): reword ~:562 to say the op set derives from REQUIRED_OPS but the 12 indicators are an explicit list. Closes #322
…t markers - Wrap setup-task's remote-sourced issue fields (title, description, criteria) in <untrusted-issue-body> tags, keeping the locally-derived issue number outside, so Principle 8's claim that all remote bodies are wrapped is now true. - Expand fetch-issues-batch Output template to show issue #2 with its full wrapper (not an elision), and add an explicit per-issue wrapping sentence. - Add Principle 8 marker-neutralisation sub-rule: before wrapping, scan for the literal closing marker and insert a backslash before the slash so an attacker cannot close containment early. - Add one-line pointer to the neutralisation rule in each affected operation: fetch-issue, fetch-issues-batch, setup-task, and fetch-review-threads.
… capture names - Add explicit carve-out to the MANDATORY Gate 0 spawn ban: Step 0's issue fetch precedes and informs Gate 0 and is the sole exception, so a model reading both lines no longer sees a contradiction that could cause it to silently skip the fetch. - Remove ISSUE_ID and ISSUE_URL from the capture list — neither name is emitted by fetch-issue or fetch-issues-batch; keeping them violated ADR-003 (no artifact without a reachable producer). ISSUE_CONTENT, ACCEPTANCE_CRITERIA, and ISSUE_REF are all derivable from the output templates and are retained.
…lean tree The learn-conventions operation writes .devflow/conventions.md, which is a git-tracked carve-out (ensure-root-gitignore re-includes it). Without a commit step, every fresh project leaves "?? .devflow/conventions.md" in git status. Add a non-blocking commit step after the Output block, mirroring the Knowledge agent pattern (knowledge.md:64-68): guard for detached HEAD, check for changes, stage and commit only .devflow/conventions.md via scoped pathspec, never push, never force, never amend. Errors are reported as CONVENTIONS_COMMIT: failed and never abort the caller's workflow.
…tall into a scratch HOME
Adds tests/integration/clause-ii-file-residue.test.ts — the composition step
neither pack-install.test.ts nor init-e2e-flags.test.ts performs:
pack real tarball → npm install into scratch node_modules → create throwaway
git repo (git init + commit .gitignore + index.js) → devflow init --recommended
with HOME pointed at a fresh mkdtemp scratch home → assert git status --porcelain
has no untracked (??) entries.
FINDING — clause (ii) is currently violated:
Observed git status --porcelain after devflow init --recommended:
M .gitignore
?? .claudeignore
devflow init --recommended calls installClaudeignore() whenever the CWD is a git
repo (claudeignoreEnabled = !!earlyGitRoot, set before the --recommended path runs;
the interactive claudeignore prompt in the advanced path is never reached). The
resulting .claudeignore file is untracked — a genuine clause-(ii) leak.
The test's clause-(ii) assertion is marked .fails() to document this finding
without papering it over with an allowlist. Remove .fails() once the residue is
fixed (e.g., add .claudeignore to the committed .gitignore, gitignore it via the
devflow carve-out, or add a --no-claudeignore flag).
Positive (non-vacuity) assertions also present:
- .gitignore shows as modified (proves init ran and wrote the carve-out).
- scratch HOME received ~/.devflow/manifest.json (proves init wrote to the
isolated scratch HOME, not the developer's real HOME).
- All other tests green: pack, install, git repo setup, init exit 0.
…hing op sets
Defect: the AC-0.10 containment guard used a combined predicate
(<untrusted-issue-body> OR <external-thread>) with floor 3. On main,
three pre-existing <external-thread> ops (fetch-review-threads,
post-resolution-summary, post-wave-report) satisfied the floor,
so the guard passed on the unmodified tree and could not detect that
the new <untrusted-issue-body> containment was never added.
Fix: split into two independent assertions.
(a) Issue-body containment: predicate <untrusted-issue-body> ONLY,
floor 3, named set {setup-task, fetch-issue, fetch-issues-batch}.
Non-vacuity proof: on main, 0 ops match — the floor fails.
Named set prevents an unrelated op from satisfying the floor.
(b) External-thread containment: predicate <external-thread> ONLY,
floor 3, named set {fetch-review-threads, post-resolution-summary,
post-wave-report}. Stabilisation assertion: any silent removal of an
expected op fails the toContain check.
numeric-floors.json: replace the single containment-ops-floor entry
with two entries (containment-issue-body-floor,
containment-external-thread-floor), both floor 3 occurrences 2.
Descriptions now name the correct matching op sets.
…onsumer Defect: Direction 3's producer check searched DIST_FILES (compiled commands) for variable names like ISSUE_CONTENT and ISSUE_REF. The only hits were plan.md's own capture lines — the consumer, not the producer. The test grepped the consumer and called it a producer. This vacuity concealed that ISSUE_ID and ISSUE_URL had no producer anywhere (git.md emits no URL field and labels the id only via the heading, not a separate ISSUE_ID label). Fix: - Point the search at git.md (via gitCorpus built in beforeAll), not DIST_FILES. The consumer (plan.md) is excluded by construction. - Use FILE-SCOPED slicing (not extractOpSectionFromCorpus) because fetch-issue and fetch-issues-batch Output templates contain ## Issue # headings that would truncate the section at \n## , hiding the <untrusted-issue-body> content (same pattern as Guard 10). - Match on emitted field patterns (e.g. '<untrusted-issue-body>', 'Acceptance Criteria', '## Issue #') — not variable names, which never appear in git.md. - Remove ISSUE_ID and ISSUE_URL: no emitted producer exists for either (removed from plan capture list in c7bff85, ADR-003). - Floor updated from 5 to 3 in numeric-floors.json to match the corrected contract size. Non-vacuity proof: temporarily re-adding ISSUE_URL triggers: ISSUE_URL: pattern "ISSUE_URL" not found in git.md fetch-issue or fetch-issues-batch Output
installClaudeignore() writes .claudeignore unconditionally when the CWD is a git repo, leaving it as an untracked '??' entry in git status after devflow init --recommended (clause-ii violation). Fix: add '.claudeignore' as the final line of the devflow-managed .gitignore block, bump the marker to v4, and add upgrade paths: v3→v4: append .claudeignore only v2→v4: append !.devflow/conventions.md + .claudeignore Both implementations (TypeScript ensureDevflowGitignore and shell ensure-root-gitignore) are updated byte-identically. Cross-parity tests confirm shell and TS produce identical output for all input cases.
…nore is ignored Remove the .fails() wrapper from the clause-ii assertion. The fix in the preceding commit adds .claudeignore to the devflow gitignore block, so devflow init --recommended now leaves git status --porcelain as ' M .gitignore' only — no untracked '?? .claudeignore' entry. 8/8 integration tests pass.
…ne offsets Rewrites extractStatusLines() in tests/helpers.ts to use unique text anchors (gitOp/between/singleLine helpers) instead of hard-coded line offsets. The old sampler was broken after commits 75f13e7, c7bff85, and 97f421a grew git.md from 963 to 989 lines. Proof gate: baseline extraction against b6928e5 is byte-identical to the frozen fixture (github-status-lines.txt). HEAD extraction differs from the frozen fixture — the three production commits changed content inside sampled ranges (setup-task, fetch-issues- batch, fetch-review-threads). The fixture is not updated here; regeneration requires explicit user authorisation per DR-03/AC-0.9. Also converts the git.md line/char toBe assertions to toBeGreaterThanOrEqual so growth is allowed but shrinkage below the Phase-0 baseline is caught, and registers the floor assertions in tests/fixtures/numeric-floors.json with literal-number patterns so the DR-27a manifest guard can verify enforceability.
Adds five new Fixed entries (items 6–12, with 7-9 folded) covering the undeclared D9 gate narrowing and the six changes that landed in today's commits, plus an Upgrade note for the v3→v4 gitignore marker bump.
…t changes Authorised unfreeze. Three vocabulary changes from 75f13e7: setup-task containment, per-issue batch wrapper, external-thread neutralisation note. Byte/line freeze baselines updated in the same commit so the tree stays green at every boundary.
…hadowing feature knowledge bases
This was referenced Sep 6, 2026
Open
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.
Problem Being Solved
The command→agent issue seam is untyped and five defects are live:
/plannever fetches issues,/debugpasses an undeclared key, three sites bypass the Git agent for issue operations,resolve.mdscontradictsgit.md's D9 authority, andrelease.mdpromises a step that does not exist. The harness Phases 1–3 depend on does not exist in a form that survives an agent rename.Key Changes to Highlight
A1 (4aa15c5) — Prompt-text fixes (19 edits) incl. the
fetch-issues-batchsingle-query rewrite [DR-07]A2 (b9d36ed) — Two golden fixtures (git-agent, github-status-lines) captured from the post-A1 tree
A3 (6d595b6, 83594b9) — Resolver, seam test, D11 union, guard-gap closures, numeric-floor manifest [DR-27a], CI integration
A4 (e8c4055) — Docs sweep (platform-assumptions.md, CHANGELOG, KB citations)
Pipeline gate commits — integration-test isolation, Simplify, Scrutinize, alignment fixes, cleanup, knowledge write-back
Post-review fixes (75f13e7 – a2207ac) — containment gaps in
setup-taskand batch template, Gate 0 / Step 0 contradiction inplan.mds,learn-conventionscommit step,.claudeignoregitignore (marker v4), seam and guard repairs, golden re-capture, CHANGELOG expansion, KB updateInternal seam corrections (not user-visible):
gh issuerouting through the Git agent (AC-0.4)Ten User-Visible Behaviour Changes
/plan #42parsed the reference and consumed a body it never fetched/plan #42spawnsOPERATION: fetch-issue;/plan #12 #15 #18spawnsOPERATION: fetch-issues-batch(one query,≤50,TRUNCATED ({n} not processed))/debug #42passedISSUE: {issue number}— a keyfetch-issuedoes not declare; the issue path never worked/debug #42passesISSUE_INPUT: {issue reference}and the issue path worksfetch-issue/fetch-issues-batch: issue title, body, labels, ACs, and dependencies reached Design agents unwrapped<untrusted-issue-body>with a data-only note appended;### Suggested Branch(locally-derived) stays outside the blockresolution-summary.mdemitted a bareTracked = (pending)Tracked = (pending)names the DEGRADED reason, at all four sitesrelease.mdpromised "close milestone" — a step that does not existresolve.mdsauthorised the Git agent to auto-resolve threads on any of three verdicts:FIXED,FALSE_POSITIVE, orBY_DESIGNFIXEDandcommit_shais non-empty — matching the D9 contractgit.mdhad always enforced, closing a live divergence (closes PF-024)setup-taskemitted issue title, description, and ACs as bare bullets;fetch-issues-batchtemplate showed the wrapper on the first issue only, leaving up to 49 uncontained; no op defended against remote content containing the literal</untrusted-issue-body>closing markersetup-taskwraps all remote-sourced fields; batch template explicitly shows the wrapper on every entry; Principle 8 mandates neutralising any closing marker found in remote content before wrapping, with pointers from all four affected operationsplan.mdsdeclared "Do not spawn any agents until Gate 0 is confirmed" with no exception, directly contradicting the Step 0 issue fetch — a session honouring the ban could silently skip the fetchlearn-conventionswrote.devflow/conventions.md— a git-tracked carve-out path — but included no commit step, leaving?? .devflow/conventions.mdingit statuson every fresh projectgit add -A, never push, never force)devflow initwrote.claudeignoreinto any git repo but never ignored the file, leaving?? .claudeignoreingit statuson every fresh install.claudeignoreis included in the devflow-managed.gitignorecarve-out block (markerv4)Honest label: "no change to the GitHub rendering of any existing traceability artifact." Not "no behaviour change" — see the ten above.
Breaking Changes
none.
Reviewer Focus Areas
Commit order — A1 (4aa15c5) edits
git.mdonly, no test file touched. A2 (b9d36ed) contains only the two fixtures, captured from the post-A1 tree. A3a/A3b split is pre-authorised. A4 (e8c4055) is the docs sweep.RED proofs — Verify seam known-bad at
tests/seams/command-agent-input.test.tsagainst the pre-A1 parser. D9 caller attests/git-agent.test.tsassertsresolve.mdsanddist/commands/resolve.mdcarry the D9 rule literal fromgit.md.manage-debtD4 atgit-agent.test.ts(AC-0.6a).gh issuescope viacollectGhIssueProseViolationsattests/build-mds.test.ts. Batch single-query [DR-07] atgit-agent.test.ts.Numeric floor manifest [DR-27a] —
tests/guards/numeric-floor-manifest.test.tsmechanises AC-0.17 againsttests/fixtures/numeric-floors.json(18 entries, occurrence-aware). GREEN: no numeric literal intests/decreases — with one argued exception (see Deviations from the Plan §1).Recorded exceptions:
src/assets/commands/release.md:85— conventions read (allowlisted)src/assets/commands/code-review.mds:77—gh pr view(allowlisted)src/assets/commands/bug-analysis.mds:44—gh pr view(allowlisted)[DR-03] —
npm run test:golden:updaterequires a named target, refusesgithub-status-lineswithout--unfreeze, has--out-dirso tests never rewrite the frozen fixture.Deviations from the Plan
Four deliberate departures from stated acceptance criteria, each argued below.
1. A numeric floor was decreased (AC-0.17 exception).
issue-capture-contract-sizeintests/fixtures/numeric-floors.jsonwent from 5 → 3. AC-0.17 states no threshold may be lowered. This is a deliberate, argued exception: the old value of 5 countedISSUE_IDandISSUE_URL, neither of which had a producer anywhere ingit.mdor a consumer inplan.mds— the floor was inflated by two erroneous entries. Lowering from 5 to 3 corrects the manifest to match the actual seam, not the imagined one. This is the only floor decrease in the manifest.2. The golden regeneration commit (
3a95c92) is not strictly fixture-only.That commit carries
tests/goldens/github-status-lines.test.tsalongside the.txtfixture, because that file holds the fixture's own byte/newline freeze baselines (17_379→17_914,233→246). Splitting them would leave the tree red at the commit boundary, which this branch's own rules forbid (all CI gates must be green at every commit). The AC's intent — no behaviour change in the golden commit — is preserved: the test file change is the baseline constant update, not a logic change.3. The frozen
github-status-lines.txtwas unfrozen and re-captured (AC-0.9 / DR-03).Third re-capture within Phase 0 (after
a5dd078and38db29e), on explicit user authorisation. The diff is exactly three hunks, all genuine status-line vocabulary introduced by75f13e7:setup-taskcontainment, the per-issue batch wrapper, and the external-thread neutralisation note. Context that matters for Phase 1: the fixture was previously extracted by hard-coded line offsets, so it broke on any insertion anywhere above a sampled range — it went red on a 26-line insertion that changed no status line at all, and would have failed outright at Phase 1 whengit.mdbecomes MDS-generated.e276175re-anchors extraction on content, proven faithful by reproducing the pre-change fixture byte-for-byte fromb6928e5before being run against HEAD.4. Phase 0 ships ten user-visible changes, not five.
Scope grew from review findings. Six of the ten were always on the branch (
/planfetch,/debugkey, containment,Trackedreason,close milestone, D9 gate — the last was present but undeclared, explaining why the CHANGELOG previously understated the count). Four are new: thesetup-task/batch/marker containment gaps, the Gate 0 / Step 0 contradiction,learn-conventionscommit step, and.claudeignoregitignore.97f421aand7074733are isolated commits and can be lifted out if the reviewer prefers a tighter phase.Test Inventory (AC-0.13)
tests/seams/command-agent-input.test.tsKEY: passed is declared/plan,/debugundeclared keystests/seams/command-agent-input.test.tsrequired INPUT is passedtests/seams/command-agent-input.test.tsissue_capture_contract()searches agent source, consumer excluded; RED proof viaISSUE_URLre-addtests/seams/command-agent-input.test.tstests/git-agent.test.tsgit.mdresolve-review-threadssection literal matchesresolve.mdstests/git-agent.test.ts60000char cap +DEGRADEDreason presencetests/git-agent.test.ts≤50issues,TRUNCATED ({n} not processed),## Issues Batch ({n} issues)gh issuescopetests/build-mds.test.tsviacollectGhIssueProseViolationscompliance_gateadoptiontests/git-agent.test.tsgh api graphqlnotgh issue viewlooptests/git-agent.test.tsfetch-issue,fetch-issues-batch,setup-task}; floor 3 on issue-body opsmain— recorded RED proof (previously the broad>= 3predicate passed on unmodifiedmainvia pre-existing<external-thread>ops, validating nothing for issue-body; now split and named)tests/git-agent.test.tsfetch-review-threads,post-resolution-summary,post-wave-report}; stabilisation guardmainsince those ops pre-existed; labelled as such rather than dressed up as validationtests/git-agent.test.ts>=8posting ops + known-bad synthetic corpustests/git-agent.test.tsINTERNAL_OPStests/registry-integrity.test.ts:397-405fetch-issues-batchremoved with SG-11 rationaletests/goldens/git-agent-golden.test.ts<<</>>>marker device on mismatchtests/goldens/github-status-lines.test.tstest:golden:update -- github-status-linesrefuses without--unfreezetests/goldens/github-status-lines.test.tstests/guards/numeric-floor-manifest.test.tsByte/Char Baselines (P0-S1)
Post-Phase-0 (HEAD
a2207ac):src/assets/agents/git.md: 989 lines / 63,646 chars (JS.length) / 64,135 bytessrc/assets/skills/git/SKILL.md: 9,204 charssrc/assets/skills/worktree-support/SKILL.md: 2,942 charstests/fixtures/golden/github-status-lines.txt: 17,914 bytes / 246 newlinesPre-Phase-0 (
main@e726874):git.md: 938 lines / 59,376 bytes / 58,903 charsConstants live in
tests/goldens/github-status-lines.test.tsand are asserted against the live files.Prefix-Shippability Evidence (§14 clause i–iv)
Run on HEAD
a2207ac:npm run buildexit 0 (13 MDS hosts compiled,dist/commands/14 files)npx tsc --noEmitexit 0npm testexit 0 — 4103 tests / 114 filesnpm run test:integrationexit 0 — 48 tests / 5 files (preload suite 7/7: Simplify 18.0s, Scrutinize 13.3s, Review 11.5s, Code 17.2s, Design 13.1s, Git 13.0s, Research 12.6s)npm pack --dry-runexit 0 — 375 filesf0e29b82106082b937f1e8804b998ca03e1454c08cb3ccbc1965390a95edd46f(status-lines) /6382d7698d80cdfdfe248ffe96db734a5836fb615ecd7e2634fbb7306b02384f(git-agent),cmpexit 0, fixture mtime unchanged across suitesPR link line|Branch tokenin git.md → 0git status --porcelainemptyClause (ii) — file-residue half MECHANISED; prompt half still manual (see Test Suite Status).
Test Suite Status
Full suite: 4103 tests / 114 files — everything this PR modifies is green.
A full-suite run produced 12 failures across 7 files:
redact-secrets,decisions/ledger-ops,shell-hooksjson-helper describe,eager-memory-refresh,decisions/decisions-usage-scan,build-mds,compliance-e2e. All 7 files pass 3/3 in isolation, 21 clean runs, zero isolated failures. Root cause is load-induced subprocess-spawn contention under concurrent test workers, not regressions — none of the 7 files is in code this PR modifies.Integration: 5 files / 48 tests green.
tests/integration/subagent-skill-preload.test.tsis excluded — it spawns liveclaudeagainst the developer's real~/.claudeand has historically committed to this repo mid-run.Clause (ii) mechanisation status:
tests/integration/clause-ii-file-residue.test.tspacks a real tarball, installs into a scratch$HOME, runsdevflow init --recommendedin a throwaway git repo, and asserts no??untracked entries — with a positive assertion that.gitignorewas modified, so it cannot pass by doing nothing. It found a real violation on first run (?? .claudeignore), now fixed by7074733./plan → /implement → /code-review → /resolve → /releasewalk-through remain unverified. Remaining manual scope is narrowed to four model-generalisation questions: (a) does the<untrusted-issue-body>wrapper repeat across all 50 batched issues; (b) is the literalTRUNCATED ({n} not processed)emitted; (c) does the agent build one GraphQL query or fall back to 50 sequentialgh issue viewcalls; (d) doesplan.mdsStep 0 actually fire.PR-Template Checklist (D-B)
Known Deviations
gh pr viewexceptions, not two (code-review.mds:77, bug-analysis.mds:44, resolve.mds:63 pre-exists on main)git.md:resolve-review-threadssection, not a line numberstep 1cretained; retired literal isissue-first gate>=3floor derivation; see Deviations from the Plan §1 and Test Inventory for detailtests/integration/**scope addition forced by wiringtest:integrationinto CIdocs/reference/platform-assumptions.mdhas no inbound reference yet (consumer lands in Phase 3a)Commit Subjects
Related Issues
Closes #322 · Tracking: #321