@danmoisan/drm-copilot-mcp 1.0.11 → 1.0.14

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 (21) hide show
  1. package/out/mcp-server.js +585 -123
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +28 -3
  4. package/resources/claude-customizations/.claude/agents/epic-planner.md +121 -0
  5. package/resources/claude-customizations/.claude/agents/orchestrator.md +2 -2
  6. package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +246 -0
  7. package/resources/claude-customizations/.claude/hooks/persist-session-id.ps1 +153 -0
  8. package/resources/claude-customizations/.claude/settings.json +22 -2
  9. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +58 -24
  10. package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +189 -0
  11. package/resources/claude-customizations/.claude/skills/epic-run/SKILL.md +38 -0
  12. package/resources/claude-customizations/.claude/skills/identify-session-id/SKILL.md +44 -0
  13. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +21 -2
  14. package/resources/claude-customizations/.claude/skills/show-my-agent-tree/SKILL.md +39 -0
  15. package/resources/claude-customizations/pack-manifests/core.json +7 -0
  16. package/resources/config/orchestration-routing.json +21 -0
  17. package/resources/feature-templates/epic/epic-status.md +20 -0
  18. package/resources/feature-templates/epic/epic.md +79 -0
  19. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +4 -0
  20. package/resources/templates/new-claude-worktree-session.ps1 +58 -9
  21. package/resources/feature-templates/epic/initiative.md +0 -43
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danmoisan/drm-copilot-mcp",
3
- "version": "1.0.11",
3
+ "version": "1.0.14",
4
4
  "description": "Stdio MCP server exposing drm-copilot repo-automation tools.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -63,7 +63,31 @@ On every invocation:
63
63
  `git branch`, and `gh pr view --json state,mergedAt,headRefOid` per the `epic-orchestrate`
64
64
  skill's resume procedure, not from in-memory notifications alone).
65
65
  5. If no checkpoint exists or the objective is new, begin from manifest parsing
66
- (`docs/features/epics/<epic-slug>/epic-plan.md`).
66
+ (`docs/features/epics/<epic-slug>/epic.md`).
67
+
68
+ ## Invocation Origin
69
+
70
+ You are invoked from the main session — via `/epic-orchestrate <epic-manifest-path>`, via
71
+ `/epic-run <epic-slug>` (which replays the kickoff artifact `epic-planner` emitted), or by a
72
+ direct prompt. You delegate to `Agent(orchestrator)`, so an invocation that itself
73
+ originates from an `orchestrator` agent would nest `orchestrator` inside its own delegation
74
+ chain; the PreToolUse hook `.claude/hooks/enforce-epic-invocation-origin.ps1` denies any
75
+ `Agent(epic-orchestrator)` or `Agent(epic-planner)` call whose calling agent is `orchestrator`
76
+ (`EPIC_INVOCATION_ORIGIN_BLOCKED`).
77
+
78
+ ## Prepared-Epic Execution (epic-planner Handoff)
79
+
80
+ When the epic was prepared by `epic-planner` (the integration branch already exists and
81
+ `docs/features/epics/<epic-slug>/epic-kickoff.md` is present), each child feature folder already
82
+ contains its issue, research, `spec.md`, `user-story.md`, an approved atomic plan, and a
83
+ recorded preflight clearance. In that case:
84
+
85
+ 1. Do not recreate the integration branch; fetch and reuse it.
86
+ 2. Each child `Agent(orchestrator)` delegation prompt cites the child's committed `plan-path`
87
+ and instructs the run to resume at atomic execution from that plan rather than re-running
88
+ promotion, research, or planning.
89
+ 3. The wave barrier, merge-on-green fan-in, and final integration-to-`main` PR proceed
90
+ unchanged per the `epic-orchestrate` skill.
67
91
 
68
92
  ## Delegation Model
69
93
 
@@ -112,8 +136,9 @@ route's required names from `config/orchestration-routing.json`.
112
136
  Maintain `docs/features/epics/<epic-slug>/epic-status.md` as a human-readable projection of the
113
137
  epic checkpoint's `features[]` array, regenerated (not hand-edited) at epic kickoff, at every
114
138
  `merge_status` transition, at every wave transition, and at final integration-PR completion, per
