@danmoisan/drm-copilot-mcp 1.1.23 → 1.1.25

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 (199) hide show
  1. package/out/mcp-server.js +13347 -7926
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/discovery-capability-sentinel.md +34 -0
  4. package/resources/claude-customizations/.claude/agents/legacy-parity-analyst.md +101 -43
  5. package/resources/claude-customizations/.claude/agents/migration-coverage-reviewer.md +107 -43
  6. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +14 -0
  7. package/resources/claude-customizations/.claude/agents/parallel-planner.md +21 -1
  8. package/resources/claude-customizations/.claude/agents/pr-author.md +17 -1
  9. package/resources/claude-customizations/.claude/agents/requirements-reconciler.md +117 -42
  10. package/resources/claude-customizations/.claude/agents/runtime-characterization-analyst.md +103 -43
  11. package/resources/claude-customizations/.claude/hooks/discovery-capability-probe.ps1 +102 -0
  12. package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +69 -194
  13. package/resources/claude-customizations/.claude/hooks/enforce-discovery-planning-gate.ps1 +87 -0
  14. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +7 -1
  15. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +6 -3
  16. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +168 -48
  17. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +1 -1
  18. package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +59 -20
  19. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1 +44 -3
  20. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +60 -4
  21. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +4 -2
  22. package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +59 -20
  23. package/resources/claude-customizations/.claude/hooks/record-discovery-agent-start.ps1 +132 -0
  24. package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +56 -212
  25. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-rejection.ps1 +172 -0
  26. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +9 -1
  27. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +9 -3
  28. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusAggregate.psm1 +154 -0
  29. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusCombination.psm1 +228 -0
  30. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +1 -0
  31. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +4 -2
  32. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +63 -16
  33. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +56 -1
  34. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusWriteIntent.psm1 +4 -1
  35. package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +5 -1
  36. package/resources/claude-customizations/.claude/lib/codex-routing/CodexDiscoveryRouting.psm1 +161 -0
  37. package/resources/claude-customizations/.claude/lib/codex-routing/CodexTopology.psm1 +25 -2
  38. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthority.psd1 +90 -0
  39. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthority.psm1 +493 -0
  40. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthorization.psm1 +479 -0
  41. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryCapability.psm1 +468 -0
  42. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryEncoding.psm1 +156 -0
  43. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryManifest.psm1 +384 -0
  44. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryObservation.psm1 +217 -0
  45. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryPayload.psm1 +276 -0
  46. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryPlanning.psm1 +336 -0
  47. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryRootGrant.psm1 +164 -0
  48. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStart.psm1 +440 -0
  49. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStartRecord.psm1 +296 -0
  50. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStop.psm1 +336 -0
  51. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryToolRouting.psm1 +228 -0
  52. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryValidation.psm1 +188 -341
  53. package/resources/claude-customizations/.claude/lib/worktree-resolution/HookTargetWorktree.psm1 +391 -0
  54. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +1 -0
  55. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +150 -18
  56. package/resources/claude-customizations/.claude/settings.json +32 -3
  57. package/resources/claude-customizations/.claude/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
  58. package/resources/claude-customizations/.claude/skills/characterize-runtime-behavior/SKILL.md +134 -0
  59. package/resources/claude-customizations/.claude/skills/discovery-behavior-reconciliation/SKILL.md +28 -47
  60. package/resources/claude-customizations/.claude/skills/discovery-coverage-ledger/SKILL.md +32 -48
  61. package/resources/claude-customizations/.claude/skills/discovery-parity-matrix/SKILL.md +28 -45
  62. package/resources/claude-customizations/.claude/skills/discovery-repo-inventory/SKILL.md +28 -62
  63. package/resources/claude-customizations/.claude/skills/discovery-runtime-characterization/SKILL.md +28 -45
  64. package/resources/claude-customizations/.claude/skills/discovery-validate-artifacts/SKILL.md +28 -61
  65. package/resources/claude-customizations/.claude/skills/discovery-workflow/SKILL.md +55 -139
  66. package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +24 -9
  67. package/resources/claude-customizations/.claude/skills/extract-feature-contract/SKILL.md +133 -0
  68. package/resources/claude-customizations/.claude/skills/generate-acceptance-scenarios/SKILL.md +132 -0
  69. package/resources/claude-customizations/.claude/skills/initialize-discovery-workspace/SKILL.md +131 -0
  70. package/resources/claude-customizations/.claude/skills/inventory-legacy-system/SKILL.md +131 -0
  71. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +5 -4
  72. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +21 -1
  73. package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +11 -1
  74. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +34 -0
  75. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +69 -6
  76. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +15 -0
  77. package/resources/claude-customizations/.claude/skills/parallel-run/SKILL.md +6 -0
  78. package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +21 -4
  79. package/resources/claude-customizations/.claude/skills/reconcile-parity-matrix/SKILL.md +131 -0
  80. package/resources/claude-customizations/.claude/skills/review-discovery-coverage/SKILL.md +129 -0
  81. package/resources/claude-customizations/.claude/skills/validate-discovery-artifacts/SKILL.md +127 -0
  82. package/resources/claude-customizations/config/blast-radius.json +6 -5
  83. package/resources/claude-customizations/config/orchestration-routing.json +61 -1
  84. package/resources/claude-customizations/pack-manifests/core.json +33 -1
  85. package/resources/codex-and-agents-customizations/.agents/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
  86. package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +7 -0
  87. package/resources/codex-and-agents-customizations/.agents/skills/characterize-runtime-behavior/SKILL.md +134 -0
  88. package/resources/codex-and-agents-customizations/.agents/skills/discovery-behavior-reconciliation/SKILL.md +46 -0
  89. package/resources/codex-and-agents-customizations/.agents/skills/discovery-coverage-ledger/SKILL.md +50 -0
  90. package/resources/codex-and-agents-customizations/.agents/skills/discovery-parity-matrix/SKILL.md +46 -0
  91. package/resources/codex-and-agents-customizations/.agents/skills/discovery-repo-inventory/SKILL.md +46 -0
  92. package/resources/codex-and-agents-customizations/.agents/skills/discovery-runtime-characterization/SKILL.md +46 -0
  93. package/resources/codex-and-agents-customizations/.agents/skills/discovery-validate-artifacts/SKILL.md +46 -0
  94. package/resources/codex-and-agents-customizations/.agents/skills/discovery-workflow/SKILL.md +62 -0
  95. package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +24 -9
  96. package/resources/codex-and-agents-customizations/.agents/skills/extract-feature-contract/SKILL.md +133 -0
  97. package/resources/codex-and-agents-customizations/.agents/skills/generate-acceptance-scenarios/SKILL.md +132 -0
  98. package/resources/codex-and-agents-customizations/.agents/skills/initialize-discovery-workspace/SKILL.md +131 -0
  99. package/resources/codex-and-agents-customizations/.agents/skills/inventory-legacy-system/SKILL.md +131 -0
  100. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +8 -0
  101. package/resources/codex-and-agents-customizations/.agents/skills/reconcile-parity-matrix/SKILL.md +131 -0
  102. package/resources/codex-and-agents-customizations/.agents/skills/review-discovery-coverage/SKILL.md +129 -0
  103. package/resources/codex-and-agents-customizations/.agents/skills/validate-discovery-artifacts/SKILL.md +127 -0
  104. package/resources/codex-and-agents-customizations/.codex/agents/discovery-capability-sentinel.toml +51 -0
  105. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c1.toml +148 -0
  106. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c2.toml +148 -0
  107. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c3-elevated.toml +148 -0
  108. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c3.toml +148 -0
  109. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c4.toml +148 -0
  110. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst.toml +148 -0
  111. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c1.toml +154 -0
  112. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c2.toml +154 -0
  113. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c3-elevated.toml +154 -0
  114. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c3.toml +154 -0
  115. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c4.toml +154 -0
  116. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer.toml +154 -0
  117. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c1.toml +164 -0
  118. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c2.toml +164 -0
  119. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c3-elevated.toml +164 -0
  120. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c3.toml +164 -0
  121. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c4.toml +164 -0
  122. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler.toml +164 -0
  123. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c1.toml +150 -0
  124. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c2.toml +150 -0
  125. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c3-elevated.toml +150 -0
  126. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c3.toml +150 -0
  127. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c4.toml +150 -0
  128. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst.toml +150 -0
  129. package/resources/codex-and-agents-customizations/.codex/config.toml +33 -3
  130. package/resources/codex-and-agents-customizations/.codex/hooks/codex-agent-profile-attestation.ps1 +22 -0
  131. package/resources/codex-and-agents-customizations/.codex/hooks/discovery-capability-probe.ps1 +97 -0
  132. package/resources/codex-and-agents-customizations/.codex/hooks/discovery-start-attestation.ps1 +171 -0
  133. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +4 -1
  134. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-discovery-artifact-gate.ps1 +187 -0
  135. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-discovery-planning-gate.ps1 +149 -0
  136. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +239 -2
  137. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +6 -15
  138. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +54 -15
  139. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +54 -15
  140. package/resources/codex-and-agents-customizations/.codex/hooks/hook-target-worktree.ps1 +369 -0
  141. package/resources/codex-and-agents-customizations/.codex/hooks/record-subagent-routing-attestation.ps1 +8 -0
  142. package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +22 -1
  143. package/resources/codex-and-agents-customizations/.codex/hooks/validate-discovery-agent-output.ps1 +178 -0
  144. package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexTopology.ps1 +11 -1
  145. package/resources/codex-and-agents-customizations/pack-manifests/core.json +63 -1
  146. package/resources/config/orchestration-routing.json +61 -1
  147. package/resources/customizations/.github/agents/discovery-capability-sentinel.agent.md +34 -0
  148. package/resources/customizations/.github/agents/legacy-parity-analyst.agent.md +122 -0
  149. package/resources/customizations/.github/agents/migration-coverage-reviewer.agent.md +128 -0
  150. package/resources/customizations/.github/agents/requirements-reconciler.agent.md +138 -0
  151. package/resources/customizations/.github/agents/runtime-characterization-analyst.agent.md +124 -0
  152. package/resources/customizations/.github/hooks/discovery-agent-start.ps1 +139 -0
  153. package/resources/customizations/.github/hooks/discovery-agent-stop.ps1 +123 -0
  154. package/resources/customizations/.github/hooks/discovery-capability-probe.ps1 +98 -0
  155. package/resources/customizations/.github/hooks/discovery-planning.ps1 +91 -0
  156. package/resources/customizations/.github/hooks/discovery-pre-tool.ps1 +123 -0
  157. package/resources/customizations/.github/hooks/discovery.json +35 -0
  158. package/resources/customizations/.github/instructions/mermaid.instructions.md +85 -0
  159. package/resources/customizations/.github/prompts/execute-hard-lock.prompt.md +105 -0
  160. package/resources/customizations/.github/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
  161. package/resources/customizations/.github/skills/characterize-runtime-behavior/SKILL.md +134 -0
  162. package/resources/customizations/.github/skills/discovery-behavior-reconciliation/SKILL.md +46 -0
  163. package/resources/customizations/.github/skills/discovery-coverage-ledger/SKILL.md +50 -0
  164. package/resources/customizations/.github/skills/discovery-parity-matrix/SKILL.md +46 -0
  165. package/resources/customizations/.github/skills/discovery-repo-inventory/SKILL.md +46 -0
  166. package/resources/customizations/.github/skills/discovery-runtime-characterization/SKILL.md +46 -0
  167. package/resources/customizations/.github/skills/discovery-validate-artifacts/SKILL.md +46 -0
  168. package/resources/customizations/.github/skills/discovery-workflow/SKILL.md +62 -0
  169. package/resources/customizations/.github/skills/execution-environment/SKILL.md +24 -9
  170. package/resources/customizations/.github/skills/extract-feature-contract/SKILL.md +133 -0
  171. package/resources/customizations/.github/skills/generate-acceptance-scenarios/SKILL.md +132 -0
  172. package/resources/customizations/.github/skills/initialize-discovery-workspace/SKILL.md +131 -0
  173. package/resources/customizations/.github/skills/inventory-legacy-system/SKILL.md +131 -0
  174. package/resources/customizations/.github/skills/reconcile-parity-matrix/SKILL.md +131 -0
  175. package/resources/customizations/.github/skills/review-discovery-coverage/SKILL.md +129 -0
  176. package/resources/customizations/.github/skills/validate-discovery-artifacts/SKILL.md +127 -0
  177. package/resources/lib/codex-routing/CodexDeployment.psm1 +5 -1
  178. package/resources/lib/codex-routing/CodexDiscoveryRouting.psm1 +161 -0
  179. package/resources/lib/codex-routing/CodexTopology.psm1 +25 -2
  180. package/resources/lib/discovery-validation/DiscoveryAuthority.psd1 +90 -0
  181. package/resources/lib/discovery-validation/DiscoveryAuthority.psm1 +493 -0
  182. package/resources/lib/discovery-validation/DiscoveryAuthorization.psm1 +479 -0
  183. package/resources/lib/discovery-validation/DiscoveryCapability.psm1 +468 -0
  184. package/resources/lib/discovery-validation/DiscoveryEncoding.psm1 +156 -0
  185. package/resources/lib/discovery-validation/DiscoveryManifest.psm1 +384 -0
  186. package/resources/lib/discovery-validation/DiscoveryObservation.psm1 +217 -0
  187. package/resources/lib/discovery-validation/DiscoveryPayload.psm1 +276 -0
  188. package/resources/lib/discovery-validation/DiscoveryPlanning.psm1 +336 -0
  189. package/resources/lib/discovery-validation/DiscoveryRootGrant.psm1 +164 -0
  190. package/resources/lib/discovery-validation/DiscoveryStart.psm1 +440 -0
  191. package/resources/lib/discovery-validation/DiscoveryStartRecord.psm1 +296 -0
  192. package/resources/lib/discovery-validation/DiscoveryStop.psm1 +336 -0
  193. package/resources/lib/discovery-validation/DiscoveryToolRouting.psm1 +228 -0
  194. package/resources/powershell/PoshQC/PoshQC.Parallel.psm1 +452 -0
  195. package/resources/powershell/PoshQC/PoshQC.Shard.psm1 +106 -8
  196. package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +12 -0
  197. package/resources/powershell/PoshQC/PoshQC.psd1 +6 -1
  198. package/resources/powershell/PoshQC/PoshQC.psm1 +7 -1
  199. package/resources/powershell/PoshQC/README.md +20 -1
