@danmoisan/drm-copilot-mcp 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/out/mcp-server.js +286 -10
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +8 -10
  4. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +477 -0
  5. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +120 -12
  6. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +130 -33
  7. package/resources/claude-customizations/.claude/rules/benchmark-baselines.md +7 -0
  8. package/resources/claude-customizations/.claude/rules/ci-workflows.md +6 -0
  9. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +24 -0
  10. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +21 -0
  11. package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +143 -2
  12. package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +9 -0
  13. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +27 -10
  14. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +10 -0
  15. package/resources/claude-customizations/config/orchestration-routing.json +1 -0
  16. package/resources/claude-customizations/pack-manifests/core.json +1 -0
  17. package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/SKILL.md +10 -0
  18. package/resources/codex-and-agents-customizations/.codex/agents/commit-steward-c1.toml +22 -0
  19. package/resources/codex-and-agents-customizations/.codex/agents/commit-steward-c2.toml +22 -0
  20. package/resources/codex-and-agents-customizations/.codex/agents/commit-steward-c3-elevated.toml +22 -0
  21. package/resources/codex-and-agents-customizations/.codex/agents/commit-steward-c3.toml +22 -0
  22. package/resources/codex-and-agents-customizations/.codex/agents/commit-steward-c4.toml +22 -0
  23. package/resources/codex-and-agents-customizations/.codex/agents/commit-steward.toml +2 -0
  24. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +7 -0
  25. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +7 -0
  26. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +7 -0
  27. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +7 -0
  28. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +7 -0
  29. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +7 -0
  30. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  31. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +477 -0
  32. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +125 -12
  33. package/resources/codex-and-agents-customizations/pack-manifests/core.json +8 -1
  34. package/resources/config/orchestration-routing.json +1 -0
  35. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +32 -2
@@ -14,13 +14,31 @@
14
14
  Feature folder resolution order:
15
15
  1. Scan the prompt text for any path matching
16
16
  docs/features/active/<token>, accepting both forward-slash and
17
- backslash separators. The longest match wins; when it points at a
18
- file (ends with .md), use its parent directory.
19
- 2. If no candidate was found in the prompt, read the feature-folder field
17
+ backslash separators. Truncate every match to two segments past the
18
+ docs/features/active/ prefix -- that is, to exactly four path segments:
19
+ docs, features, active, and the feature-folder name. Truncation is
20
+ depth-insensitive, so the feature folder itself, a spec.md path, a
21
+ research/ artifact path, and an evidence/ artifact path all resolve to
22
+ the same folder. A match that truncates to fewer than four segments is
23
+ rejected. Candidates are deduplicated preserving first-occurrence
24
+ order.
25
+ 2. Select among the distinct candidates: one candidate is used directly;
26
+ otherwise the candidate equal to the checkpoint's feature-folder field
27
+ is preferred, because the checkpoint is the orchestrator's own record of
28
+ which feature is in flight; otherwise the earliest-occurring candidate
29
+ in the prompt wins, because the orchestrator names the active feature
30
+ folder before citing artifacts inside it.
31
+ 3. If no candidate was found in the prompt, read the feature-folder field
20
32
  from artifacts/orchestration/orchestrator-state.json.
21
- 3. If neither yields a folder, block with a reason instructing the caller
33
+ 4. If neither yields a folder, block with a reason instructing the caller
22
34
  to reference a feature folder explicitly.
23
35
 
36
+ Known limitation: resolution stops at the feature-folder segment, so it does
37
+ not descend into a version folder (v1/, v2/). No versioned folder exists
38
+ under docs/features/active/ today, and issue.md sits at the feature root in
39
+ every case, so the limitation is inert; it is recorded here rather than coded
40
+ around.
41
+
24
42
  Once the folder is resolved, the hook reads the persisted work-mode marker
25
43
  (`- Work Mode: minor-audit|full-feature|full-bug|full`) from that folder's
26
44
  issue.md, per the mode contract in
@@ -30,11 +48,16 @@
30
48
  - full-bug -> spec.md only is required.
31
49
  - minor-audit -> neither is required; issue.md carries the acceptance
32
50
  criteria for this mode.
33
- - marker absent, unreadable, or unrecognized -> fail closed to the
34
- strictest set (spec.md and user-story.md), and the block reason states
35
- that the work mode could not be determined so the operator can tell
36
- this case apart from a genuine missing prerequisite. The legacy `full`
37
- marker normalizes to full-feature's requirement set.
51
+ - marker absent, unreadable, or unrecognized -> deny on a distinct
52
+ decision path that names the resolved folder and the issue.md path it
53
+ probed and states adding or correcting the marker as the remedy. That
54
+ path does not run the required-file probe and names neither prerequisite
55
+ document, because when the mode is unknown no prerequisite set is
56
+ knowable: a set containing user-story.md cannot be satisfied by full-bug
57
+ or minor-audit work without violating the lifecycle contract, and the
58
+ empty set would fail open. The delegation is still denied, so the gate
59
+ remains fail-closed. The legacy `full` marker normalizes to
60
+ full-feature's requirement set.
38
61
 
39
62
  If any required file is missing, the script emits a PreToolUse JSON
40
63
  response with hookSpecificOutput.permissionDecision='deny' and a reason
@@ -137,9 +160,14 @@ function Get-PrdFeatureRequiredFile {
137
160
  allowed.
138
161
  .DESCRIPTION
139
162
  full-feature requires spec.md and user-story.md; full-bug requires
140
- spec.md only; minor-audit requires neither. A $null or unrecognized
141
- mode fails closed to the strictest set (spec.md and user-story.md) so
142
- an undeterminable mode never becomes permissive.
163
+ spec.md only; minor-audit requires neither.
164
+
165
+ The default arm returns spec.md alone. It is not reached from the
166
+ decision path for an undeterminable mode, which denies on its own branch
167
+ without probing at all; the arm exists so a direct caller passing a $null
168
+ or unrecognized mode never receives a permissive empty set, and it must
169
+ not name user-story.md, because that document is required to be ABSENT
170
+ for full-bug and minor-audit work.
143
171
  #>
144
172
  [CmdletBinding()]
145
173
  [OutputType([string[]])]
@@ -154,7 +182,7 @@ function Get-PrdFeatureRequiredFile {
154
182
  'full-feature' { return [string[]]@('spec.md', 'user-story.md') }
155
183
  'full-bug' { return [string[]]@('spec.md') }
156
184
  'minor-audit' { return [string[]]@() }
157
- default { return [string[]]@('spec.md', 'user-story.md') }
185
+ default { return [string[]]@('spec.md') }
158
186
  }
159
187
  }
160
188
 