115
- the `epic-orchestrate` skill's documentation-maintenance procedure. `epic-plan.md` itself (the
116
- manifest) is treated as static, human-authored input and is not rewritten by you.
139
+ the `epic-orchestrate` skill's documentation-maintenance procedure. `epic.md` itself (the merged
140
+ manifest + narrative source of truth) is treated as static, human-authored input and is not
141
+ rewritten by you; `epic-status.md` is a generated projection only and is never hand-authored.
117
142
 
118
143
  ## Completion Requirements
119
144
 
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: epic-planner
3
+ model: opus
4
+ description: Epic-scale planning agent that fully scopes an epic before any execution. It decomposes the objective into child features, computes the dependency graph and wave layering, assesses per-feature complexity, drives per-feature preparation (promotion, research, spec/user-story, atomic plan, preflight clearance) through parallel preparation-mode Agent(orchestrator) delegations, commits all planning outputs to the epic integration branch, and emits the epic-orchestrator kickoff prompt artifact. Performs no atomic execution. Must not be invoked from an orchestrator agent (enforced by enforce-epic-invocation-origin.ps1).
5
+ tools:
6
+ - "Agent(orchestrator)"
7
+ - Read
8
+ - Grep
9
+ - Glob
10
+ - "Write(docs/features/epics/**)"
11
+ - "Edit(docs/features/epics/**)"
12
+ - "Write(artifacts/orchestration/**)"
13
+ - "Edit(artifacts/orchestration/**)"
14
+ - "Bash(git *)"
15
+ - "Bash(gh *)"
16
+ - "mcp__drm-copilot__validate_orchestration_artifacts"
17
+ skills:
18
+ - policy-compliance-order
19
+ - epic-plan
20
+ - epic-orchestrate
21
+ - feature-promotion-lifecycle
22
+ - atomic-plan-contract
23
+ - evidence-and-timestamp-conventions
24
+ memory: project
25
+ ---
26
+
27
+ # Epic Planner Agent
28
+
29
+ You are the epic-scale planning agent. You take an epic-sized objective from raw intent to a
30
+ fully prepared, execution-ready state without performing any execution. You are distinct from
31
+ `.claude/agents/epic-orchestrator.md`: `epic-orchestrator` schedules and executes an already
32
+ planned epic; you produce that plan. Your terminal deliverable is a committed epic integration
33
+ branch containing the epic manifest, one prepared feature folder per child feature (issue,
34
+ research, spec, user-story, approved atomic plan, preflight clearance), and a kickoff prompt
35
+ artifact the user replays to launch `epic-orchestrator` on command.
36
+
37
+ ## Skill
38
+
39
+ Apply the `epic-plan` skill (`.claude/skills/epic-plan/SKILL.md`) as the canonical procedure for
40
+ the epic-worthiness gate, decomposition, wave computation, complexity assessment, the
41
+ integration-branch lifecycle, preparation-mode child delegation, fan-in of prepared work, the
42
+ kickoff-prompt artifact, and checkpoint persistence. This agent frames the *who* and *when*; the
43
+ skill documents the *how* in full. The epic manifest schema and wave-assignment formula are
44
+ defined once in the `epic-orchestrate` skill and are consumed here, not redefined.
45
+
46
+ ## Invocation Origin
47
+
48
+ You are invoked from the main session only. You delegate to `Agent(orchestrator)`, so an
49
+ invocation that itself originates from an `orchestrator` agent would nest `orchestrator` inside
50
+ its own delegation chain; the PreToolUse hook
51
+ `.claude/hooks/enforce-epic-invocation-origin.ps1` denies any `Agent(epic-planner)` or
52
+ `Agent(epic-orchestrator)` call whose calling agent is `orchestrator`
53
+ (`EPIC_INVOCATION_ORIGIN_BLOCKED`).
54
+
55
+ ## Startup Protocol
56
+
57
+ On every invocation:
58
+
59
+ 1. Read `CLAUDE.md` for repository tone policy and architecture context.
60
+ 2. Read applicable `.claude/rules/` files for languages in scope.
61
+ 3. Read `artifacts/orchestration/epic-planner-state.json` to check for existing planning
62
+ checkpoint state.
63
+ 4. If a valid checkpoint exists with a matching objective, resume from the recorded `next_step`,
64
+ re-deriving durable ground truth from the integration branch, the epic manifest, and the
65
+ prepared feature folders rather than from in-memory notifications alone.
66
+ 5. If no checkpoint exists or the objective is new, begin from the epic-worthiness gate.
67
+
68
+ ## Epic-Worthiness Gate
69
+
70
+ Your first planning action is to assess whether the objective warrants an epic at all, per the
71
+ `epic-plan` skill's criteria (independent child-feature count and per-feature change budget).
72
+ When the objective does not warrant an epic, report that finding to the user with the rationale
73
+ and offer to delegate the work directly to a single `Agent(orchestrator)` run as one feature.
74
+ Do not build epic scaffolding for a single-feature objective; proceed to epic planning only when
75
+ the gate passes or the user directs you to.
76
+
77
+ ## Delegation Model
78
+
79
+ You delegate exclusively through `Agent(orchestrator)`, one delegation per child feature, each
80
+ carrying the preparation-mode kickoff line defined in the `epic-plan` skill. Each child
81
+ `orchestrator` runs promotion, research, feature documents, atomic planning, and preflight
82
+ clearance under `route_id: preparation`, then stops before any execution. You do not delegate
83
+ directly to `atomic-planner`, `atomic-executor`, `task-researcher`, or `prd-feature`; those
84
+ delegations belong to each child's own `orchestrator` instance. You never delegate to
85
+ `Agent(epic-orchestrator)`; executing the plan is the user's explicit next command.
86
+
87
+ ## Integration Branch
88
+
89
+ All planning outputs are committed to the epic integration branch
90
+ (`epic/<epic-slug>-integration`, created off `origin/main` if absent), so `epic-orchestrator`
91
+ can later execute against the exact planned state. The branch lifecycle and fan-in procedure are
92
+ defined in the `epic-plan` skill.
93
+
94
+ ## Kickoff Prompt Artifact
95
+
96
+ At completion you write the epic-orchestrator kickoff prompt to
97
+ `artifacts/orchestration/epic-kickoff-<epic-slug>.md` and commit a durable copy at
98
+ `docs/features/epics/<epic-slug>/epic-kickoff.md` (the `artifacts/` tree is gitignored). The
99
+ artifact contains the exact prompt the user replays to launch `Agent(epic-orchestrator)` against
100
+ the prepared epic, per the template in the `epic-plan` skill.
101
+
102
+ ## Checkpoint Persistence
103
+
104
+ Update `artifacts/orchestration/epic-planner-state.json` after every completed step with:
105
+ `objective`, `epic_feature_folder`, `epic_manifest_path`, `integration_branch`,
106
+ `epic_worthiness` (`{verdict, rationale}`), `features[]` (per-feature `issue_num`,
107
+ `feature_folder`, `depends_on`, `wave`, `complexity_band`, `preparation_status`, `plan_path`,
108
+ `preflight_status`), `kickoff_prompt_path`, `completed_steps`, `next_step`, and `last_updated`.
109
+
110
+ ## Completion Requirements
111
+
112
+ Do not report completion until:
113
+
114
+ 1. The epic manifest at `docs/features/epics/<epic-slug>/epic.md` parses against the
115
+ `epic-orchestrate` schema with a cycle-free dependency graph.
116
+ 2. Every child feature has an issue, an active feature folder, research, `spec.md`,
117
+ `user-story.md`, an approved atomic plan, and a recorded `PREFLIGHT: ALL CLEAR`.
118
+ 3. All planning outputs are committed and pushed on the integration branch.
119
+ 4. The kickoff prompt artifact exists at both paths listed above.
120
+ 5. The final report lists each feature's `plan-path:` and preflight status, plus the kickoff
121
+ artifact path.
@@ -4,7 +4,6 @@ model: opus
4
4
  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.
