@danmoisan/drm-copilot-mcp 0.0.5 → 0.0.6

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 (47) hide show
  1. package/out/mcp-server.js +93 -20
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/orchestrator.md +6 -2
  4. package/resources/claude-customizations/.claude/agents/pr-author.md +78 -0
  5. package/resources/claude-customizations/.claude/agents/task-researcher.md +10 -6
  6. package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +68 -10
  7. package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +28 -1
  8. package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +40 -14
  9. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +210 -0
  10. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +153 -10
  11. package/resources/claude-customizations/.claude/hooks/validate-pr-author-output.ps1 +136 -0
  12. package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +22 -6
  13. package/resources/claude-customizations/.claude/settings.json +22 -0
  14. package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +0 -1
  15. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +76 -2
  16. package/resources/claude-customizations/.claude/skills/research-issue/SKILL.md +5 -3
  17. package/resources/claude-customizations/.claude-variants/csharp-legacy/agents/csharp-typed-engineer.md +69 -0
  18. package/resources/claude-customizations/.claude-variants/csharp-legacy/rules/csharp.md +96 -0
  19. package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +77 -0
  20. package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/invoke-csharp-engineer/SKILL.md +64 -0
  21. package/resources/claude-customizations/pack-manifests/core.json +65 -0
  22. package/resources/claude-customizations/pack-manifests/csharp-legacy.json +11 -0
  23. package/resources/claude-customizations/pack-manifests/csharp-modern.json +12 -0
  24. package/resources/claude-customizations/pack-manifests/powershell.json +14 -0
  25. package/resources/claude-customizations/pack-manifests/python.json +14 -0
  26. package/resources/claude-customizations/pack-manifests/typescript.json +9 -0
  27. package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +30 -13
  28. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +113 -10
  29. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +33 -8
  30. package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +12 -7
  31. package/resources/codex-and-agents-customizations/.codex/config.toml +65 -6
  32. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +303 -0
  33. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +300 -0
  34. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +148 -0
  35. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-pr-author-skill.ps1 +336 -0
  36. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +83 -17
  37. package/resources/config/orchestration-routing.json +2 -10
  38. package/resources/customizations/.github/agents/pr-author.agent.md +25 -0
  39. package/resources/customizations/.github/agents/task-researcher.agent.md +4 -4
  40. package/resources/customizations/.github/prompts/fillout-prd-feature.prompt.md +1 -1
  41. package/resources/customizations/.github/prompts/research-issue.prompt.md +4 -3
  42. package/resources/scripts/dev_tools/push_down_claude_customizations.py +253 -224
  43. package/resources/scripts/dev_tools/push_down_claude_filesystem.py +472 -0
  44. package/resources/scripts/dev_tools/push_down_claude_pack_selection.py +401 -0
  45. package/resources/scripts/dev_tools/validate_orchestrator_state.py +106 -29
  46. package/resources/scripts/dev_tools/validate_policy_audit_artifact.py +24 -0
  47. package/resources/templates/push_down_claude_customizations.py +128 -304