@@ -191,9 +219,22 @@ function Get-PrdFeatureCheckpointFolder {
191
219
  function Find-PrdFeatureFolderFromPrompt {
192
220
  <#
193
221
  .SYNOPSIS
194
- Scans a prompt string for docs/features/active/<...> path tokens and
195
- returns the longest unique match resolved to a folder path. Returns
196
- $null when no match is found.
222
+ Scans a prompt string for docs/features/active/<...> path tokens,
223
+ truncates each to exactly four path segments, and returns the selected
224
+ feature folder. Returns $null when no token truncates to four segments.
225
+ .DESCRIPTION
226
+ Truncation to four segments -- docs, features, active, and the
227
+ feature-folder name -- is two segments past the docs/features/active/
228
+ prefix, so the depth at which an artifact is cited cannot change which
229
+ folder is resolved. Candidates are deduplicated preserving
230
+ first-occurrence order; selection among two or more distinct candidates
231
+ prefers the checkpoint's feature-folder field and otherwise takes the
232
+ earliest occurrence in the prompt.
233
+
234
+ The return value is a repo-relative path normalized to forward slashes,
235
+ or $null. The function reads no file except through the existing
236
+ checkpoint seam, and it is deterministic for a given prompt and
237
+ checkpoint value.
197
238
  #>
198
239
  [CmdletBinding()]
199
240
  [OutputType([string])]
@@ -214,22 +255,56 @@ function Find-PrdFeatureFolderFromPrompt {
214
255
  return $null
215
256
  }
216
257
 
217
- $unique = @{}
258
+ # Deduplicate preserving FIRST-OCCURRENCE order. A [hashtable] must not be
259
+ # used here: PowerShell hashtable key enumeration order is unspecified, so a
260
+ # first-occurrence selection rule fed by a hashtable is not deterministic.
261
+ [System.Collections.Generic.List[string]] $candidates = [System.Collections.Generic.List[string]]::new()
218
262
  foreach ($m in $matchList) {
219
263
  $normalized = ($m.Value -replace '\\', '/').TrimEnd('/')
220
- $unique[$normalized] = $true
264
+
265
+ # Truncate to exactly two segments past the docs/features/active/ prefix,
266
+ # that is, to the four segments docs, features, active, and the feature
267
+ # folder name. Truncation is depth-insensitive, so a folder path, a
268
+ # spec.md path, a research/ artifact path, and an evidence/ artifact path
269
+ # all reduce to the same value. A '.' component is a path no-op and is
270
+ # discarded first, so a degenerate token such as docs/features/active/.
271
+ # yields three segments and is rejected rather than resolved.
272
+ $segments = @($normalized -split '/' | Where-Object { $_ -ne '' -and $_ -ne '.' })
273
+ if ($segments.Count -lt 4) {
274
+ continue
275
+ }
276
+
277
+ $truncated = ($segments[0..3] -join '/')
278
+ if (-not $candidates.Contains($truncated)) {
279
+ $candidates.Add($truncated)
280
+ }
221
281
  }
222
282
 
223
- $candidates = @(@($unique.Keys) | Sort-Object -Property Length -Descending)
224
- $best = $candidates[0]
283
+ if ($candidates.Count -eq 0) {
284
+ return $null
285
+ }
225
286
 
226
- # If the longest match ends in .md, treat it as a file and use its parent.
227
- if ($best -match '\.md$') {
228
- $parent = $best -replace '/[^/]+\.md$', ''
229
- return $parent
287
+ # One distinct candidate is used directly, so the common case never consults
288
+ # the checkpoint.
289
+ if ($candidates.Count -eq 1) {
290
+ return $candidates[0]
291
+ }
292
+
293
+ # More than one distinct feature folder was cited. Prefer the folder the
294
+ # orchestrator itself records as in flight: the checkpoint is the
295
+ # authoritative disambiguator, and it reuses a seam this hook already owns.
296
+ $checkpointFolder = Get-PrdFeatureCheckpointFolder
297
+ if ($checkpointFolder) {
298
+ $checkpointNormalized = ($checkpointFolder -replace '\\', '/').TrimEnd('/')
299
+ if ($candidates.Contains($checkpointNormalized)) {
300
+ return $checkpointNormalized
301
+ }
230
302
  }
231
303
 
232
- return $best
304
+ # Tiebreak of last resort: the orchestrator supplies the active feature folder
305
+ # among its delegation inputs and names it before citing artifacts inside it,
306
+ # so a cross-reference to another feature appears later in the prompt.
307
+ return $candidates[0]
233
308
  }
234
309
 
235
310
  function Get-PrdFeatureMissingFile {
@@ -308,10 +383,32 @@ function Invoke-PrdFeatureBeforePlannerDecision {
308
383
 
309
384
  # Derive the prerequisite set from the persisted work-mode marker rather
310
385
  # than a fixed spec.md/user-story.md pair. A marker that cannot be read or
311
- # recognized must fail closed to the strictest set, not fail open.
386
+ # recognized must fail closed, not fail open: it denies on its own branch
387
+ # below, naming no prerequisite set and probing for no required file.
312
388
  $issueContent = Get-PrdFeatureIssueContent -FeatureFolder $folderNormalized
313
389
  $workMode = Resolve-PrdFeatureWorkMode -IssueContent $issueContent
314
- $modeDetermined = [bool]$workMode
390
+
391
+ # An indeterminate mode is its own decision path, and it deliberately does NOT
392
+ # run the required-file probe. When the mode is unknown no prerequisite set is
393
+ # knowable, so any set the gate named would be wrong for at least one mode:
394
+ # a set containing user-story.md is unsatisfiable for full-bug and minor-audit
395
+ # without violating the lifecycle contract, and the empty set fails open. The
396
+ # only remedy true in all three modes is repairing the marker, so that is what
397
+ # the reason states. This still DENIES, so the gate remains fail-closed.
398
+ if (-not $workMode) {
399
+ return [ordered]@{
400
+ hookSpecificOutput = [ordered]@{
401
+ hookEventName = 'PreToolUse'
402
+ permissionDecision = 'deny'
403
+ permissionDecisionReason = "PRD_FEATURE_BLOCKED: resolved feature folder '$folderNormalized', " +
404
+ "but its work mode could not be determined from '$folderNormalized/issue.md' " +
405
+ '(the ''- Work Mode:'' marker is absent, unreadable, or unrecognized). ' +
406
+ 'Confirm that is the intended feature folder, then add or correct the ' +
407
+ '''- Work Mode:'' marker in that file so the prerequisite set can be derived.'
408
+ }
409
+ }
410
+ }
411
+
315
412
  # Force array wrapping: PowerShell unravels a zero-element array return down
316
413
  # the pipeline to $null, which would otherwise fail the Mandatory
317
414
  # -RequiredFile parameter on Get-PrdFeatureMissingFile for minor-audit mode.
@@ -322,13 +419,13 @@ function Invoke-PrdFeatureBeforePlannerDecision {
322
419
  return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
323
420
  }
324
421
 
422
+ # Lead with the resolved folder, not with the remedy: a reader who sees a
423
+ # folder they did not intend diagnoses a path problem immediately instead of
424
+ # re-running a step that has already completed correctly.
325
425
  $list = ($missing -join ', ')
326
- if ($modeDetermined) {
327
- $reason = "PRD_FEATURE_BLOCKED: cannot delegate to atomic-planner before prd-feature outputs are present in '$folderNormalized'. Missing: $list (work mode: $workMode). Invoke the prd-feature subagent first."
328
- }
329
- else {
330
- $reason = "PRD_FEATURE_BLOCKED: cannot delegate to atomic-planner before prd-feature outputs are present in '$folderNormalized'. Missing: $list. Work mode could not be determined from '$folderNormalized/issue.md' (marker absent, unreadable, or unrecognized); failing closed to the strictest prerequisite set (spec.md, user-story.md). Invoke the prd-feature subagent first."
331
- }
426
+ $reason = "PRD_FEATURE_BLOCKED: resolved feature folder '$folderNormalized' is missing: " +
427
+ "$list (work mode: $workMode). Confirm that is the intended feature folder, then " +
428
+ 'invoke the prd-feature subagent to produce the missing output(s).'
332
429
 
333
430
  return [ordered]@{
334
431
  hookSpecificOutput = [ordered]@{
@@ -1,3 +1,10 @@
1
+ ---
2
+ paths:
3
+ - "scripts/benchmarks/**"
4
+ - "**/baseline*.json"
5
+ description: Runner-environment parity and provenance rules for committed benchmark baselines.
6
+ ---
7
+
1
8
  # Benchmark Baseline Provenance
2
9
 
3
10
  This rule governs performance baselines used by benchmark regression gates. It exists because a baseline captured on a developer workstation was compared against a `windows-latest` runner, producing deterministic latency regressions that the benchmark gate could not survive (issue #26, PR #30).
@@ -1,3 +1,9 @@
1
+ ---
2
+ paths:
3
+ - ".github/workflows/**"
4
+ description: Exit-code rules for GitHub Actions workflow steps that run pwsh.
5
+ ---
6
+
1
7
  # CI Workflow Authoring
2
8
 
3
9
  This rule governs GitHub Actions workflow steps that run PowerShell (`pwsh`). It exists because a `pwsh` step that intentionally invoked a failing nested command left `$LASTEXITCODE == 1` after its verification logic had already succeeded, leaking a failure to GitHub Actions even though the step's intent was satisfied (issue #26, PR #30).
@@ -1,3 +1,27 @@
1
+ ---
2
+ paths:
3
+ - "artifacts/orchestration/*orchestrator-state.json"
4
+ - "artifacts/orchestration/*planner-state.json"
5
+ - "scripts/dev_tools/*orchestrator_state*"
6
+ - "extensions/drm-copilot/src/lib/validate/orchestrator-state-*"
7
+ - "scripts/dev_tools/compute_complexity_floor.py"
8
+ - "scripts/dev_tools/resolve_delegation_model.py"
9
+ - ".claude/hooks/validate-orchestrator-output.ps1"
10
+ - ".claude/hooks/enforce-model-routing-receipt.ps1"
11
+ - "config/orchestration-routing.json"
12
+ - ".claude/agents/orchestrator.md"
13
+ - ".claude/agents/epic-orchestrator.md"
14
+ - ".claude/agents/parallel-orchestrator.md"
15
+ - ".claude/agents/epic-planner.md"
16
+ - ".claude/agents/parallel-planner.md"
17
+ - ".claude/skills/orchestrate/SKILL.md"
18
+ - ".claude/skills/epic-orchestrate/SKILL.md"
19
+ - ".claude/skills/parallel-orchestrate/SKILL.md"
20
+ - ".claude/skills/epic-plan/SKILL.md"
21
+ - ".claude/skills/parallel-plan/SKILL.md"
22
+ description: Checkpoint invariants for the orchestration state artifact and the surfaces that write or validate it.
23
+ ---
24
+
1
25
  # Orchestrator-State Remediation-Cycle and Human-Interaction Invariants
2
26
 
3
27
  This rule governs remediation-cycle records and the optional `human_interaction` block in the orchestrator-state checkpoint at `artifacts/orchestration/orchestrator-state.json`. It documents three invariants that must hold for each remediation cycle, plus three invariants for the `human_interaction` block, so that resume and review workflows do not depend on a structurally invalid checkpoint.
@@ -1,3 +1,24 @@
1
+ ---
2
+ paths:
3
+ - "artifacts/orchestration/parallel-*"
4
+ - "docs/features/parallel/**"
5
+ - "scripts/dev_tools/*parallel*"
6
+ - "extensions/drm-copilot/src/lib/validate/parallel-*"
7
+ - "scripts/dev_tools/*blast_radius*"
8
+ - "config/blast-radius.json"
9
+ - "**/config/blast-radius.json"
10
+ - "extensions/drm-copilot/src/lib/push-down/claude-blast-radius-derive-core.ts"
11
+ - ".claude/lib/blast-radius/**"
12
+ - ".claude/lib/bash/parallel-yaml-scan.sh"
13
+ - "scripts/dev_tools/validate_orchestration_artifacts.py"
14
+ - "extensions/drm-copilot/src/lib/validate/orchestration-artifacts.ts"
15
+ - ".claude/hooks/enforce-epic-merge-gate.ps1"
16
+ - ".claude/agents/parallel-orchestrator.md"
17
+ - ".claude/agents/parallel-planner.md"
18
+ - ".claude/skills/parallel-*/SKILL.md"
19
+ description: Artifact invariants and blast-radius contention doctrine for the parallel orchestration surface.
20
+ ---
21
+
1
22
  # Parallel Orchestration Artifact Invariants
2
23
 
3
24
  This rule governs the three artifacts of the `parallel` orchestration surface: the parallel-run manifest at `docs/features/parallel/<slug>/parallel.md`, the parallel-orchestrator checkpoint at `artifacts/orchestration/parallel-orchestrator-state.json`, and the parallel-planner checkpoint at `artifacts/orchestration/parallel-planner-state.json`. It records the invariants those artifacts must satisfy as numbered prose so that downstream features consume a fixed schema and add behavior only.
@@ -1,3 +1,15 @@
1
+ ---
2
+ paths:
3
+ - "scripts/dev_tools/plan_gate_*"
4
+ - "scripts/dev_tools/validate_orchestration_artifacts.py"
5
+ - "extensions/drm-copilot/src/lib/validate/plan-gate-*"
6
+ - "extensions/drm-copilot/src/lib/validate/orchestration-artifacts.ts"
7
+ - "docs/features/**/plan.*.md"
8
+ - "docs/features/**/remediation-plan.*.md"
9
+ - ".claude/skills/atomic-plan-contract/SKILL.md"
10
+ description: Acceptance-gate rules G1 through G6 applied to the shell commands an atomic plan states as acceptance conditions.
11
+ ---
12
+
1
13
  # Atomic-Plan Acceptance Gates (G1 through G6)
2
14
 
3
15
  This rule governs the acceptance-gate rules the plan validator applies to the shell commands an atomic plan states as acceptance conditions. It exists because a plan can state an acceptance condition that cannot fail: a coverage argument that collects no data, or a search for a literal that returns zero matches whatever the executor does. Such a condition reads as a verification step and gates nothing (issue #486).
@@ -24,10 +36,18 @@ Every finding string begins with the square-bracketed `P#-T#` identifier of the
24
36
  | **G4** | A `--cov` value supplied space-separated (`--cov <value>`) rather than with `=`. The ambiguous form can bind the following positional argument. Independent of resolvability, so it is reported for every value. | **Warning** |
25
37
  | **G5** | A checkable search literal that is absent from the tracked tree **and** not quoted in the plan document outside the command span it was read from. | **Warning** (see below) |
26
38
  | **G6** | A checkable search literal absent from every single line of a tracked file but present in that file's sliding-window join of adjacent lines. A line-oriented search returns zero matches. | **Warning** |
39
+ | **G7** | A write-mode command — one matching a write-mode register entry, that is a tool that rewrites tracked source and exits 0 after rewriting — whose attributed task text carries none of that entry's observation markers. The exit code alone cannot distinguish a clean run from a repairing one, so the acceptance condition holds either way. | **Warning** |
40
+ | **G8** | A `git diff` invocation carrying no non-flag ref operand and neither `--cached` nor `--staged`. It compares the worktree against the index, which is ambient state: the comparison passes vacuously once the change is committed. Exonerated when the attributed task text carries a second `git diff` or a `git status` span. | **Warning** |
41
+ | **G8b** | A `git diff` invocation carrying a non-flag ref operand together with `--name-only` or `--name-status`, whose attributed task text carries neither a `git add` span nor a `git status --porcelain` span. A name-listing diff enumerates tracked changes only, so a path the plan creates is invisible to it. | **Warning** |
42
+ | **G9** | A command carrying a `--cov` token, carrying no token beginning `--cov-report=term` and no token beginning `--cov-fail-under`, whose project `addopts` value also carries no `--cov-report=term`. No coverage table is printed, so a coverage number the acceptance condition demands can never be read. | **Warning** |
43
+
44
+ G1 through G9 are the complete shipped set. G1 through G6 were added by issue #486 and G7 through G9 by issue #519; the parenthetical in this file's title names the original set only.
27
45
 
28
46
  G1 through G4 form a cascade over each `--cov` value: the value is decided once, so a value G1 rejects is never additionally reported by G2 or G3. G4 is evaluated independently of the cascade because the ambiguous form is a defect whatever the value resolves to. G6 is evaluated before G5, because cross-line presence falsifies G5's tree-absence claim.
29
47
 
30
- G1 and G4 are context-free and run on every invocation. G2, G3, G5, and G6 require a repository seam; with no context supplied they do not run, and the Blocking list is byte-identical to the pre-change output for the same text.
48
+ G8b is a distinct rule from G8 and not a sub-case of it: G8 reports a diff with no ref operand, G8b reports one that has a ref operand but cannot observe an untracked path. A single invocation can satisfy only one of the two, because the presence of a ref operand decides between them.
49
+
50
+ G1 and G4 are context-free and run on every invocation. G2, G3, G5, and G6 require a repository seam; with no context supplied they do not run, and the Blocking list is byte-identical to the pre-change output for the same text. G7, G8, and G8b are likewise context-free and run on every invocation. G9 requires the repository seam, because it reads the project `addopts` value through it, so with no context supplied G9 does not run. All four of the rules added by issue #519 ship in the Warning channel, so none of them can alter the Blocking list at all.
31
51
 
32
52
  ### Attribution window
33
53
 
@@ -43,7 +63,7 @@ A repository seam that raises, or that reports a non-zero exit, causes G2, G3, G
43
63
 
44
64
  The shipped G5 severity was not chosen by argument. It was fixed by a pre-declared rule applied to a measurement over the committed plan corpus: Blocking if and only if the total G5 finding count is greater than zero **and** the recorded false-positive count is zero; otherwise Warning.
45
65
 
46
- The measurement is recorded in `docs/features/active/2026-08-17-reject-unfalsifiable-acceptance-gates-in-atomic-plans-486/evidence/qa-gates/g5-corpus-measurement.2026-08-20T12-02.md`. It scanned 166 plan files, evaluated 100 candidate literals, and produced a total G5 finding count of 0. A zero false-positive count over zero findings measures nothing, so the first conjunct failed and **G5 ships as a Warning**.
66
+ The measurement is recorded in `docs/features/completed/2026-08-17-reject-unfalsifiable-acceptance-gates-in-atomic-plans-486/evidence/qa-gates/g5-corpus-measurement.2026-08-20T12-02.md`. That feature has since been completed and its folder moved out of the active tree, so the citation names the completed tree; the path this file previously carried, under `docs/features/active/`, no longer resolves. It scanned 166 plan files, evaluated 100 candidate literals, and produced a total G5 finding count of 0. A zero false-positive count over zero findings measures nothing, so the first conjunct failed and **G5 ships as a Warning**.
47
67
 
48
68
  The zero count is a property of the corpus, not a defect in the measurement. Every committed plan is a tracked file, so a fixed-string search for a literal quoted inside a committed plan always finds at least that plan itself, and the tree-absence condition holds for no committed candidate. The measurement artifact records the four checks that established this (non-vacuous enumeration, a working repository seam, a self-hit on every sampled lookup, and predicate-order equivalence with the shipped rule).
49
69
 
@@ -63,6 +83,127 @@ Rejecting such a plan would block a correct plan on evidence about a state the p
63
83
 
64
84
  The window size is fixed at four adjacent non-blank lines. Blank lines are removed before windowing, and one window is emitted per start position, so the boundary is exact: two lines further apart than the window size never appear in the same join. The size is recorded here rather than left implicit so that a later feature can revise it against measured wrap-depth data instead of re-deriving it.
65
85
 
86
+ ### The shared measurement behind G7, G8, G8b, and G9
87
+
88
+ The four severities below were fixed by one corpus measurement recorded in `docs/features/active/2026-08-23-plan-acceptance-gates-miss-unobservable-and-ambient-state-gates-519/evidence/qa-gates/corpus-measurement.2026-08-24T00-00.md`. The pre-declared decision rule was written and committed **before** the driver existed and before any count was taken, so the ordering is verifiable from git history rather than only asserted in prose. Its form matches the G5 precedent: the shipped severity is Blocking if and only if the total finding count for that rule is greater than zero **and** the recorded false-positive count for that rule is zero; otherwise Warning.
89
+
90
+ The false-positive definition the rule consumes is likewise pre-declared: a finding is a false positive when the acceptance condition it reports is in fact falsifiable — when the plan states an observation sufficient to distinguish a passing run from a failing one, by a mechanism the rule's predicate does not recognise. A finding is a true positive when the acceptance condition it reports genuinely cannot fail, or can only be satisfied vacuously.
91
+
92
+ The measurement scanned 194 plan files under `docs/features`, covering the `active`, `completed`, and `archive` trees. Unlike the G5 measurement, every rule here found findings, so each false-positive count is a count over an examined population rather than over an empty one. The vacuity declaration therefore applies to no rule of this set.
93
+
94
+ | Rule | Corpus files | Candidates | Findings | True positives | False positives | Decision | Shipped |
95
+ | --- | --- | --- | --- | --- | --- | --- | --- |
96
+ | G7 | 194 | 519 | 466 | 444 | 22 | second conjunct fails | **Warning** |
97
+ | G8 | 194 | 237 | 82 | 75 | 7 | second conjunct fails | **Warning** |
98
+ | G8b | 194 | 47 | 19 | 19 | 0 | pre-declared unconditional clause | **Warning** |
99
+ | G9 | 194 | 273 | 8 | 4 | 4 | second conjunct fails | **Warning** |
100
+
101
+ ### G7 — ships as a Warning because the measurement recorded 22 false positives
102
+
103
+ G7's finding count of 466 satisfies the first conjunct, so the rule was not decided by a vacuous measurement. It fails the second conjunct: 22 of the 466 findings are false positives, in two classes named in full in the measurement artifact.
104
+
105
+ **Class 1, two findings — read-only argv shape.** The `prettier-write` register entry matches the argv shape `npm run format` and declares no exclusion for a check flag, so a check-mode invocation written as `npm run format -- --check` is matched as if it wrote. That command does not write and exits non-zero on drift, so its acceptance condition is falsifiable by the exit code alone.
106
+
107
+ **Class 2, twenty findings — the task observes the tree rather than the tool's stdout.** The attributed task text carries a `git status --porcelain` or `git status` span, so the plan distinguishes a clean run from a repairing one by comparing the tree before and after. G7's marker set recognises tool-output observation only, so it does not see this mechanism. Class 2 is the larger of the two and is the reason the rule cannot ship Blocking on this measurement: a plan that observes the tree has stated a real acceptance condition, and rejecting it would block a correct plan.
108
+
109
+ Both classes are addressable by a later feature — Class 1 by adding a check-flag exclusion to the `prettier-write` entry, Class 2 by admitting a tree-observation span as an alternative to a marker — but neither was changed here, because narrowing a predicate after reading its measurement would invalidate the measurement that decided its severity. A later feature that narrows either class must re-take the measurement the same way.
110
+
111
+ Counts, and every one of the 22 false positives named by plan path, task identifier, and offending span: `docs/features/active/2026-08-23-plan-acceptance-gates-miss-unobservable-and-ambient-state-gates-519/evidence/qa-gates/corpus-measurement.2026-08-24T00-00.md`.
112
+
113
+ ### G8 — ships as a Warning because the measurement recorded 7 false positives
114
+
115
+ G8's finding count of 82 satisfies the first conjunct. It fails the second: 7 findings are false positives, in two classes.
116
+
117
+ **Class 1, six findings — `--no-index`.** `git diff --no-index` compares two named paths on disk. It does not compare the worktree against the index, so G8's stated claim that the comparison passes vacuously once the change is committed is false for this form.
118
+
119
+ **Class 2, one finding — the unmerged-path filter.** `--diff-filter=U` selects conflicted paths during a merge or rebase. In that state the worktree-against-index comparison is the correct one and does not become vacuous on commit, because the conflict is precisely what is not yet committed.
120
+
121
+ Counts, and every one of the 7 false positives named by plan path, task identifier, and offending span: `docs/features/active/2026-08-23-plan-acceptance-gates-miss-unobservable-and-ambient-state-gates-519/evidence/qa-gates/corpus-measurement.2026-08-24T00-00.md`.
122
+
123
+ ### G8b — ships as a Warning unconditionally, by the pre-declared rule
124
+
125
+ G8b is exempt from the two-conjunct rule and **cannot reach the Blocking channel by any measured outcome**. That exemption was declared before the counts were taken, and it holds even though G8b is the only rule of the four whose measured false-positive count is zero.
126
+
127
+ The reason is that G8b carries the highest false-positive surface of the set, and a false-positive count taken over one corpus does not bound the false-positive surface of a predicate. Promoting a rule on the strength of a clean run over one corpus would convert an accident of that corpus into a gate. The 19 findings were nonetheless examined individually and all 19 were retained as true positives, across three sub-classes: a placeholder ref operand, which resolves to a real ref at run time and still cannot make the diff see an untracked path; an artifact-field label inside the span, which is cosmetic and leaves the underlying acceptance condition as the finding describes it; and a pathspec written without the `--` separator, where the predicate reads the pathspec as a ref operand. That last sub-class is a true positive for G8b and is simultaneously recorded in the measurement as a **G8 false negative**: the same span should also have been reported by G8, and was not.
128
+
129
+ Counts, the zero false-positive record, and the three sub-classes with their plan paths and offending spans: `docs/features/active/2026-08-23-plan-acceptance-gates-miss-unobservable-and-ambient-state-gates-519/evidence/qa-gates/corpus-measurement.2026-08-24T00-00.md`.
130
+
131
+ ### G9 — ships as a Warning because the measurement recorded 4 false positives
132
+
133
+ G9's finding count of 8 satisfies the first conjunct. It fails the second: 4 of the 8 findings are false positives, and all four share one cause — the offending span is not a command the plan states as an acceptance condition. Two are prose quotations of a flag or flag pair used to describe something the task declares out of scope; one is a test-data literal inside a sentence asserting a helper's return values; and one is a truncated restatement of a command whose full form on the task line does supply a terminal reporter. In every one of the four, the acceptance condition the task actually states is decided by a pass count, an exit code, or an artifact field, not by a coverage number.
134
+
135
+ The remaining 4 findings are true positives, and each is the exact defect the rule was written for: the acceptance condition demands a numeric coverage percentage, the command supplies no terminal reporter, the project `addopts` supplies only an LCOV reporter, and the number the acceptance demands is therefore never printed.
136
+
137
+ Counts, and every one of the 4 false positives named by plan path, task identifier, offending span, and the reason its acceptance condition is falsifiable: `docs/features/active/2026-08-23-plan-acceptance-gates-miss-unobservable-and-ambient-state-gates-519/evidence/qa-gates/corpus-measurement.2026-08-24T00-00.md`.
138
+
139
+ ## The Write-Mode Register
140
+
141
+ G7 reads a fixed register of six entries. Each entry is an argv predicate plus a set of observation markers matched case-sensitively as substrings of the owning task's attributed text. The register is data, not code, so the same six entries are transcribed into the TypeScript twin without porting behaviour.
142
+
143
+ ### Membership criterion
144
+
145
+ **A tool belongs in the write-mode register when it rewrites tracked source and still exits 0 after rewriting.** That conjunction is the whole criterion. The exit code of such a tool is the same on a clean run and on a repairing run, so a plan that states the invocation as an acceptance condition and records nothing but the exit code has asserted nothing the tool can fail. The register's markers name the literals the tool's success-case output prints, which is the observation that separates the two runs.
146
+
147
+ The six entries are `black-write`, `ruff-fix`, `prettier-write`, `poshqc-format`, `run_poshqc_analyze_autofix`, and `poshqc-suite`. The fifth is named for the MCP tool `run_poshqc_analyze_autofix`, whose argv predicate is an argv word ending with that name.
148
+
149
+ ### Executable-position constraint
150
+
151
+ A word satisfies an entry's argv shape only when its index lies within the leading four-word scan window and the word immediately preceding it does not begin with a hyphen. A tool name supplied as the operand of a search flag is therefore never read as an invocation, which mirrors the extractor's existing rule that a grep-family name appearing as an operand is not the executable. Without this constraint a task that searches a policy file for a register member's name would report a finding against its own search command — including a task that searches this file.
152
+
153
+ ### Exclusions, with the reason for each
154
+
155
+ Two tools that do write are deliberately **not** register members.
156
+
157
+ - **`git add` is excluded.** A plan stages in order to make a later diff complete, so its acceptance concerns the diff, not the staging. The staging itself carries no acceptance condition to make unfalsifiable, and G8b already reads a `git add` span as an exonerating companion rather than as an offence.
158
+ - **`npm ci` is excluded.** Its only write target is git-ignored, so it rewrites no tracked source and fails the membership criterion's first half. A plan can still state a real observation of it — an installed-package count, or the existence of a resolved binary — but it is not a case G7 needs to report.
159
+
160
+ ### Two writers that are not register members and are not exclusions
161
+
162
+ Two further tools write files without being register members, and they are recorded here so a later maintainer does not read their absence as an oversight.
163
+
164
+ - **The Python test runner** writes coverage output under the artifacts tree, for example the LCOV file the project `addopts` value names. It rewrites no tracked source, and its exit code already distinguishes a passing run from a failing one, so it fails the membership criterion on both halves.
165
+ - **The PoshQC test tool** likewise writes result and coverage files under the artifacts tree without rewriting tracked source, and likewise reports pass and fail through its own result. The three PoshQC entries that *are* members — format, analyze-autofix, and the suite — are members because they rewrite tracked PowerShell source in place.
166
+
167
+ A tool that writes only under the artifacts tree is therefore outside the register by construction. The register is about tracked source, not about writing in general.
168
+
169
+ ### Known false-negative class — the single-token tool-name span
170
+
171
+ The command extractor drops any command span shorter than two shell words. That two-word minimum-argv floor predates these rules, is pinned by an existing test, and was deliberately left unchanged: relaxing it would newly admit a single-word coverage-argument span into the G1 and G4 scan, which would change existing output, and changing existing output is exactly what this addition forbids itself.
172
+
173
+ The consequence is a false-negative class. **A tool invoked as a bare single-token name is never extracted, so G7 can never report it, however unobservable its acceptance condition is.** This is the form plans commonly use for MCP tools, including the PoshQC formatter, the PoshQC analyzer autofix tool, and the PoshQC composite suite tool — three of the six register entries. Those three entries are reachable only by a span whose first word is the tool name and whose second word is an operand, for example the tool name followed by a path.
174
+
175
+ This is a stated limitation, not a promise deferred, and it is recorded here for the same reason the placeholder guard's false-negative class is recorded above: it is the cost side of a trade, and a later feature must weigh it rather than rediscover it. The limitation is pinned by a test asserting that a single-token tool-name span produces zero findings from every one of the four rules, so the boundary cannot move silently.
176
+
177
+ A later feature may revisit the floor, but only together with a plan for the coverage-rule output change that relaxing it would cause. The two cannot be separated: the floor is one value read by one extractor feeding every rule, so any relaxation reaches G1 and G4 as well as G7.
178
+
179
+ ## Deliberately Uncovered Sub-Classes
180
+
181
+ Issue #519 measured five classes of unfalsifiable acceptance condition on a single plan. G7 and G9 cover two decidable slices of the first, G8 and G8b cover the third. The remainder is recorded here so a reviewer knows what these rules do **not** reach and that human attention is still required for it.
182
+
183
+ ### The general unobservable-success-output class, beyond what G7 and G9 reach
184
+
185
+ The class is: an acceptance condition that asserts over output the command does not emit on a successful run. G9 covers the slice where a coverage command prints no table at all, and G7 covers the slice where a write-mode tool's exit code is identical on a clean and a repairing run. Neither reaches the general case.
186
+
187
+ Three measured instances outside their reach illustrate why. A gate requiring a formatter's `reformatted` line to be recorded verbatim is unsatisfiable on a clean run, because that line is printed only when a file was rewritten. A gate demanding a zero-diagnostic count from an analyzer that returns only an ok flag and a one-sentence summary names a value that has no source. A gate demanding separate line and branch percentages from a coverage run that prints one combined `Cover` column reads two numbers that are never printed.
188
+
189
+ **Deciding these requires knowing each tool's success-case output, which is not derivable from the plan text, the rules, or the tool documentation.** That is what makes the class invisible to review: four careful review cycles on the measured plan missed it entirely, and it surfaced only when the reviewer ran the tools and observed what they actually printed. A rule would need a per-tool output model, which is a different mechanism from a register of argv predicates. The class is therefore addressed by an authoring requirement in `.claude/skills/atomic-plan-contract/SKILL.md` — that a plan author observe a command's success-case output before asserting over that output — and not by a rule.
190
+
191
+ ### The task-ordering class
192
+
193
+ The class is: an acceptance condition that is unsatisfiable because of where its task sits in the plan rather than because of what the command does. A gate that runs a test path containing deliberately-failing cases added by an earlier task, before the later task that makes them pass, cannot exit 0. A baseline captured *after* a write-mode formatter has already repaired pre-existing drift becomes either a blanket waiver or makes the later gate unsatisfiable.
194
+
195
+ **No rule covers this class.** Detecting it requires intra-plan dependency reasoning across phases: the validator would have to model which task changes which file, and which later assertion depends on that change. That is a different analysis from the per-command predicates these rules apply. It is recorded here so a reviewer knows the class still needs human attention.
196
+
197
+ ### The executor-choice heuristic — rejected, and closed rather than deferred
198
+
199
+ The proposal was a rule that flags selection vocabulary in acceptance text — "any", "a suitable", "the known", "choose" — on the reasoning that an executor free to pick the evidence it is judged against cannot fail. The underlying concern is real and was measured: two conditions on the examined plan asked the executor to identify "the known-genuine pair" and to choose a survivor list.
200
+
201
+ **The rule is rejected.** The vocabulary it would scan for is ordinary plan prose used in roles that carry no selection semantics, and the research recorded a corpus instance in which the word "any" appears inside a *prohibition* rather than a selection — a case the scan would report and the author would be right to ignore. A keyword scan over prose is not statically decidable for the property the rule would claim to detect. This file's own guidance is to weigh a new rule on its authoring-time false-positive rate rather than on how many committed plans it would have flagged, and on that test the proposal fails.
202
+
203
+ The concern is addressed as authoring guidance in `.claude/skills/atomic-plan-contract/SKILL.md` instead, where a false positive costs an author a moment's judgment rather than a blocked plan.
204
+
205
+ **This is closed, not deferred.** Reviving it requires new evidence about its false-positive rate on plan prose, not a restatement of the original proposal.
206
+
66
207
  ## Checkable-Literal Definition and the Placeholder Guard
67
208
 
68
209
  G5 and G6 apply only to a *checkable* literal. The specification defines a checkable literal by two conditions: the command carries the fixed-string flag `-F`, or the pattern contains none of the regular-expression metacharacters `. * [ ] ^ $ \ ( ) { } | + ?`. That condition is conservative in POSIX BRE, POSIX ERE, PCRE, and the Rust regex dialect simultaneously, so no dialect-selection logic is required.
@@ -159,6 +159,8 @@ Before a plan can be treated as approved:
159
159
 
160
160
  The same validator call also applies the acceptance-gate rules G1 through G6 defined in `.claude/rules/plan-acceptance-gates.md`. Those rules report acceptance conditions that cannot fail — a coverage argument that collects no data, or a search for a literal that returns zero matches whatever the executor does. They run automatically on the existing `plan` route with no additional flag. Blocking findings appear in the validator's error output and fail the gate; Warnings are surfaced without failing it, prefixed with `PLAN GATE WARNING: ` on the CLI and carried on the optional `warnings` field of the MCP result. Read that rule file before authoring acceptance conditions.
161
161
 
162
+ The same call additionally applies the rules G7, G8, G8b, and G9, which report a write-mode command observed only by its exit code, an unanchored `git diff`, a name-listing diff with no companion span, and a coverage command that prints no table. All four ship in the Warning channel, so they surface without failing the gate. The complete shipped set is therefore G1 through G9.
163
+
162
164
  ## Wrap-Tolerant Assertion Authoring (Mandatory)
163
165
 
164
166
  An acceptance condition must be able to fail. A condition whose command returns the same result whatever the executor does verifies nothing, however precise it reads. Author every acceptance condition in a wrap-tolerant form: one that survives line wrapping and shell quoting in the file it asserts against.
@@ -171,6 +173,13 @@ Rules:
171
173
  - **Dotted coverage-argument form.** Coverage assertions must name an importable dotted module, for example `--cov=scripts.dev_tools.plan_gate_discrimination`. The filesystem-path spellings `--cov=scripts/dev_tools/module.py` and `--cov=scripts/dev_tools/module` collect no data, so a coverage threshold asserted against them cannot fail. Rules G1 through G3 report those spellings.
172
174
  - **Use the `=` form, not the space-separated form.** `--cov <value>` can bind the following positional argument. Rule G4 reports it.
173
175
  - **Quote what the task will create.** When an asserted literal does not yet exist in the tree, quote the exact literal in the plan prose outside the command span. The gate reads that quotation as the executor's instruction and exonerates the assertion; a paraphrase does not.
176
+ - **Record an observation beyond the exit code for a write-mode command.** A formatter or a fixing linter rewrites tracked source and still exits 0 after rewriting, so its exit code is identical on a clean run and on a repairing one. State, in the task text, the literal its success-case output prints — for example the summary line a formatter prints when it changed nothing — or state a before-and-after tree observation. Rule G7 in `.claude/rules/plan-acceptance-gates.md` reports a write-mode command whose task text carries neither.
177
+ - **Anchor every `git diff` to a ref.** A `git diff` with no ref operand and no `--cached` compares the worktree against the index, which is ambient state: it passes vacuously once the change is committed, so the assertion cannot fail for the executor who commits before running it. Supply an explicit ref operand, usually the base branch. Rule G8 reports the unanchored form.
178
+ - **Pair a name-listing diff with a staging or porcelain-status companion.** An anchored `git diff --name-only` or `--name-status` enumerates tracked changes only, so it can never report a file the task creates, and an assertion that it lists newly created files always sees an empty list. Add a `git add` span or a `git status --porcelain` span in the same task. Rule G8b reports a name-listing diff with neither companion. The two mechanisms are complementary and each alone is wrong in one state: the anchored diff is blind to untracked files, and porcelain status goes empty once the change is committed.
179
+ - **Pass a terminal reporter to every coverage command.** The project `addopts` value supplies an LCOV reporter only, so a coverage command that does not pass `--cov-report=term-missing` prints no coverage table at all, and a numeric percentage the acceptance condition demands can never be read from it. Rule G9 reports a coverage command that supplies no terminal reporter and no `--cov-fail-under` threshold.
180
+ - **Observe a command's success-case output before asserting over that output (mandatory).** Run the command, or read a recorded run of it, and confirm the value you intend to assert is actually printed on a *successful* run. Do not infer it from the tool's documentation, from the plan, or from what the tool prints on failure. This requirement exists because the class of defect it prevents is not detectable by reading: an assertion on a line the tool prints only when it changed something is unsatisfiable on a clean run; a demand for a zero-diagnostic count from a tool that returns only an ok flag names a value with no source; a demand for separate line and branch percentages from a coverage run that prints one combined column reads two numbers that are never printed. G7 and G9 cover two decidable slices of this class. The remainder is covered by this requirement and by nothing else, so a reviewer cannot rely on the gate to catch it.
181
+ - **Fix the evidence in the plan; never leave the executor to select it.** An executor free to choose the evidence it is judged against cannot fail. Do not write an acceptance condition that asks the executor to identify "the known-genuine pair", to choose a survivor list, or to pick any suitable instance. Name the instances, or state the mechanical derivation that produces them, so a third party re-running it obtains the same set. This is authoring guidance and not a rule: a validator rule that scanned acceptance text for selection vocabulary was proposed and rejected, because that vocabulary is ordinary plan prose used in roles carrying no selection semantics and a keyword scan over prose is not statically decidable for the property it would claim to detect. The judgment is the author's, and it is not automated. `.claude/rules/plan-acceptance-gates.md` records the rejection and the reason for it.
182
+ - **Check that the task-ordering does not make the condition unsatisfiable.** No rule covers this. A gate that runs a test path containing deliberately-failing cases added by an earlier task, before the later task that makes them pass, cannot exit 0. A baseline captured after a write-mode formatter has already repaired pre-existing drift becomes either a blanket waiver or makes a later gate unsatisfiable. Read each acceptance condition against the state the plan will actually be in when its task runs.
174
183
 
175
184
  ## Plan-Path Continuity Contract (Mandatory)
176
185
 
@@ -19,14 +19,6 @@ checkpoint handling, wave computation, integration-branch lifecycle, wave barrie
19
19
  merge-conflict handling, worktree cleanup, and documentation-maintenance procedures so the
20
20
  procedure is not re-derived ad hoc on each epic run.
21
21
 
22
- ## Prerequisites
23
-
24
- Before proceeding, `epic-orchestrator` must:
25
-
26
- 1. Read `CLAUDE.md` for repository tone policy and architectural context.
27
- 2. Read applicable `.claude/rules/` files for the languages in scope.
28
- 3. Read the policy files listed in the compliance reading order section of `CLAUDE.md`.
29
-
30
22
  ## Epic Dependency Manifest
31
23
 
32
24
  The epic manifest is the YAML frontmatter of the single epic home
@@ -123,7 +115,7 @@ function of the DAG.
123
115
  When `epic-orchestrator` delegates a child feature to `Agent(orchestrator)`, the prompt includes
124
116
  the literal epic-mode kickoff line:
125
117
 
126
- > `Epic mode: true. epic_feature_folder: <epic-slug>. integration_branch: epic/<epic-slug>-integration. epic_checkpoint_path: artifacts/orchestration/epic-orchestrator-state.json. PR base branch MUST be <integration_branch>, not main; pass --base <integration_branch> to gh pr create.`
118
+ > `Epic mode: true. epic_feature_folder: <epic-slug>. integration_branch: epic/<epic-slug>-integration. epic_checkpoint_path: artifacts/orchestration/epic-orchestrator-state.json. PR base branch MUST be <integration_branch>, not main; pass --base <integration_branch> to gh pr create. Your final report MUST be exactly the bounded return shape (issue_num, feature_folder, merge_status, pr_number, merge_commit_sha, blocked_reason, branch_name, worktree_path) and nothing else; any additional narrative is discarded because the parent re-derives authoritative state regardless.`
127
119
 
128
120
  The child's own `orchestrator`, on reading this line, records `epic_mode: true` and
129
121
  `epic_context: { epic_feature_folder, integration_branch, epic_checkpoint_path }` at its first
@@ -131,6 +123,31 @@ checkpoint write, and on CI-green (S9 step 6) merges its own PR into the integra
131
123
  recording `epic_merge: { merge_commit_sha, target_branch, merged_at }`. Standalone (non-epic)
132
124
  orchestration is unchanged: `epic_mode` absent or `false` makes S9 step 6 a no-op.
133
125
 
126
+ ## Bounded Child Return Contract
127
+
128
+ A child `orchestrator`'s final report is consumed as a fixed eight-field shape and nothing else:
129
+
130
+ - `issue_num` — the child's GitHub issue number.
131
+ - `feature_folder` — the child's feature-folder path.
132
+ - `merge_status` — the child's terminal merge-status enum value.
133
+ - `pr_number` — the child's pull-request number, or null when none was opened.
134
+ - `merge_commit_sha` — the merge commit, or null when the child did not merge.
135
+ - `blocked_reason` — a short reason string when the child is blocked, otherwise null.
136
+ - `branch_name` — the child's feature branch.
137
+ - `worktree_path` — the child's isolated worktree path.
138
+
139
+ Content beyond these eight fields is **discarded**. A child that returns a longer narrative is not
140
+ in error; the excess is simply not read into the parent's context, which is what keeps the parent's
141
+ footprint flat as the child count grows.
142
+
143
+ Discarding is safe because the parent re-derives authoritative state regardless, from
144
+ `git worktree list --porcelain`, `git branch`, and
145
+ `gh pr view --json state,mergedAt,headRefOid`. `branch_name` and `worktree_path` are carried in the
146
+ shape only to spare the parent a re-parse of porcelain output per child before
147
+ `git worktree remove`; they are not authoritative and are re-derived like every other field. The
148
+ governing argument is the cache doctrine already recorded in
149
+ `.claude/rules/parallel-orchestration.md`, which is cited here rather than restated.
150
+
134
151
  ## Model Selection
135
152
 
136
153
  When `epic-orchestrator` delegates a child feature to `Agent(orchestrator)`, the prompt appends the
@@ -265,7 +282,7 @@ checkpoint JSON remains the durable, machine-authoritative source.
265
282
  `docs/features/epics/<epic-slug>/epic.md`), `epic_status_doc_path`, `integration_branch`,
266
283
  `completed_steps`, `next_step`, `last_updated`, `current_wave`, `waves[]`, `features[]`,
267
284
  `epic_merge_pr`, and the three receipt arrays (`delegation_receipts[]`, `skill_receipts[]`,
268
- `mcp_call_receipts[]`) — the full schema is defined in `spec.md` §6 of this feature. The
285
+ `mcp_call_receipts[]`). The
269
286
  `merge_status` enum is: `not_started`, `worktree_created`, `pr_open`, `ci_green`,
270
287
  `merge_conflict`, `blocked_conflict_loop_limit`, `merged`, `worktree_removed`. The optional
271
288
  `intent` object (projection of the `epic.md` intent block) is validated presence-gated.
@@ -96,6 +96,16 @@ A delegation prompt carrying the literal marker `Preparation mode: true` (issued
96
96
  - **Terminal checkpoint.** Stop with `completed_steps` containing `S3_promotion` and `S4_atomic_planning`, `next_step: "S5_atomic_execution"`, out-of-scope step statuses set to `not-applicable`, and `blocked_reason: "none"`. Do NOT assert completion (`next_step: "complete"`, `S12_complete`, or a `completed` step8/9/10 status): the run has no PR or CI evidence, and the route's `requires_ci_gate: false` exempts it from `ci_gate` at the completion validator instead.
97
97
  - **Commit.** Commit the prepared feature folder and plan to the current branch (the worktree branch created off the epic integration branch) before stopping, and report the `plan-path` and preflight status in the final output.
98
98
 
99
+ ## Epic Mode Bounded Return
100
+
101
+ Under `Epic mode: true`, your final report must be exactly this eight-field shape and nothing else:
102
+ `issue_num`, `feature_folder`, `merge_status`, `pr_number`, `merge_commit_sha`, `blocked_reason`,
103
+ `branch_name`, `worktree_path`.
104
+
105
+ Content beyond those fields is discarded unread, so spend no effort on it. The parent
106
+ `epic-orchestrator` re-derives authoritative state from git and `gh` regardless, per
107
+ `## Bounded Child Return Contract` in `.claude/skills/epic-orchestrate/SKILL.md`.
108
+
99
109
  ## Model Selection
100
110
 
101
111
  Model selection is a second axis, strictly separate from `route`. `route` (`small | large | remediation | preparation | epic`) is deterministic — file-count driven for `small`/`large`, marker-driven for `preparation` (the `Preparation mode: true` kickoff line) and `epic`; it governs `required_agents`, `required_skills`, and `required_mcp_tools` only. `route` is NOT an input to model selection anywhere. The sole feature-level input to the delegation model tier is a judgment-based `complexity_band` (`C1 | C2 | C3 | C4`). The authoritative values live in the `model_policy` block of `config/orchestration-routing.json`.