@@ -7,14 +7,28 @@
7
7
  operation. When the target file is a Python file (.py), it decides whether the
8
8
  change may proceed in direct mode.
9
9
 
10
- Direct mode counts distinct production Python paths per Claude Code session. The
11
- running set is persisted under .claude/state/python-batch-budget.<session_id>.json,
12
- and repeated edits to the same file are counted once. The 4th distinct production
13
- path is denied with a PYTHON_LARGE_PATH_REQUIRED reason that instructs the caller
14
- to route the change through /orchestrate.
15
-
16
- The orchestrated large path is detected from
17
- <root>/artifacts/orchestration/orchestrator-state.json. The selected route is the
10
+ Direct mode counts distinct production Python paths per Claude Code session and per
11
+ resolved target worktree, and repeated edits to the same file are counted once. The
12
+ 4th distinct production path in one worktree is denied with a
13
+ PYTHON_LARGE_PATH_REQUIRED reason that instructs the caller to route the change
14
+ through /orchestrate.
15
+
16
+ The edited file is placed with Resolve-HookTargetWorktree from
17
+ .claude/lib/worktree-resolution/HookTargetWorktree.psm1. An absolute path resolves to
18
+ its nearest enclosing registered git worktree; a relative path, a path inside no
19
+ registered worktree, and an ambiguous result use the session root (the checkout that
20
+ holds this hook). The route is read from
21
+ artifacts/orchestration/orchestrator-state.json in the resolved target worktree.
22
+ The running set is kept under the session root, in
23
+ .claude/state/python-batch-budget.<session_id>.json when the resolved target worktree
24
+ is the session root and in
25
+ .claude/state/python-batch-budget.<session_id>.<worktree key>.json otherwise, where
26
+ the key comes from Get-HookTargetWorktreeKey; no state is written under another
27
+ worktree. A path under the root-level artifacts/ directory of the resolved target worktree
28
+ is gitignored scratch: it is allowed, consumes no slot, and writes no state. A
29
+ nested directory named artifacts elsewhere in the tree still counts.
30
+
31
+ The selected route is the
18
32
  route_id value when that key is present, otherwise the path_selected value, and it