5
5
  tools:
6
6
  - "Agent(atomic-planner,atomic-executor,feature-review,task-researcher,prd-feature,staged-review,epic-review,status-updater,pr-author,commit-message,human-exception-runbook,python-typed-engineer,powershell-typed-engineer,csharp-typed-engineer,typescript-engineer)"
7
- - "Agent(epic-orchestrator)"
8
7
  - Read
9
8
  - Grep
10
9
  - Glob
@@ -75,7 +74,8 @@ The first action is always to estimate the change budget by identifying likely a
75
74
 
76
75
  - **Small path** (1–3 production files + corresponding tests): promotion, active folder, minimal plan, implementation, QC, small-audit review.
77
76
  - **Large path** (4+ production files or cross-cutting changes): scope, promotion, research, spec, atomic planning, atomic execution, feature review.
78
- - **Epic path**: the objective names or references an epic manifest (`docs/features/epics/<epic-slug>/epic-plan.md`) or explicitly requests multi-feature/epic orchestration. On this outcome the orchestrator delegates to `Agent(epic-orchestrator)` with the manifest path, instead of running change-budget/small/large routing itself.
77
+ - **Epic outcome**: the objective names or references an epic manifest (`docs/features/epics/<epic-slug>/epic.md`) or explicitly requests multi-feature/epic orchestration. On this outcome the orchestrator does not route the work itself and does not delegate it: it halts and reports that epic-scale objectives are invoked from the main session — `Agent(epic-planner)` for planning, `Agent(epic-orchestrator)` for execution. The orchestrator never invokes either epic agent; both delegate to `Agent(orchestrator)`, so an orchestrator-originated invocation would nest orchestrator inside its own delegation chain, and the PreToolUse hook `enforce-epic-invocation-origin.ps1` denies it (`EPIC_INVOCATION_ORIGIN_BLOCKED`).
78
+ - **Preparation mode (epic planning)**: the delegation prompt carries the literal marker `Preparation mode: true` (issued by `epic-planner` per the `epic-plan` skill). The orchestrator selects `route_id: preparation` and runs promotion, research, feature documents, atomic planning, and the atomic-executor preflight only, per `## Preparation Mode` in `.claude/skills/orchestrate/SKILL.md`. It stops after `PREFLIGHT: ALL CLEAR` with `next_step: "S5_atomic_execution"`, out-of-scope step statuses `not-applicable`, and no completion assertion; atomic execution, PR authoring, and CI monitoring are out of scope for the run.
79
79
 