package/out/mcp-server.js CHANGED
@@ -15890,7 +15890,24 @@ var REPO_AUTOMATION_TOOL_DEFINITIONS = [
15890
15890
  inputSchema: {
15891
15891
  type: "object",
15892
15892
  properties: {
15893
- workspace_root: workspaceRootProperty
15893
+ workspace_root: workspaceRootProperty,
15894
+ packs: {
15895
+ type: "array",
15896
+ items: {
15897
+ type: "string"
15898
+ },
15899
+ description: "Optional language pack names to publish (for example 'core', 'typescript'). When omitted, the full tree is published. 'core' is always included."
15900
+ },
15901
+ csharp_variant: {
15902
+ type: "string",
15903
+ enum: ["modern", "legacy"],
15904
+ description: "Optional C# toolchain variant to source ('modern' default or 'legacy')."
15905
+ },
15906
+ memory_mode: {
15907
+ type: "string",
15908
+ enum: ["overwrite", "merge", "skip"],
15909
+ description: "Optional agent-memory handling mode: 'overwrite' (default), 'merge', or 'skip'."
15910
+ }
15894
15911
  },
15895
15912
  additionalProperties: false
15896
15913
  }
@@ -16203,6 +16220,56 @@ var REPO_AUTOMATION_TOOLS = [
16203
16220
  "validate_orchestration_artifacts"
16204
16221
  ];
16205
16222
 
16223
+ // ../../extensions/drm-copilot/src/mcp-tool-inputs-push-down.ts
16224
+ function resolveClaudePacksField(rawValue) {
16225
+ if (rawValue === void 0) {
16226
+ return void 0;
16227
+ }
16228
+ if (!Array.isArray(rawValue)) {
16229
+ throw new Error("Field 'packs' must be an array of strings when provided.");
16230
+ }
16231
+ return rawValue.map(
16232
+ (entry, index) => normalizeRequiredText(entry, `packs[${index}]`)
16233
+ );
16234
+ }
16235
+ function resolveCsharpVariantField(rawValue) {
16236
+ if (rawValue === void 0) {
16237
+ return void 0;
16238
+ }
16239
+ const variant = normalizeRequiredText(rawValue, "csharp_variant");
16240
+ if (variant !== "modern" && variant !== "legacy") {
16241
+ throw new Error("Field 'csharp_variant' must be 'modern' or 'legacy'.");
16242
+ }
16243
+ return variant;
16244
+ }
16245
+ function resolveMemoryModeField(rawValue) {
16246
+ if (rawValue === void 0) {
16247
+ return void 0;
16248
+ }
16249
+ const mode = normalizeRequiredText(rawValue, "memory_mode");
16250
+ if (mode !== "overwrite" && mode !== "merge" && mode !== "skip") {
16251
+ throw new Error(
16252
+ "Field 'memory_mode' must be 'overwrite', 'merge', or 'skip'."
16253
+ );
16254
+ }
16255
+ return mode;
16256
+ }
16257
+ function resolvePushDownClaudeCustomizationsToolInput(rawInput, fallbackWorkspaceRoot) {
16258
+ const args = asToolArgumentObject(rawInput);
16259
+ const packs = resolveClaudePacksField(args["packs"]);
16260
+ const csharpVariant = resolveCsharpVariantField(args["csharp_variant"]);
16261
+ const memoryMode = resolveMemoryModeField(args["memory_mode"]);
16262
+ return {
16263
+ workspaceRoot: normalizeWorkspaceRoot(
16264
+ args["workspace_root"],
16265
+ fallbackWorkspaceRoot
16266
+ ),
16267
+ ...packs === void 0 ? {} : { packs },
16268
+ ...csharpVariant === void 0 ? {} : { csharpVariant },
16269
+ ...memoryMode === void 0 ? {} : { memoryMode }
16270
+ };
16271
+ }
16272
+
16206
16273
  // ../../extensions/drm-copilot/src/mcp-tool-inputs.ts
16207
16274
  function asToolArgumentObject(rawInput) {
16208
16275
  if (rawInput === void 0) {
@@ -16339,15 +16406,6 @@ function resolvePushDownCodexAndAgentsCustomizationsToolInput(rawInput, fallback
16339
16406
  )
16340
16407
  };
16341
16408
  }
16342
- function resolvePushDownClaudeCustomizationsToolInput(rawInput, fallbackWorkspaceRoot) {
16343
- const args = asToolArgumentObject(rawInput);
16344
- return {
16345
- workspaceRoot: normalizeWorkspaceRoot(
16346
- args["workspace_root"],
16347
- fallbackWorkspaceRoot
16348
- )
16349
- };
16350
- }
16351
16409
  function resolveNewPotentialBugEntryToolInput(rawInput, fallbackWorkspaceRoot) {
16352
16410
  const args = asToolArgumentObject(rawInput);
16353
16411
  return {
@@ -16853,6 +16911,30 @@ function buildValidateOrchestrationArtifactsArgs(input) {
16853
16911
  return args;
16854
16912
  }
16855
16913
 
16914
+ // ../../extensions/drm-copilot/src/repo-automation-service-push-down.ts
16915
+ function buildPushDownClaudeCustomizationsOptions(input) {
16916
+ const args = ["--destination", input.workspaceRoot];
16917
+ if (input.packs !== void 0 && input.packs.length > 0) {
16918
+ args.push("--packs", input.packs.join(","));
16919
+ }
16920
+ if (input.csharpVariant !== void 0) {
16921
+ args.push("--csharp-variant", input.csharpVariant);
16922
+ }
16923
+ if (input.memoryMode !== void 0) {
16924
+ args.push("--memory-mode", input.memoryMode);
16925
+ }
16926
+ return {
16927
+ tool: "push_down_claude_customizations",
16928
+ runtimeKind: "python",
16929
+ bundledRelativePath: "resources/templates/push_down_claude_customizations.py",
16930
+ workspaceRoot: input.workspaceRoot,
16931
+ invocationId: input.invocationId ?? "push_down_claude_customizations",
16932
+ args,
16933
+ summary: "Pushed bundled Claude Code customizations into the destination workspace.",
16934
+ stdoutArtifactPattern: /Wrote push-down summary artifact to:\s*(.+)/i
16935
+ };
16936
+ }
16937
+
16856
16938
  // ../../extensions/drm-copilot/src/repo-automation-service-workflows.ts
16857
16939
  var path5 = __toESM(require("node:path"));
16858
16940
 
@@ -17091,16 +17173,7 @@ var DefaultRepoAutomationService = class {
17091
17173
  });
17092
17174
  }
17093
17175
  async pushDownClaudeCustomizations(input) {
17094
- return this.executeScript({
17095
- tool: "push_down_claude_customizations",
17096
- runtimeKind: "python",
17097
- bundledRelativePath: "resources/templates/push_down_claude_customizations.py",
17098
- workspaceRoot: input.workspaceRoot,
17099
- invocationId: input.invocationId ?? "push_down_claude_customizations",
17100
- args: ["--destination", input.workspaceRoot],
17101
- summary: "Pushed bundled Claude Code customizations into the destination workspace.",
17102
- stdoutArtifactPattern: /Wrote push-down summary artifact to:\s*(.+)/i
17103
- });
17176
+ return this.executeScript(buildPushDownClaudeCustomizationsOptions(input));
17104
17177
  }
17105
17178
  async newPotentialBugEntry(input) {
17106
17179
  return this.executeScript({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danmoisan/drm-copilot-mcp",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Stdio MCP server exposing drm-copilot repo-automation tools.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -2,7 +2,7 @@
2
2
  name: orchestrator
3
3
  description: Deterministic repository orchestrator that estimates change budget, selects small or large workflow path, delegates to specialist subagents, persists checkpoint state, and enforces completion gates proactively.
4
4
  tools:
5
- - "Agent(atomic-planner,atomic-executor,feature-review,task-researcher,prd-feature,staged-review,epic-review,status-updater,python-typed-engineer,powershell-typed-engineer,csharp-typed-engineer,typescript-engineer)"
5
+ - "Agent(atomic-planner,atomic-executor,feature-review,task-researcher,prd-feature,staged-review,epic-review,status-updater,pr-author,python-typed-engineer,powershell-typed-engineer,csharp-typed-engineer,typescript-engineer)"
6
6
  - Read
7
7
  - Grep
8
8
  - Glob
@@ -69,10 +69,14 @@ Delegate exclusively through configured subagents:
69
69
  - `atomic-planner` — generates phased implementation plans (planning only)
70
70
  - `atomic-executor` — executes approved plans task-by-task (execution only)
71
71
  - `feature-review` — produces policy, code, and feature audit artifacts
72
- - `task-researcher` — performs deep research and writes findings to `artifacts/research/`
72
+ - `task-researcher` — performs deep research and writes findings to the research path the orchestrator resolves before delegating: `docs/features/<feature>/research/` when an active `feature-folder` is in scope in `orchestrator-state.json`, otherwise `docs/research/` for one-off research. The orchestrator passes the resolved path in the delegation prompt.
73
73
 
74
74
  For required delegated steps, delegation is mandatory. If a handoff cannot be started, resumed, or completed, stop execution and record blocked state. Do not perform the step locally.
75
75
 
76
+ ## PR Creation Delegation
77
+
78
+ PR creation and PR body edits must be delegated to `Agent(pr-author)`. The orchestrator must not call `gh pr create` or `gh pr edit --body*` directly from the main thread; those commands are blocked by the `enforce-pr-author-skill.ps1` PreToolUse hook unless a valid authorization sentinel issued by the `pr-author` agent is present. The orchestrator first produces the PR-context artifact via `mcp__drm-copilot__collect_pr_context`, then delegates to `Agent(pr-author)`, which authors the PR body via the `pr-author` skill, writes and deletes the authorization sentinel around the `gh` command, and reports the resulting PR URL or PR number.
79
+
76
80
  ## Checkpoint Persistence
77
81
 
78
82
  Update `artifacts/orchestration/orchestrator-state.json` after every completed step with:
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: pr-author
3
+ description: Project-scoped agent that runs the pr-author skill to produce a GitHub-ready PR body from the canonical PR-context bundle, then opens or updates the pull request. Sole authorized caller of gh pr create and gh pr edit --body*. Writes a short-lived authorization sentinel immediately before each gh command and deletes it afterward.
4
+ model: sonnet
5
+ skills:
6
+ - pr-author
7
+ memory: project
8
+ tools:
9
+ - Read
10
+ - "Bash(git log *)"
11
+ - "Bash(git rev-parse *)"
12
+ - "Bash(gh pr create *)"
13
+ - "Bash(gh pr edit *)"
14
+ - "Write(/artifacts/**)"
15
+ hooks:
16
+ SubagentStop:
17
+ - matcher: "pr-author"
18
+ hooks:
19
+ - type: command
20
+ command: pwsh -NoProfile -File .claude/hooks/validate-pr-author-output.ps1
21
+ ---
22
+
23
+ # PR Author Agent
24
+
25
+ You are the dedicated pull-request authoring agent. Your sole responsibility is to consume the
26
+ canonical PR-context bundle, produce the PR body using the `pr-author` skill, and open or update the
27
+ pull request. You are the only agent authorized to run `gh pr create` and `gh pr edit --body*`. The
28
+ orchestrator and all other agents are blocked from these commands by the
29
+ `enforce-pr-author-skill.ps1` PreToolUse hook.
30
+
31
+ ## Skill
32
+
33
+ Apply the `pr-author` skill (`.claude/skills/pr-author/SKILL.md`) as the canonical workflow for
34
+ authoring the PR body. The skill produces the PR body text from the PR-context bundle
35
+ (`artifacts/pr_context.summary.txt` and `artifacts/pr_context.appendix.txt`) and its enumerated
36
+ additional context files. The skill itself only authors body text; opening and editing the pull
37
+ request via `gh pr create` / `gh pr edit` is this agent's responsibility, not the skill's.
38
+
39
+ ## Authorization Sentinel Write/Delete Protocol
40
+
41
+ Before any `gh pr create` or `gh pr edit --body*` command, you MUST perform these steps in order:
42
+
43
+ 1. Run `git rev-parse HEAD` to obtain the current `head_sha`.
44
+ 2. Write `artifacts/pr_author_authorization.json` with exactly these fields:
45
+ - `issued_by`: `"pr-author"`
46
+ - `issued_at`: the current time as a UTC ISO-8601 timestamp (for example `2026-06-24T16:00:00Z`)
47
+ - `head_sha`: the value from step 1
48
+ - `ttl_seconds`: `120`
49
+ 3. Issue the `gh pr create` or `gh pr edit --body-file ...` command immediately, within the 120-second
50
+ TTL. The PreToolUse hook verifies that the sentinel is present, that `issued_by` is exactly
51
+ `pr-author`, and that the sentinel has not expired before allowing the command.
52
+ 4. Delete `artifacts/pr_author_authorization.json` after the `gh` command completes, on both success
53
+ and failure. The TTL also expires abandoned sentinels, but explicit deletion is required.
54
+
55
+ The sentinel filename is exactly `artifacts/pr_author_authorization.json`. The PR body must be passed
56
+ via `--body-file`; inline `--body` is blocked by the hook (Case A).
57
+
58
+ ## Final Output Requirement
59
+
60
+ After opening or updating the pull request, report the resulting PR URL
61
+ (`https://github.com/<owner>/<repo>/pull/<n>`) or the PR number (`PR #<n>`) in your final output. The
62
+ `validate-pr-author-output.ps1` SubagentStop hook blocks completion when the final output contains no
63
+ PR URL or PR number.
64
+
65
+ ## Enforcement Strength (Honest Disclosure)
66
+
67
+ The authorization sentinel is a **policy guardrail, not a cryptographic or security control.** Any
68
+ actor with `Write(/artifacts/**)` access can forge `artifacts/pr_author_authorization.json`, because
69
+ all agents share the same filesystem and the runtime exposes no native agent-identity signal at Bash
70
+ PreToolUse time. The mechanism prevents accidental bypass (such as the PR #228 pattern where the
71
+ orchestrator wrote the body file and called `gh pr create` directly) and requires a deliberate,
72
+ documented act to circumvent. It is not tamper-proof and is not a security boundary.
73
+
74
+ ## Standing Rules
75
+
76
+ Repository tone is defined in `CLAUDE.md` and `.claude/rules/tonality.md`. Reference only files
77
+ listed under "Additional context files" in the PR-context bundle; do not cite or summarize files
78
+ outside that enumeration. Do not invent issue or PR numbers.
@@ -1,13 +1,14 @@
1
1
  ---
2
2
  name: task-researcher
3
- description: Research specialist that performs deep investigation and writes structured findings exclusively to artifacts/research/.
3
+ description: Research specialist that performs deep investigation and writes structured findings to the orchestrator-supplied research path under docs/features/<feature>/research/ (feature-associated) or docs/research/ (one-off).
4
4
  model: sonnet
5
5
  tools:
6
6
  - Read
7
7
  - Grep
8
8
  - Glob
9
9
  - WebFetch
10
- - "Write(/artifacts/research/**)"
10
+ - "Write(/docs/features/**/research/**)"
11
+ - "Write(/docs/research/**)"
11
12
  - evidence-and-timestamp-conventions
12
13
  memory: project
13
14
  hooks:
@@ -20,13 +21,16 @@ hooks:
20
21
 
21
22
  # Task Researcher Agent
22
23
 
23
- You are a research-only specialist. You perform deep analysis for task planning and write structured research notes. You do not make changes to source code, configurations, or project files outside `artifacts/research/`.
24
+ You are a research-only specialist. You perform deep analysis for task planning and write structured research notes. You do not make changes to source code, configurations, or project files outside the research root the orchestrator supplies.
24
25
 
25
26
  ## Output Location
26
27
 
27
- Write all research artifacts to `artifacts/research/` using the filename convention:
28
+ Write each research artifact to the research path the orchestrator supplies in the delegation prompt. There are two tracked research roots:
28
29
 
29
- - `artifacts/research/<timestamp>-<short-name>-research.md`
30
+ - Feature-associated research: `docs/features/<feature>/research/<timestamp>-<short-name>-research.md` (for example `docs/features/active/<feature>/research/<timestamp>-<short-name>-research.md`).
31
+ - One-off research not tied to a feature: `docs/research/<timestamp>-<short-name>-research.md`.
32
+
33
+ The orchestrator resolves which root to use from whether an active feature folder is in scope and passes the exact path in the delegation prompt; do not infer the feature folder independently. The filename convention `<timestamp>-<short-name>-research.md` is unchanged.
30
34
 
31
35
  ## Core Principles
32
36
 
@@ -67,7 +71,7 @@ Write all research artifacts to `artifacts/research/` using the filename convent
67
71
 
68
72
  ## Constraints
69
73
 
70
- - Write only to `artifacts/research/`. Do not modify source code or configurations.
74
+ - Write only to the orchestrator-supplied research path under `docs/features/<feature>/research/` (feature-associated) or `docs/research/` (one-off). Do not modify source code or configurations.
71
75
  - Ground all findings in verified evidence.
72
76
  - Keep discussion of non-selected approaches brief.
73
77
  - Do not claim nested worker delegation.
@@ -134,6 +134,53 @@ function Get-OutOfOrderPair {
134
134
  return $null
135
135
  }
136
136
 
137
+ function Test-StepHasPrefix {
138
+ [CmdletBinding()]
139
+ [OutputType([bool])]
140
+ param(
141
+ [Parameter(Mandatory)]
142
+ [string] $StepEntry,
143
+
144
+ [Parameter(Mandatory)]
145
+ [string] $Prefix
146
+ )
147
+
148
+ return $StepEntry -eq $Prefix -or $StepEntry.StartsWith("$Prefix" + '_') -or $StepEntry.StartsWith("$Prefix.") -or $StepEntry.StartsWith("$Prefix-")
149
+ }
150
+
151
+ function Get-MissingPrerequisiteForAdvancedStep {
152
+ [CmdletBinding()]
153
+ param(
154
+ [Parameter(Mandatory)]
155
+ [AllowEmptyCollection()]
156
+ [string[]] $CompletedSteps
157
+ )
158
+
159
+ $hasPromotion = $false
160
+ $hasPlanning = $false
161
+ foreach ($step in $CompletedSteps) {
162
+ if (Test-StepHasPrefix -StepEntry $step -Prefix 'S3_promotion') {
163
+ $hasPromotion = $true
164
+ }
165
+ if (Test-StepHasPrefix -StepEntry $step -Prefix 'S4_atomic_planning') {
166
+ $hasPlanning = $true
167
+ }
168
+ }
169
+
170
+ foreach ($step in $CompletedSteps) {
171
+ $index = Get-CanonicalStepIndex -StepEntry $step
172
+ if ($index -ge 5 -and (-not $hasPromotion -or -not $hasPlanning)) {
173
+ return [pscustomobject]@{
174
+ Step = $step
175
+ MissingPromotion = -not $hasPromotion
176
+ MissingPlanning = -not $hasPlanning
177
+ }
178
+ }
179
+ }
180
+
181
+ return $null
182
+ }
183
+
137
184
  function Test-IsCheckpointPath {
138
185
  [CmdletBinding()]
139
186
  [OutputType([bool])]
@@ -204,10 +251,6 @@ function Invoke-CheckpointMonotonicDecision {
204
251
  }
205
252
  }
206
253
 
207
- if ($steps.Count -lt 2) {
208
- return [ordered]@{ decision = 'allow' }
209
- }
210
-
211
254
  $rollbackHistory = $null
212
255
  if ($payload.PSObject.Properties.Name -contains 'rollback_history') {
213
256
  $rollbackHistory = $payload.rollback_history
@@ -216,15 +259,30 @@ function Invoke-CheckpointMonotonicDecision {
216
259
  return [ordered]@{ decision = 'allow' }
217
260
  }
218
261
 
219
- $pair = Get-OutOfOrderPair -CompletedSteps $steps
220
- if ($null -eq $pair) {
221
- return [ordered]@{ decision = 'allow' }
262
+ $pair = if ($steps.Count -ge 2) { Get-OutOfOrderPair -CompletedSteps $steps } else { $null }
263
+ if ($null -ne $pair) {
264
+ return [ordered]@{
265
+ decision = 'block'
266
+ reason = "CHECKPOINT_ORDER_BLOCKED: completed_steps lists '$($pair.EarlierEntry)' at position $($pair.EarlierPos) before '$($pair.LaterEntry)' at position $($pair.LaterPos), but the canonical orchestrator workflow requires the later step to follow the earlier one. Reorder completed_steps or, if a rollback occurred, record it in rollback_history."
267
+ }
222
268
  }
223
269
 
224
- return [ordered]@{
225
- decision = 'block'
226
- reason = "CHECKPOINT_ORDER_BLOCKED: completed_steps lists '$($pair.EarlierEntry)' at position $($pair.EarlierPos) before '$($pair.LaterEntry)' at position $($pair.LaterPos), but the canonical orchestrator workflow requires the later step to follow the earlier one. Reorder completed_steps or, if a rollback occurred, record it in rollback_history."
270
+ $missingPrerequisite = Get-MissingPrerequisiteForAdvancedStep -CompletedSteps $steps
271
+ if ($null -ne $missingPrerequisite) {
272
+ $missing = @()
273
+ if ($missingPrerequisite.MissingPromotion) {
274
+ $missing += 'S3_promotion'
275
+ }
276
+ if ($missingPrerequisite.MissingPlanning) {
277
+ $missing += 'S4_atomic_planning'
278
+ }
279
+ return [ordered]@{
280
+ decision = 'block'
281
+ reason = "CHECKPOINT_ORDER_BLOCKED: completed_steps lists '$($missingPrerequisite.Step)' before required prerequisite step(s): $($missing -join ', '). Record promotion and planning completion before implementation, review, PR, CI, or DONE steps."
282
+ }
227
283
  }
284
+
285
+ return [ordered]@{ decision = 'allow' }
228
286
  }
229
287
 
230
288
  # Guard allows dot-sourcing in tests without executing the entrypoint.
@@ -188,6 +188,28 @@ function Get-MissingCompletionEvidence {
188
188
  }
189
189
  }
190
190
 
191
+ if ($issueNum -eq '232') {
192
+ $prGate = $null
193
+ if ($null -ne $Payload -and ($Payload.PSObject.Properties.Name -contains 'pr_gate')) {
194
+ $prGate = $Payload.pr_gate
195
+ }
196
+ if ($null -eq $prGate -or $prGate -isnot [System.Management.Automation.PSCustomObject]) {
197
+ $missing += 'pr_gate (object with pr_number, pr_url, head_branch, and head_sha)'
198
+ }
199
+ else {
200
+ foreach ($field in @('pr_number', 'pr_url', 'head_branch', 'head_sha')) {
201
+ if (-not (Get-CheckpointStringValue -Payload $prGate -Name $field)) {
202
+ $missing += "pr_gate.$field"
203
+ }
204
+ }
205
+ $prHeadSha = Get-CheckpointStringValue -Payload $prGate -Name 'head_sha'
206
+ $ciHeadSha = Get-CheckpointStringValue -Payload $ciGate -Name 'head_sha'
207
+ if ($prHeadSha -and $ciHeadSha -and $prHeadSha -ne $ciHeadSha) {
208
+ $missing += 'ci_gate.head_sha matching pr_gate.head_sha'
209
+ }
210
+ }
211
+ }
212
+
191
213
  return [string[]]$missing
192
214
  }
193
215
 
@@ -249,9 +271,14 @@ function Invoke-CompletionConsistencyDecision {
249
271
  return [ordered]@{ decision = 'allow' }
250
272
  }
251
273
 
274
+ $issueContext = ''
275
+ if ((Get-CheckpointStringValue -Payload $payload -Name 'issue-num') -eq '232') {
276
+ $issueContext = ' Issue #232 requires pr_gate evidence and current-head ci_gate evidence.'
277
+ }
278
+
252
279
  return [ordered]@{
253
280
  decision = 'block'
254
- reason = "COMPLETION_CONSISTENCY_BLOCKED: the checkpoint asserts completion but is missing required completion evidence: $($missing -join ', '). A completion-asserting checkpoint must include a non-empty issue-num, a non-empty feature-folder, and a ci_gate object with conclusion == 'success' and a non-empty head_sha. Supply the missing evidence or remove the completion assertion."
281
+ reason = "COMPLETION_CONSISTENCY_BLOCKED: the checkpoint asserts completion but is missing required completion evidence: $($missing -join ', ').$issueContext A completion-asserting checkpoint must include a non-empty issue-num, a non-empty feature-folder, and a ci_gate object with conclusion == 'success' and a non-empty head_sha. Supply the missing evidence or remove the completion assertion."
255
282
  }
256
283
  }
257
284
 
@@ -17,12 +17,15 @@
17
17
  - artifacts/evidence/
18
18
  - artifacts/regression-testing/
19
19
  - artifacts/post-change/
20
+ - artifacts/research/
20
21
 
21
22
  All other paths pass through, including canonical evidence paths of the form
22
23
  <FEATURE>/evidence/<kind>/ and permitted artifacts/ sub-paths such as
23
- artifacts/orchestration/, artifacts/research/, artifacts/pr_context,
24
- artifacts/reviews/, artifacts/status/, artifacts/python/, artifacts/pester/,
25
- and artifacts/csharp/.
24
+ artifacts/orchestration/, artifacts/pr_context, artifacts/reviews/,
25
+ artifacts/status/, artifacts/python/, artifacts/pester/, and
26
+ artifacts/csharp/. Research output is no longer an artifacts/ sub-path; it
27
+ is written to the tracked roots docs/features/<feature>/research/
28
+ (feature-associated) or docs/research/ (one-off).
26
29
 
27
30
  If the file_path resolves to a forbidden prefix, the script writes a JSON response
28
31
  to stdout with 'decision': 'block' and exits with code 0 so Claude Code surfaces
@@ -61,7 +64,8 @@ function Test-EvidenceLocationForbidden {
61
64
  'artifacts/coverage/',
62
65
  'artifacts/evidence/',
63
66
  'artifacts/regression-testing/',
64
- 'artifacts/post-change/'
67
+ 'artifacts/post-change/',
68
+ 'artifacts/research/'
65
69
  )
66
70
 
67
71
  # Match the prefix either at the start of the string or after any directory separator,
@@ -133,18 +137,40 @@ function Invoke-EvidenceLocationDecision {
133
137
  return [ordered]@{ decision = 'allow' }
134
138
  }
135
139
 
140
+ function Invoke-EvidenceLocationEntryPoint {
141
+ <#
142
+ .SYNOPSIS
143
+ Runs the evidence-location decision and returns the process exit code.
144
+ .DESCRIPTION
145
+ Wraps the dispatch logic that the hook entry point performs so it can be
146
+ exercised by unit tests. On success it writes the compact JSON decision to
147
+ the output stream and returns 0. On a hard failure (malformed JSON) it
148
+ writes the error record and returns 1. This function does not call exit;
149
+ the thin entry-point wiring converts the returned code into a process exit.
150
+ .PARAMETER ToolInputRaw
151
+ The raw JSON string from $env:CLAUDE_TOOL_INPUT.
152
+ #>
153
+ [CmdletBinding()]
154
+ [OutputType([int])]
155
+ param(
156
+ [string] $ToolInputRaw = $env:CLAUDE_TOOL_INPUT
157
+ )
158
+
159
+ try {
160
+ $decision = Invoke-EvidenceLocationDecision -ToolInputRaw $ToolInputRaw
161
+ } catch {
162
+ Write-Error $_
163
+ return 1
164
+ }
165
+
166
+ $decision | ConvertTo-Json -Compress | Write-Output
167
+
168
+ return 0
169
+ }
170
+
136
171
  # Guard allows dot-sourcing in tests without executing the entrypoint.
137
172
  if ($MyInvocation.InvocationName -eq '.') {
138
173
  return
139
174
  }
140
175
 
141
- try {
142
- $decision = Invoke-EvidenceLocationDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
143
- } catch {
144
- Write-Error $_
145
- exit 1
146
- }
147
-
148
- $decision | ConvertTo-Json -Compress | Write-Output
149
-
150
- exit 0
176
+ exit (Invoke-EvidenceLocationEntryPoint)