19
33
  is usable only as a non-blank string. When the selected route is large,
20
34
  remediation, or preparation and the checkpoint is not terminal (next_step is not
@@ -38,9 +52,9 @@
38
52
  is composed into a file name, so a hostile id cannot escape the state
39
53
  directory. Resolving the id never creates the state directory.
40
54
 
41
- Candidate paths are contained to the resolved root. A candidate that resolves
42
- outside it is discarded: the decision is 'allow', no slot is consumed, and no
43
- state is written. Persisted entries that fail the same containment test are
55
+ Candidate paths are contained to the resolved target worktree root. A candidate
56
+ that resolves outside it is discarded: the decision is 'allow', no slot is consumed,
57
+ and no state is written. Persisted entries that fail the same containment test are
44
58
  dropped when state is rehydrated, so a state file carried between worktrees
45
59
  cannot spend this worktree's budget.
46
60
 
@@ -48,9 +62,12 @@
48
62
  hookSpecificOutput.permissionDecision = 'deny' and exits 0. Files already counted
49
63
  are always allowed through.
50
64
 
51
- Known limitation: a stale non-terminal large-path checkpoint left at the root
52
- exempts a later direct-mode session at that root. Orchestrator checkpoint hygiene
53
- (issue #673) moves foreign checkpoints aside before a new run.
65
+ Known limitations: an 8.3 short-name path, or a path reached through a junction,
66
+ that differs textually from the long path git reports matches no registered
67
+ worktree and falls back to the session root. A stale non-terminal large-path
68
+ checkpoint in a worktree exempts a later direct-mode session editing that worktree;
69
+ orchestrator checkpoint hygiene (issue #673) moves foreign checkpoints aside before
70
+ a new run.
54
71
 
55
72
  .NOTES
56
73
  Compatible with PowerShell 7+.
@@ -61,6 +78,7 @@ $script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot
61
78
 
62
79
 
63
80
  try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
81
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/HookTargetWorktree.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookTargetWorktree.psm1' -ErrorRecord $_ }
64
82
  $script:PythonBatchBudgetRouteImportFailure = $null; try { . (Join-Path $PSScriptRoot 'enforce-batch-budget-route.ps1') } catch { $script:PythonBatchBudgetRouteImportFailure = 'enforce-batch-budget-route.ps1' }
65
83
 
66
84
  function Test-PythonBatchBudgetPathInRoot {
@@ -354,7 +372,8 @@ function Invoke-PythonBatchBudgetHook {
354
372
  [scriptblock] $WriteState = {
355
373
  param([string] $Path, [System.Collections.IDictionary] $State)
356
374
  $State | ConvertTo-Json -Depth 5 | Set-Content -Path $Path -Encoding UTF8
357
- }
375
+ },
376
+ [scriptblock] $WorktreeListInvoker
358
377
  )
359
378
 
360
379
  $payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
@@ -376,6 +395,25 @@ function Invoke-PythonBatchBudgetHook {
376
395
  }
377
396
  if ($script:PythonBatchBudgetRouteImportFailure) { return Get-PythonBatchBudgetBlockDecision -Reason "PYTHON_LARGE_PATH_REQUIRED: the route helper '$($script:PythonBatchBudgetRouteImportFailure)' failed to load, so the batch budget for '$normalized' cannot be evaluated; the gate fails closed." }
378
397
 
398
+ # The file is evaluated against the registered worktree that holds it. The session root
399
+ # stays the fallback for a relative path, a path in no registered worktree, and an
400
+ # ambiguous result, which keeps today's checkpoint, containment, and state-file name.
401
+ $resolverArguments = @{ SessionRoot = $Root; FilePath = @($filePath) }
402
+ if ($PSBoundParameters.ContainsKey('WorktreeListInvoker')) { $resolverArguments['WorktreeListInvoker'] = $WorktreeListInvoker }
403
+ $resolution = Resolve-HookTargetWorktree @resolverArguments
404
+ $targetRoot = $Root
405
+ $isSessionRoot = $true
406
+ if (-not $resolution.Ambiguous -and $null -ne $resolution.WorktreeRoot -and -not [string]::Equals($resolution.WorktreeRoot, (ConvertTo-HookTargetWorktreeNormalizedPath -Path $Root), [System.StringComparison]::OrdinalIgnoreCase)) {
407
+ $targetRoot = $resolution.WorktreeRoot
408
+ $isSessionRoot = $false
409
+ }
410
+
411
+ # The root-level artifacts/ directory is gitignored scratch, never a production file.
412
+ $relativePath = if ($normalized -match '^(/|[A-Za-z]:/)') { Get-HookTargetWorktreeRelativePath -Path $normalized -WorktreeRoot $targetRoot } else { $normalized }
413
+ if ($null -ne $relativePath -and $relativePath.StartsWith('artifacts/', [System.StringComparison]::OrdinalIgnoreCase)) {
414
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
415
+ }
416
+
379
417
  $stateDir = Join-Path -Path $Root -ChildPath '.claude/state'
380
418
 
381
419
  # Resolved before the directory is ensured, because reading the session-id
@@ -386,11 +424,12 @@ function Invoke-PythonBatchBudgetHook {
386
424
  -SessionIdFilePath (Join-Path -Path $stateDir -ChildPath 'current-session-id') `
387
425
  -ReadSessionIdFile $ReadSessionIdFile
388
426
 
389
- $stateFile = Join-Path -Path $stateDir -ChildPath ("python-batch-budget.$resolvedSessionId.json")
427
+ $stateLeaf = if ($isSessionRoot) { "python-batch-budget.$resolvedSessionId.json" } else { "python-batch-budget.$resolvedSessionId.$(Get-HookTargetWorktreeKey -WorktreeRoot $targetRoot).json" }
428
+ $stateFile = Join-Path -Path $stateDir -ChildPath $stateLeaf
390
429
 
391
430
  # The checkpoint is read before any state operation, so the large path neither
392
431
  # creates the state directory nor reads or writes the state file.
393
- $checkpointPath = Join-Path -Path $Root -ChildPath 'artifacts/orchestration/orchestrator-state.json'
432
+ $checkpointPath = Join-Path -Path $targetRoot -ChildPath 'artifacts/orchestration/orchestrator-state.json'
394
433
  $checkpointText = ''
395
434
  try {
396
435
  $checkpointText = [string](& $ReadCheckpoint $checkpointPath)
@@ -402,7 +441,7 @@ function Invoke-PythonBatchBudgetHook {
402
441
  $isLargePath = Test-BatchBudgetLargePathRoute -CheckpointText $checkpointText
403
442
  $observedRoute = Get-BatchBudgetSelectedRoute -CheckpointText $checkpointText
404
443
  if ($isLargePath) {
405
- return Invoke-PythonBatchBudgetDecision -FilePath $filePath -State (Get-PythonBatchBudgetState -ProdCap $ProdCap) -StateFile $stateFile -Root $Root -LargePathRoute
444
+ return Invoke-PythonBatchBudgetDecision -FilePath $filePath -State (Get-PythonBatchBudgetState -ProdCap $ProdCap) -StateFile $stateFile -Root $targetRoot -LargePathRoute
406
445
  }
407
446
 
408
447
  if (-not (& $TestPathExists $stateDir)) {
@@ -414,13 +453,13 @@ function Invoke-PythonBatchBudgetHook {
414
453
  if (& $TestPathExists $stateFile) {
415
454
  try {
416
455
  $loaded = & $ReadState $stateFile | ConvertFrom-Json -ErrorAction Stop
417
- $state = ConvertTo-PythonBatchBudgetState -InputObject $loaded -ProdCap $ProdCap -Root $Root
456
+ $state = ConvertTo-PythonBatchBudgetState -InputObject $loaded -ProdCap $ProdCap -Root $targetRoot
418
457
  } catch {
419
458
  Write-Verbose "Ignoring unreadable Python batch-budget state file '$stateFile': $($_.Exception.Message)"
420
459
  }
421
460
  }
422
461
 
423
- $decision = Invoke-PythonBatchBudgetDecision -FilePath $filePath -State $state -StateFile $stateFile -Root $Root -ObservedRoute $observedRoute
462
+ $decision = Invoke-PythonBatchBudgetDecision -FilePath $filePath -State $state -StateFile $stateFile -Root $targetRoot -ObservedRoute $observedRoute
424
463
  if ($decision.shouldWriteState) {
425
464
  try {
426
465
  & $WriteState $stateFile $decision.state
@@ -0,0 +1,132 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Native SubagentStart attestation adapter for Claude Code discovery agents.
4
+
5
+ .DESCRIPTION
6
+ Invoked by the Claude Code SubagentStart hook. The hook reads the start envelope,
7
+ derives the launch context from the payload alone (session, the host-assigned
8
+ agent_id and agent_type, and the literal parent root), and takes its decision
9
+ from the shared DiscoveryStart module. The start is observational: Claude Code
10
+ does not let a SubagentStart hook prevent the agent from running.
11
+
12
+ - a start whose agent type is not a discovery specialist is left to the
13
+ ordinary and epic attestation path and produces no output;
14
+ - a discovery specialist is attested when the protected store holds exactly one
15
+ prepared discovery-invocation bound to this host, session, parent and agent
16
+ type and the activation, deployment, agent profile hash and capability pins
17
+ agree; the start-attestation is recorded and the invocation moves to started;
18
+ - a start that cannot be attested writes the DISCOVERY_START_ATTESTATION_BLOCKED:
19
+ reason as the SubagentStart system message; the PreToolUse adapter then binds
20
+ the prepared invocation at the first controlled call or denies every grant.
21
+
22
+ No orchestrator checkpoint is read or written. An envelope the hook cannot read
23
+ and a dependency that fails to load write the reason to stderr at exit 2
24
+ (issue #980).
25
+
26
+ .NOTES
27
+ Requires PowerShell 7+. Invokes NO external process and never reads a profile,
28
+ locator or payload field to decide identity or authority.
29
+ #>
30
+ [CmdletBinding()]
31
+ param()
32
+ $script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
33
+
34
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
35
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryEncoding.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryEncoding.psm1' -ErrorRecord $_ }
36
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryAuthority.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryAuthority.psm1' -ErrorRecord $_ }
37
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryPayload.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryPayload.psm1' -ErrorRecord $_ }
38
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryStartRecord.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryStartRecord.psm1' -ErrorRecord $_ }
39
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryStart.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryStart.psm1' -ErrorRecord $_ }
40
+
41
+ $script:DiscoveryStartReasonPrefix = 'DISCOVERY_START_ATTESTATION_BLOCKED:'
42
+ $script:DiscoveryStartHandlerPath = $PSCommandPath
43
+
44
+ function Get-DiscoveryStartHandlerSha256 {
45
+ <#
46
+ .SYNOPSIS
47
+ Returns the SHA-256 of this handler's own bytes for the attestation writer record.
48
+ #>
49
+ [CmdletBinding()]
50
+ [OutputType([string])]
51
+ param([Parameter(Mandatory = $false)][AllowNull()][string]$HandlerPath = $script:DiscoveryStartHandlerPath)
52
+
53
+ if ([string]::IsNullOrEmpty($HandlerPath) -or -not (Test-Path -LiteralPath $HandlerPath -PathType Leaf)) {
54
+ return ('0' * 64)
55
+ }
56
+ return Get-DiscoveryBytesSha256Hex -Bytes ([System.IO.File]::ReadAllBytes($HandlerPath))
57
+ }
58
+
59
+ function Invoke-DiscoveryAgentStartDecision {
60
+ <#
61
+ .SYNOPSIS
62
+ Parses the SubagentStart envelope and returns the shared start outcome.
63
+ .DESCRIPTION
64
+ Returns the outcome object (Decision skip, observe or attest; Code; Reason;
65
+ Attestation; AttestationPath; Invocation). Every seam defaults to the real
66
+ host and is injectable so tests run over in-memory values. A recorded
67
+ dependency failure denies before the envelope is read.
68
+ #>
69
+ [CmdletBinding()]
70
+ [OutputType([pscustomobject])]
71
+ param(
72
+ [Parameter(Mandatory = $false)][AllowNull()][AllowEmptyString()][string]$PayloadRaw,
73
+ [Parameter(Mandatory = $false)][AllowNull()][hashtable]$HostEnvironment = $null,
74
+ [Parameter(Mandatory = $false)][AllowNull()][hashtable]$Store = $null,
75
+ [Parameter(Mandatory = $false)][AllowNull()][nullable[datetime]]$Now = $null,
76
+ [Parameter(Mandatory = $false)][AllowNull()][string]$Nonce = $null,
77
+ [Parameter(Mandatory = $false)][AllowNull()][scriptblock]$ReadBytes = $null
78
+ )
79
+ if (Test-HookDependencyFailure) {
80
+ $reason = Get-HookDependencyFailureReason -ReasonPrefix $script:DiscoveryStartReasonPrefix
81
+ return [pscustomobject]@{ Decision = 'deny'; Code = 'DEPENDENCY'; Reason = $reason.Substring($script:DiscoveryStartReasonPrefix.Length).TrimStart(); Attestation = $null; AttestationPath = $null; Invocation = $null }
82
+ }
83
+
84
+ # Seams resolve only after the dependency check so a missing module denies
85
+ # instead of raising while a default is evaluated.
86
+ if ($null -eq $HostEnvironment) { $HostEnvironment = Get-DiscoveryTrustedHostEnvironment }
87
+ if ($null -eq $Store) { $Store = Get-DiscoveryStartStore }
88
+ if ($null -eq $Now) { $Now = [datetime]::UtcNow }
89
+ if ([string]::IsNullOrEmpty($Nonce)) { $Nonce = Get-DiscoveryAuthorizationNonceText }
90
+
91
+ $normalized = ConvertFrom-DiscoveryHostPayloadText -HostName 'claude-code' -Text $PayloadRaw
92
+ $parameters = @{
93
+ Normalized = $normalized; HostEnvironment = $HostEnvironment
94
+ HandlerSha256 = (Get-DiscoveryStartHandlerSha256); Store = $Store; Now = $Now; Nonce = $Nonce
95
+ }
96
+ if ($null -ne $ReadBytes) { $parameters['ReadBytes'] = $ReadBytes }
97
+ return Get-DiscoveryStartDecision @parameters
98
+ }
99
+
100
+ function Invoke-DiscoveryAgentStartEntryPoint {
101
+ <#
102
+ .SYNOPSIS
103
+ Reads one SubagentStart payload, writes the hook output and returns the process exit code.
104
+ .DESCRIPTION
105
+ An unreadable envelope or a dependency failure writes its reason to stderr
106
+ and returns 2. An observed (unattested) start writes the SubagentStart output
107
+ JSON to stdout and returns 0. A skipped or attested start writes nothing and returns 0.
108
+ #>
109
+ [CmdletBinding()]
110
+ [OutputType([int])]
111
+ param([Parameter(Mandatory = $false)][AllowNull()][AllowEmptyString()][string]$PayloadRaw)
112
+
113
+ $outcome = Invoke-DiscoveryAgentStartDecision -PayloadRaw $PayloadRaw
114
+ if ($outcome.Decision -notin @('deny', 'observe')) { return 0 }
115
+ if ($outcome.Code -in @('ENVELOPE', 'DEPENDENCY')) {
116
+ [Console]::Error.WriteLine("$($script:DiscoveryStartReasonPrefix) $($outcome.Reason)")
117
+ return 2
118
+ }
119
+ $output = ConvertTo-DiscoveryStartHookOutput -Outcome $outcome -ReasonPrefix $script:DiscoveryStartReasonPrefix
120
+ [Console]::Out.WriteLine(($output | ConvertTo-Json -Compress -Depth 5))
121
+ return 0
122
+ }
123
+
124
+ # Guard allows dot-sourcing in tests without executing the entrypoint.
125
+ if ($MyInvocation.InvocationName -eq '.') {
126
+ return
127
+ }
128
+ if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('DISCOVERY_START_ATTESTATION_BLOCKED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
129
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix 'DISCOVERY_START_ATTESTATION_BLOCKED:'
130
+ if ($null -ne $dependencyDecision) { [Console]::Error.WriteLine($dependencyDecision.Reason); exit $dependencyDecision.ExitCode }
131
+
132
+ exit (Invoke-DiscoveryAgentStartEntryPoint -PayloadRaw (Read-ClaudeHookRawPayload))
@@ -1,244 +1,88 @@
1
1
  <#
2
2
  .SYNOPSIS
3
- SubagentStop hook that validates discovery artifacts referenced in a
4
- terminating subagent's final output.
3
+ SubagentStop hook that validates a discovery specialist's protected output authority.
5
4
 
6
5
  .DESCRIPTION
7
6
  Invoked by the Claude Code SubagentStop hook under the existing broad
8
- generic-agent matcher group. The hook reads the SubagentStop envelope JSON
9
- from stdin through the shared envelope helper (subagentstop-envelope.ps1),
10
- takes its last_assistant_message field (the terminating subagent's last
11
- assistant message), scans that text for discovery-artifact path references
12
- via Get-DiscoveryArtifactType, and for each recognized reference (when a
13
- required-artifact declaration is present) invokes the discovery validator
14
- via Invoke-DiscoveryValidatorExe.
15
-
16
- Any referenced artifact that fails validation blocks the subagent's
17
- termination: the hook writes the reason with a
18
- DISCOVERY_ARTIFACT_GATE_BLOCKED: prefix to stderr and exits 2 through the
19
- shared envelope helper. An invalid envelope (empty, not valid JSON, not a
20
- JSON object, or without a string last_assistant_message) also blocks, with
21
- a "DISCOVERY_ARTIFACT_GATE_BLOCKED: payload anomaly" reason. The hook
22
- allows termination when the last assistant message is blank, when it
23
- references no discovery-artifact path, when the required-artifact
24
- declaration is absent (fail-open), or when every referenced artifact
25
- passes validation.
26
-
27
- This hook provides the authoritative, defense-in-depth check of final
28
- workspace state, regardless of which tool produced the artifact,
29
- complementing the PreToolUse gate in
30
- enforce-discovery-artifact-gate.ps1. Neither hook implements or
31
- reimplements discovery-validator logic; both only route to the validator
32
- CLI delivered by a separate feature and interpret its exit code and
33
- captured output.
7
+ generic-agent matcher group. The hook reads the SubagentStop envelope through
8
+ the shared envelope helper (subagentstop-envelope.ps1), derives the stopping
9
+ principal from the payload alone (session, the host-assigned agent_id and
10
+ agent_type, and the literal parent root), and takes its decision from the
11
+ shared DiscoveryStop module:
12
+
13
+ - a stop whose agent type is not a discovery specialist is allowed, so the
14
+ other SubagentStop validators keep their behavior;
15
+ - a discovery specialist may stop only when its protected invocation was
16
+ finalized through the gateway and the completion receipt is the gateway's
17
+ validation-receipt for this invocation, session, role and gate mode, bound to
18
+ the current activation and workspace, with every required output present,
19
+ every input unchanged since the receipt and output_valid true; the
20
+ migration-coverage-reviewer may stop on a valid failing report;
21
+ - filenames or success prose in the final message never admit a stop, and a
22
+ blank final message never skips the validation.
23
+
24
+ A stop that cannot be validated blocks: the reason is written with the
25
+ DISCOVERY_ARTIFACT_GATE_BLOCKED: prefix to stderr and the hook exits 2 through
26
+ the shared envelope helper. An invalid envelope blocks with a "payload anomaly"
27
+ reason. An unhandled error during validation blocks with exit 2 and a reason
28
+ (issue #928). No orchestrator checkpoint is read or written.
34
29
 
35
30
  .NOTES
36
- Requires PowerShell 7.4+ (the shared validation module uses
37
- `Test-Json -SchemaFile` Draft 2020-12 support). Read-only validation gate that
38
- invokes NO external process: validation runs in-process through
39
- `.claude/lib/discovery-validation/DiscoveryValidation.psm1` (issue #475).
40
- An unhandled error during validation blocks with exit 2 and a reason (issue #928).
31
+ Requires PowerShell 7+. Invokes NO external process and never reads a profile,
32
+ locator or final-message field to decide identity or authority (issue #980).
41
33
  #>
42
34
  [CmdletBinding()]
43
35
  param()
44
36
  $script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
45
37
  try { . (Join-Path $PSScriptRoot 'subagentstop-envelope.ps1') } catch { Add-HookDependencyFailure -Name 'subagentstop-envelope.ps1' -ErrorRecord $_ }
46
- try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryValidation.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryValidation.psm1' -ErrorRecord $_ }
38
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryEncoding.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryEncoding.psm1' -ErrorRecord $_ }
39
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryAuthority.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryAuthority.psm1' -ErrorRecord $_ }
40
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryManifest.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryManifest.psm1' -ErrorRecord $_ }
41
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryPayload.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryPayload.psm1' -ErrorRecord $_ }
42
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryStartRecord.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryStartRecord.psm1' -ErrorRecord $_ }
43
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryStop.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryStop.psm1' -ErrorRecord $_ }
47
44
 
48
- function Invoke-DiscoveryValidatorExe {
49
- <#
50
- .SYNOPSIS
51
- Wrapper around the discovery-artifact validator. Mockable seam.
52
- .DESCRIPTION
53
- Delegates to the portable PowerShell implementation in
54
- `.claude/lib/discovery-validation/DiscoveryValidation.psm1`, keeping this
55
- function's name, its `-ValidatorArgs <string[]>` parameter, and its
56
- `@{ ExitCode; Output }` return shape unchanged so existing mocks and
57
- `Should -Invoke` assertions continue to bind.
58
-
59
- This no longer invokes a Python interpreter (issue #475). The `.claude/**`
60
- payload ships to destinations with no guaranteed Python, Poetry, or
61
- `scripts/dev_tools`, where the previous `python -m ...` call failed
62
- obscurely or blocked every operation.
63
-
64
- Success is SILENT by contract: a passing validation returns `ExitCode = 0`
65
- with an EMPTY `Output`. The caller denies on a non-zero exit code OR on
66
- non-empty output, so any success chatter here would deny a passing
67
- validation (defect D-2).
68
- #>
69
- [CmdletBinding()]
70
- [OutputType([hashtable])]
71
- param(
72
- [Parameter(Mandatory = $true)]
73
- [string[]] $ValidatorArgs
74
- )
75
-
76
- $modulePath = Join-Path -Path $PSScriptRoot `
77
- -ChildPath '../lib/discovery-validation/DiscoveryValidation.psm1'
78
- if (-not (Test-Path -LiteralPath $modulePath -PathType Leaf)) {
79
- return @{ ExitCode = 1; Output = "Discovery-validation module not found: $modulePath" }
80
- }
81
-
82
- Import-Module -Name $modulePath -Force -ErrorAction Stop
83
- return Invoke-DiscoveryArtifactValidation -ValidatorArgs $ValidatorArgs
84
- }
85
-
86
- function Get-DiscoveryArtifactType {
87
- <#
88
- .SYNOPSIS
89
- Maps a normalized file path to a discovery-artifact-type token.
90
- .DESCRIPTION
91
- Returns one of the eight validator subcommand tokens (profile,
92
- feature-contract, coverage-ledger, runtime-scenario, parity-matrix,
93
- unspecified-behavior, product-decision, evidence-reference), or $null
94
- when the path does not resolve to a recognized discovery-artifact
95
- type.
96
-
97
- # TODO(#9002): this is a narrow, replaceable directory/filename lookup.
98
- The schema-versioned directory/filename convention this mapping
99
- depends on is owned by #9002 and is not finalized in this branch.
100
- Replace this lookup once #9002 ships its versioning convention.
101
- #>
102
- [CmdletBinding()]
103
- [OutputType([string])]
104
- param(
105
- [Parameter(Mandatory = $true)]
106
- [string] $Path
107
- )
108
-
109
- $normalized = $Path -replace '\\', '/'
110
-
111
- $typeMap = [ordered]@{
112
- 'discovery/profile' = 'profile'
113
- 'discovery/feature-contract' = 'feature-contract'
114
- 'discovery/coverage-ledger' = 'coverage-ledger'
115
- 'discovery/runtime-scenario' = 'runtime-scenario'
116
- 'discovery/parity-matrix' = 'parity-matrix'
117
- 'discovery/unspecified-behavior' = 'unspecified-behavior'
118
- 'discovery/product-decision' = 'product-decision'
119
- 'discovery/evidence-reference' = 'evidence-reference'
120
- }
121
-
122
- foreach ($prefix in $typeMap.Keys) {
123
- if ($normalized -match "(^|/)$([regex]::Escape($prefix))") {
124
- return $typeMap[$prefix]
125
- }
126
- }
127
-
128
- return $null
129
- }
130
-
131
- function Get-RequiredDiscoveryArtifactDeclaration {
132
- <#
133
- .SYNOPSIS
134
- Reads the domain-profile required-artifact declaration, if present.
135
- .DESCRIPTION
136
- # TODO(#9001): this is a narrow, injectable RequiredArtifactPathsReader
137
- seam. The discovery-workspace root and which of the eight artifact
138
- types are "required" for a given gate are domain-profile runtime
139
- configuration owned by #9001, which has no shipped parser/schema in
140
- this branch.
141
-
142
- Default behavior on absence is documented inline as fail-open
143
- (allow/exit 0): when no domain profile is present, this function
144
- returns an object with Present = $false, and callers must treat that
145
- as "always allow, never invoke the validator" rather than as an error.
146
- #>
147
- [CmdletBinding()]
148
- [OutputType([hashtable])]
149
- param(
150
- [Parameter(Mandatory = $false)]
151
- [scriptblock] $ProfileReader = { $null }
152
- )
153
-
154
- $declaration = & $ProfileReader
155
- if ($null -eq $declaration) {
156
- # Fail open: no domain profile / required-artifact declaration present.
157
- return @{ Present = $false }
158
- }
159
-
160
- return @{ Present = $true; Declaration = $declaration }
161
- }
162
-
163
- function Find-DiscoveryArtifactReference {
164
- <#
165
- .SYNOPSIS
166
- Extracts candidate discovery-artifact path references from subagent
167
- output text.
168
- .DESCRIPTION
169
- Splits the output text into whitespace-delimited tokens and returns
170
- the distinct set of tokens that resolve to a recognized
171
- discovery-artifact type via Get-DiscoveryArtifactType. This is a
172
- lightweight text scan, not a JSON/markdown parser, matching the
173
- precision needed for a defense-in-depth completion gate.
174
- #>
175
- [CmdletBinding()]
176
- [OutputType([string[]])]
177
- param(
178
- [Parameter(Mandatory = $true)]
179
- [AllowEmptyString()]
180
- [string] $OutputText
181
- )
182
-
183
- if ([string]::IsNullOrWhiteSpace($OutputText)) {
184
- return [string[]]@()
185
- }
186
-
187
- $tokens = $OutputText -split '\s+' | Where-Object { $_ }
188
- $foundReferences = New-Object System.Collections.Generic.List[string]
189
- foreach ($token in $tokens) {
190
- $trimmed = $token.Trim('`', '"', "'", ',', ';', '(', ')', '[', ']')
191
- if ($trimmed -and (Get-DiscoveryArtifactType -Path $trimmed)) {
192
- if (-not $foundReferences.Contains($trimmed)) {
193
- $foundReferences.Add($trimmed)
194
- }
195
- }
196
- }
197
-
198
- return [string[]]$foundReferences
199
- }
45
+ $script:DiscoveryGateReasonPrefix = 'DISCOVERY_ARTIFACT_GATE_BLOCKED:'
200
46
 
201
47
  function Invoke-DiscoveryArtifactGateValidation {
202
48
  <#
203
49
  .SYNOPSIS
204
- Reads the SubagentStop envelope and returns an Ok/Message validation
205
- result for a discovery-artifact completion gate.
50
+ Reads the SubagentStop envelope and returns an Ok/Message validation result for a discovery stop.
51
+ .DESCRIPTION
52
+ Every seam defaults to the real host (platform state roots, protected
53
+ store, workspace bytes) and is injectable so tests run over in-memory
54
+ values. A recorded dependency failure blocks before the envelope is read.
55
+ The result also carries the shared outcome Code for diagnostics.
206
56
  #>
207
57
  [CmdletBinding()]
208
58
  [OutputType([hashtable])]
209
59
  param(
210
60
  [AllowNull()] [AllowEmptyString()] [string] $InputText,
211
-
212
- [Parameter(Mandatory = $false)]
213
- [scriptblock] $RequiredArtifactReader = { Get-RequiredDiscoveryArtifactDeclaration }
61
+ [Parameter(Mandatory = $false)][AllowNull()][hashtable]$HostEnvironment = $null,
62
+ [Parameter(Mandatory = $false)][AllowNull()][hashtable]$Store = $null,
63
+ [Parameter(Mandatory = $false)][AllowNull()][scriptblock]$ReadBytes = $null
214
64
  )
215
- $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix 'DISCOVERY_ARTIFACT_GATE_BLOCKED:'
65
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix $script:DiscoveryGateReasonPrefix
216
66
  if ($null -ne $dependencyDecision) { return $dependencyDecision }
217
67
  $envelope = if ($PSBoundParameters.ContainsKey('InputText')) { Read-SubagentStopEnvelope -InputText $InputText } else { Read-SubagentStopEnvelope }
218
- if (-not $envelope.IsValid) { return @{ Ok = $false; Message = "DISCOVERY_ARTIFACT_GATE_BLOCKED: payload anomaly - $(Get-SubagentStopEnvelopeAnomalyReason -Anomaly $envelope.Anomaly)" } }
219
- $assistantMessage = [string]$envelope.AssistantText
68
+ if (-not $envelope.IsValid) { return @{ Ok = $false; Message = "$($script:DiscoveryGateReasonPrefix) payload anomaly - $(Get-SubagentStopEnvelopeAnomalyReason -Anomaly $envelope.Anomaly)"; Code = 'ENVELOPE' } }
220
69
 
221
- $references = Find-DiscoveryArtifactReference -OutputText $assistantMessage
222
- if ($references.Count -eq 0) {
223
- return @{ Ok = $true; Message = $null }
224
- }
70
+ # Seams resolve only after the dependency check so a missing module blocks
71
+ # instead of raising while a default is evaluated.
72
+ if ($null -eq $HostEnvironment) { $HostEnvironment = Get-DiscoveryTrustedHostEnvironment }
73
+ if ($null -eq $Store) { $Store = Get-DiscoveryStartStore }
225
74
 
226
- $requiredDeclaration = & $RequiredArtifactReader
227
- if (-not $requiredDeclaration.Present) {
228
- # Fail open: no domain profile / required-artifact declaration present.
229
- return @{ Ok = $true; Message = $null }
75
+ $normalized = ConvertFrom-DiscoveryHostEnvelope -HostName 'claude-code' -Envelope $envelope.Envelope
76
+ $parameters = @{
77
+ Normalized = $normalized; HostEnvironment = $HostEnvironment
78
+ AssistantText = [string]$envelope.AssistantText; StopHookActive = [bool]$envelope.StopHookActive; Store = $Store
230
79
  }
231
-
232
- foreach ($reference in $references) {
233
- $artifactType = Get-DiscoveryArtifactType -Path $reference
234
- $validatorResult = Invoke-DiscoveryValidatorExe -ValidatorArgs @($artifactType, $reference)
235
- $hasErrorOutput = -not [string]::IsNullOrWhiteSpace($validatorResult['Output'])
236
- if ($validatorResult['ExitCode'] -ne 0 -or $hasErrorOutput) {
237
- return @{ Ok = $false; Message = "DISCOVERY_ARTIFACT_GATE_BLOCKED: $($validatorResult['Output'])" }
238
- }
80
+ if ($null -ne $ReadBytes) { $parameters['ReadBytes'] = $ReadBytes }
81
+ $outcome = Get-DiscoveryStopDecision @parameters
82
+ if ($outcome.Decision -eq 'block') {
83
+ return @{ Ok = $false; Message = "$($script:DiscoveryGateReasonPrefix) $($outcome.Reason)"; Code = $outcome.Code }
239
84
  }
240
-
241
- return @{ Ok = $true; Message = $null }
85
+ return @{ Ok = $true; Message = $null; Code = $outcome.Code }
242
86
  }
243
87
 
244
88
  # Guard allows dot-sourcing in tests without executing the entrypoint.