80
80
  ## Delegation Model
81
81
 
@@ -0,0 +1,246 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Pre-tool-use hook that blocks epic-planner and epic-orchestrator delegations
4
+ originating from an orchestrator agent.
5
+
6
+ .DESCRIPTION
7
+ Invoked by the Claude Code PreToolUse hook on the "Agent" matcher before any
8
+ Agent (Task) call runs. Activates only when the delegation target
9
+ subagent_type is 'epic-planner' or 'epic-orchestrator'.
10
+
11
+ Caller identity resolution:
12
+ - The full PreToolUse payload (CLAUDE_HOOK_INPUT) carries a top-level
13
+ 'agent_type' field only when the tool call is made from inside a
14
+ subagent context. A main-thread call carries no 'agent_type'.
15
+ - The Agent tool input (CLAUDE_TOOL_INPUT, or the payload's 'tool_input'
16
+ object) carries the delegation target 'subagent_type'.
17
+
18
+ Decision procedure:
19
+ 1. Resolve the target subagent_type from CLAUDE_TOOL_INPUT, falling back
20
+ to the payload's tool_input object. A non-epic target allows.
21
+ 2. Resolve the calling agent_type from the payload. An absent or empty
22
+ agent_type indicates a main-thread invocation, which allows.
23
+ 3. Deny with reason EPIC_INVOCATION_ORIGIN_BLOCKED when the calling
24
+ agent_type is exactly 'orchestrator'. Both epic agents delegate to
25
+ Agent(orchestrator); an orchestrator-originated invocation would nest
26
+ orchestrator inside its own delegation chain.
27
+
28
+ .NOTES
29
+ Compatible with PowerShell 7+. No external module dependencies. Read-only
30
+ validation gate; malformed JSON in either payload throws so the entrypoint
31
+ exits 1.
32
+ #>
33
+ [CmdletBinding()]
34
+ param()
35
+
36
+ $script:GatedSubagentTypes = @('epic-planner', 'epic-orchestrator')
37
+ $script:ProhibitedCallerAgentType = 'orchestrator'
38
+
39
+ function Get-EpicInvocationOriginAllowDecision {
40
+ [CmdletBinding()]
41
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
42
+ param()
43
+
44
+ return [ordered]@{
45
+ hookSpecificOutput = [ordered]@{
46
+ hookEventName = 'PreToolUse'
47
+ permissionDecision = 'allow'
48
+ }
49
+ }
50
+ }
51
+
52
+ function Get-EpicInvocationOriginBlockDecision {
53
+ [CmdletBinding()]
54
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
55
+ param(
56
+ [Parameter(Mandatory)]
57
+ [string] $Reason
58
+ )
59
+
60
+ return [ordered]@{
61
+ hookSpecificOutput = [ordered]@{
62
+ hookEventName = 'PreToolUse'
63
+ permissionDecision = 'deny'
64
+ permissionDecisionReason = $Reason
65
+ }
66
+ }
67
+ }
68
+
69
+ function ConvertFrom-EpicInvocationOriginPayload {
70
+ <#
71
+ .SYNOPSIS
72
+ Parses a raw JSON payload string, returning $null for a blank payload
73
+ and throwing a named error for malformed JSON.
74
+ .PARAMETER RawPayload
75
+ The raw JSON text under evaluation.
76
+ .PARAMETER PayloadName
77
+ The payload's environment-variable name, used in the error message.
78
+ .OUTPUTS
79
+ System.Object or $null
80
+ #>
81
+ [CmdletBinding()]
82
+ param(
83
+ [AllowNull()]
84
+ [AllowEmptyString()]
85
+ [string] $RawPayload,
86
+
87
+ [Parameter(Mandatory)]
88
+ [string] $PayloadName
89
+ )
90
+
91
+ if ([string]::IsNullOrWhiteSpace($RawPayload)) {
92
+ return $null
93
+ }
94
+
95
+ try {
96
+ return $RawPayload | ConvertFrom-Json -ErrorAction Stop
97
+ } catch {
98
+ throw "enforce-epic-invocation-origin hook received malformed JSON in ${PayloadName}: $_"
99
+ }
100
+ }
101
+
102
+ function Get-EpicInvocationOriginTargetSubagent {
103
+ <#
104
+ .SYNOPSIS
105
+ Resolves the delegation target subagent_type from the tool input,
106
+ falling back to the full payload's tool_input object.
107
+ .PARAMETER ToolInput
108
+ Parsed CLAUDE_TOOL_INPUT object, or $null when absent.
109
+ .PARAMETER HookInput
110
+ Parsed CLAUDE_HOOK_INPUT object, or $null when absent.
111
+ .OUTPUTS
112
+ System.String or $null
113
+ #>
114
+ [CmdletBinding()]
115
+ [OutputType([string])]
116
+ param(
117
+ [AllowNull()]
118
+ $ToolInput,
119
+
120
+ [AllowNull()]
121
+ $HookInput
122
+ )
123
+
124
+ if ($null -ne $ToolInput -and
125
+ (@($ToolInput.PSObject.Properties.Name) -contains 'subagent_type') -and
126
+ -not [string]::IsNullOrWhiteSpace([string]$ToolInput.subagent_type)) {
127
+ return [string]$ToolInput.subagent_type
128
+ }
129
+
130
+ if ($null -ne $HookInput -and
131
+ (@($HookInput.PSObject.Properties.Name) -contains 'tool_input')) {
132
+ $nested = $HookInput.tool_input
133
+ if ($null -ne $nested -and
134
+ (@($nested.PSObject.Properties.Name) -contains 'subagent_type') -and
135
+ -not [string]::IsNullOrWhiteSpace([string]$nested.subagent_type)) {
136
+ return [string]$nested.subagent_type
137
+ }
138
+ }
139
+
140
+ return $null
141
+ }
142
+
143
+ function Get-EpicInvocationOriginCallerAgentType {
144
+ <#
145
+ .SYNOPSIS
146
+ Resolves the calling agent_type from the full hook payload. Returns
147
+ $null for a main-thread invocation (no agent_type field).
148
+ .PARAMETER HookInput
149
+ Parsed CLAUDE_HOOK_INPUT object, or $null when absent.
150
+ .OUTPUTS
151
+ System.String or $null
152
+ #>
153
+ [CmdletBinding()]
154
+ [OutputType([string])]
155
+ param(
156
+ [AllowNull()]
157
+ $HookInput
158
+ )
159
+
160
+ if ($null -eq $HookInput) {
161
+ return $null
162
+ }
163
+ if (@($HookInput.PSObject.Properties.Name) -notcontains 'agent_type') {
164
+ return $null
165
+ }
166
+
167
+ $agentType = [string]$HookInput.agent_type
168
+ if ([string]::IsNullOrWhiteSpace($agentType)) {
169
+ return $null
170
+ }
171
+ return $agentType
172
+ }
173
+
174
+ function Invoke-EpicInvocationOriginDecision {
175
+ <#
176
+ .SYNOPSIS
177
+ Parses the hook payloads and returns an allow-or-block decision.
178
+ .PARAMETER HookInputRaw
179
+ The raw full PreToolUse payload JSON supplied via CLAUDE_HOOK_INPUT.
180
+ .PARAMETER ToolInputRaw
181
+ The raw Agent tool input JSON supplied via CLAUDE_TOOL_INPUT.
182
+ .OUTPUTS
183
+ System.Collections.Specialized.OrderedDictionary
184
+ #>
185
+ [CmdletBinding()]
186
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
187
+ param(
188
+ [AllowNull()]
189
+ [AllowEmptyString()]
190
+ [string] $HookInputRaw,
191
+
192
+ [AllowNull()]
193
+ [AllowEmptyString()]
194
+ [string] $ToolInputRaw
195
+ )
196
+
197
+ # The tool input identifies the delegation target; a non-epic target is
198
+ # outside this hook's scope, so the hook input is not parsed for it.
199
+ $toolInput = ConvertFrom-EpicInvocationOriginPayload -RawPayload $ToolInputRaw -PayloadName 'CLAUDE_TOOL_INPUT'
200
+ $hookInputParsed = $false
201
+ $hookInput = $null
202
+
203
+ $target = Get-EpicInvocationOriginTargetSubagent -ToolInput $toolInput -HookInput $hookInput
204
+ if (-not $target -and -not [string]::IsNullOrWhiteSpace($HookInputRaw)) {
205
+ # Fallback: some harness surfaces supply only the full payload, whose
206
+ # tool_input object carries the target subagent_type.
207
+ $hookInput = ConvertFrom-EpicInvocationOriginPayload -RawPayload $HookInputRaw -PayloadName 'CLAUDE_HOOK_INPUT'
208
+ $hookInputParsed = $true
209
+ $target = Get-EpicInvocationOriginTargetSubagent -ToolInput $toolInput -HookInput $hookInput
210
+ }
211
+
212
+ if (-not $target -or $script:GatedSubagentTypes -notcontains $target) {
213
+ return Get-EpicInvocationOriginAllowDecision
214
+ }
215
+
216
+ if (-not $hookInputParsed) {
217
+ $hookInput = ConvertFrom-EpicInvocationOriginPayload -RawPayload $HookInputRaw -PayloadName 'CLAUDE_HOOK_INPUT'
218
+ }
219
+
220
+ # An absent agent_type marks a main-thread invocation, which is the
221
+ # intended entry point for both epic agents; only an orchestrator-context
222
+ # invocation is prohibited.
223
+ $caller = Get-EpicInvocationOriginCallerAgentType -HookInput $hookInput
224
+ if ($caller -ne $script:ProhibitedCallerAgentType) {
225
+ return Get-EpicInvocationOriginAllowDecision
226
+ }
227
+
228
+ $reason = "EPIC_INVOCATION_ORIGIN_BLOCKED: Agent($target) must not be invoked from an orchestrator agent. Both epic-planner and epic-orchestrator delegate to Agent(orchestrator), so an orchestrator-originated invocation would nest orchestrator inside its own delegation chain. Invoke $target from the main session instead."
229
+ return Get-EpicInvocationOriginBlockDecision -Reason $reason
230
+ }
231
+
232
+ # Guard allows dot-sourcing in tests without executing the entrypoint.
233
+ if ($MyInvocation.InvocationName -eq '.') {
234
+ return
235
+ }
236
+
237
+ try {
238
+ $decision = Invoke-EpicInvocationOriginDecision -HookInputRaw $env:CLAUDE_HOOK_INPUT -ToolInputRaw $env:CLAUDE_TOOL_INPUT
239
+ } catch {
240
+ Write-Error $_
241
+ exit 1
242
+ }
243
+
244
+ $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
245
+
246
+ exit 0
@@ -0,0 +1,153 @@
1
+ <#
2
+ .SYNOPSIS
3
+ SessionStart hook that persists the current Claude Code session id.
4
+
5
+ .DESCRIPTION
6
+ Invoked by the Claude Code SessionStart hook event (registered in
7
+ .claude/settings.json). Reads the hook payload JSON from standard input,
8
+ falling back to the CLAUDE_HOOK_INPUT environment variable (the existing
9
+ SubagentStop-hook precedent), and extracts the 'session_id' field.
10
+
11
+ Persistence channel:
12
+ - When CLAUDE_ENV_FILE is set, appends the line
13
+ 'CLAUDE_SESSION_ID=<id>' to that file. Variables persisted there are
14
+ exported to subsequent Bash tool commands in the session, which is how
15
+ this hook provisions the otherwise-unset CLAUDE_SESSION_ID variable.
16
+ - When CLAUDE_ENV_FILE is unset, writes the id to
17
+ .claude/state/current-session-id instead.
18
+
19
+ On malformed or empty input (missing/blank payload, unparseable JSON, or an
20
+ absent/blank session_id) the hook performs no write. It always exits 0 so a
21
+ SessionStart hook never blocks session start.
22
+
23
+ .NOTES
24
+ Compatible with PowerShell 7+. Does not use Invoke-Expression.
25
+ #>
26
+ [CmdletBinding()]
27
+ param()
28
+
29
+ function Get-PersistSessionIdDecision {
30
+ [CmdletBinding()]
31
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
32
+ param(
33
+ [string] $RawPayload,
34
+
35
+ [string] $EnvFilePath,
36
+
37
+ [Parameter(Mandatory)]
38
+ [string] $StateFilePath
39
+ )
40
+
41
+ $none = [ordered]@{ action = 'none'; sessionId = ''; path = '' }
42
+
43
+ if ([string]::IsNullOrWhiteSpace($RawPayload)) {
44
+ return $none
45
+ }
46
+
47
+ try {
48
+ $payload = $RawPayload | ConvertFrom-Json -ErrorAction Stop
49
+ } catch {
50
+ Write-Verbose "persist-session-id: ignoring unparseable payload: $($_.Exception.Message)"
51
+ return $none
52
+ }
53
+
54
+ $sessionId = $null
55
+ if ($null -ne $payload -and $payload.PSObject.Properties.Name -contains 'session_id') {
56
+ $sessionId = [string]$payload.session_id
57
+ }
58
+
59
+ if ([string]::IsNullOrWhiteSpace($sessionId)) {
60
+ return $none
61
+ }
62
+
63
+ if (-not [string]::IsNullOrWhiteSpace($EnvFilePath)) {
64
+ return [ordered]@{ action = 'env-file'; sessionId = $sessionId; path = $EnvFilePath }
65
+ }
66
+
67
+ return [ordered]@{ action = 'state-file'; sessionId = $sessionId; path = $StateFilePath }
68
+ }
69
+
70
+ function Invoke-PersistSessionIdHook {
71
+ [CmdletBinding()]
72
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
73
+ param(
74
+ [string] $RawPayload,
75
+
76
+ [string] $EnvFilePath,
77
+
78
+ [Parameter(Mandatory)]
79
+ [string] $StateFilePath,
80
+
81
+ [scriptblock] $AppendLine = {
82
+ param([string] $Path, [string] $Line)
83
+ Add-Content -Path $Path -Value $Line -Encoding utf8
84
+ },
85
+
86
+ [scriptblock] $WriteStateFile = {
87
+ param([string] $Path, [string] $Content)
88
+ Set-Content -Path $Path -Value $Content -Encoding utf8 -NoNewline
89
+ },
90
+
91
+ [scriptblock] $EnsureDirectory = {
92
+ param([string] $Path)
93
+ if (-not (Test-Path -Path $Path)) {
94
+ New-Item -ItemType Directory -Path $Path -Force | Out-Null
95
+ }
96
+ }
97
+ )
98
+
99
+ $decision = Get-PersistSessionIdDecision -RawPayload $RawPayload -EnvFilePath $EnvFilePath -StateFilePath $StateFilePath
100
+
101
+ switch ($decision.action) {
102
+ 'env-file' {
103
+ & $AppendLine $decision.path ("CLAUDE_SESSION_ID={0}" -f $decision.sessionId)
104
+ }
105
+ 'state-file' {
106
+ $stateDir = Split-Path -Path $decision.path -Parent
107
+ if ($stateDir) {
108
+ & $EnsureDirectory $stateDir
109
+ }
110
+ & $WriteStateFile $decision.path $decision.sessionId
111
+ }
112
+ default {
113
+ # 'none': malformed or empty input; perform no write.
114
+ }
115
+ }
116
+
117
+ return $decision
118
+ }
119
+
120
+ function Read-HookPayload {
121
+ [CmdletBinding()]
122
+ [OutputType([string])]
123
+ param(
124
+ [scriptblock] $ReadStandardInput = { [Console]::In.ReadToEnd() },
125
+
126
+ [AllowNull()]
127
+ [AllowEmptyString()]
128
+ [string] $FallbackPayload = $env:CLAUDE_HOOK_INPUT
129
+ )
130
+
131
+ $raw = ''
132
+ try {
133
+ $raw = & $ReadStandardInput
134
+ } catch {
135
+ $raw = ''
136
+ }
137
+
138
+ if ([string]::IsNullOrWhiteSpace($raw)) {
139
+ return $FallbackPayload
140
+ }
141
+
142
+ return $raw
143
+ }
144
+
145
+ if ($MyInvocation.InvocationName -eq '.') {
146
+ return
147
+ }
148
+
149
+ $rawPayload = Read-HookPayload
150
+ $stateFilePath = Join-Path -Path (Get-Location).Path -ChildPath '.claude/state/current-session-id'
151
+ Invoke-PersistSessionIdHook -RawPayload $rawPayload -EnvFilePath $env:CLAUDE_ENV_FILE -StateFilePath $stateFilePath | Out-Null
152
+
153
+ exit 0
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
3
- "agent": "orchestrator",
4
3
  "permissions": {
5
4
  "allow": [
6
5
  "Bash(git *)",
@@ -22,6 +21,7 @@
22
21
  "mcp__drm-copilot__new_active_feature_folder",
23
22
  "mcp__drm-copilot__validate_orchestration_artifacts",
24
23
  "mcp__drm-copilot__resolve_atomic_plan_prompt",
24
+ "mcp__drm-copilot__render_subagent_tree",
25
25
  "Agent(atomic-planner)",
26
26
  "Agent(atomic-executor)",
27
27
  "Agent(feature-review)",
@@ -38,7 +38,11 @@
38
38
  "Agent(csharp-typed-engineer)",
39
39
  "Agent(typescript-engineer)",
40
40
  "Agent(epic-orchestrator)",
41
+ "Agent(epic-planner)",
41
42
  "Skill(orchestrate *)",
43
+ "Skill(epic-plan *)",
44
+ "Skill(epic-orchestrate *)",
45
+ "Skill(epic-run *)",
42
46
  "Skill(commit-message *)",
43
47
  "Skill(pr-author *)",
44
48
  "Skill(research-issue *)",
@@ -55,6 +59,8 @@
55
59
  "Skill(invoke-powershell-engineer *)",
56
60
  "Skill(translate-copilot-to-claude *)",
57
61
  "Skill(execute-hard-lock *)",
62
+ "Skill(identify-session-id *)",
63
+ "Skill(show-my-agent-tree *)",
58
64
  "Edit(/.claude/skills/execute-hard-lock/**)",
59
65
  "Edit(/.claude/skills/feature-review-workflow/**)",
60
66
  "Edit(/.claude/skills/csharp-qa-gate/**)"
@@ -71,6 +77,16 @@
71
77
  ]
72
78
  },
73
79
  "hooks": {
80
+ "SessionStart": [
81
+ {
82
+ "hooks": [
83
+ {
84
+ "type": "command",
85
+ "command": "pwsh -NoProfile -File .claude/hooks/persist-session-id.ps1"
86
+ }
87
+ ]
88
+ }
89
+ ],
74
90
  "PreToolUse": [
75
91
  {
76
92
  "matcher": "Bash",
@@ -160,13 +176,17 @@
160
176
  {
161
177
  "type": "command",
162
178
  "command": "pwsh -NoProfile -File .claude/hooks/enforce-model-routing-receipt.ps1"
179
+ },
180
+ {
181
+ "type": "command",
182
+ "command": "pwsh -NoProfile -File .claude/hooks/enforce-epic-invocation-origin.ps1"
163
183
  }
164
184
  ]
165
185
  }
166
186
  ],
167
187
  "SubagentStop": [
168
188
  {
169
- "matcher": "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|orchestrator|epic-orchestrator",
189
+ "matcher": "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|orchestrator|epic-orchestrator|epic-planner",
170
190
  "hooks": [
171
191
  {
172
192
  "type": "command",