@danmoisan/drm-copilot-mcp 1.0.10 → 1.0.13

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danmoisan/drm-copilot-mcp",
3
- "version": "1.0.10",
3
+ "version": "1.0.13",
4
4
  "description": "Stdio MCP server exposing drm-copilot repo-automation tools.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -63,7 +63,7 @@ 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
67
 
68
68
  ## Delegation Model
69
69
 
@@ -112,8 +112,9 @@ route's required names from `config/orchestration-routing.json`.
112
112
  Maintain `docs/features/epics/<epic-slug>/epic-status.md` as a human-readable projection of the
113
113
  epic checkpoint's `features[]` array, regenerated (not hand-edited) at epic kickoff, at every
114
114
  `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.
115
+ the `epic-orchestrate` skill's documentation-maintenance procedure. `epic.md` itself (the merged
116
+ manifest + narrative source of truth) is treated as static, human-authored input and is not
117
+ rewritten by you; `epic-status.md` is a generated projection only and is never hand-authored.
117
118
 
118
119
  ## Completion Requirements
119
120
 
@@ -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
@@ -22,6 +22,7 @@
22
22
  "mcp__drm-copilot__new_active_feature_folder",
23
23
  "mcp__drm-copilot__validate_orchestration_artifacts",
24
24
  "mcp__drm-copilot__resolve_atomic_plan_prompt",
25
+ "mcp__drm-copilot__render_subagent_tree",
25
26
  "Agent(atomic-planner)",
26
27
  "Agent(atomic-executor)",
27
28
  "Agent(feature-review)",
@@ -55,6 +56,8 @@
55
56
  "Skill(invoke-powershell-engineer *)",
56
57
  "Skill(translate-copilot-to-claude *)",
57
58
  "Skill(execute-hard-lock *)",
59
+ "Skill(identify-session-id *)",
60
+ "Skill(show-my-agent-tree *)",
58
61
  "Edit(/.claude/skills/execute-hard-lock/**)",
59
62
  "Edit(/.claude/skills/feature-review-workflow/**)",
60
63
  "Edit(/.claude/skills/csharp-qa-gate/**)"
@@ -71,6 +74,16 @@
71
74
  ]
72
75
  },
73
76
  "hooks": {
77
+ "SessionStart": [
78
+ {
79
+ "hooks": [
80
+ {
81
+ "type": "command",
82
+ "command": "pwsh -NoProfile -File .claude/hooks/persist-session-id.ps1"
83
+ }
84
+ ]
85
+ }
86
+ ],
74
87
  "PreToolUse": [
75
88
  {
76
89
  "matcher": "Bash",
@@ -22,10 +22,12 @@ Before proceeding, `epic-orchestrator` must:
22
22
 
23
23
  ## Epic Dependency Manifest
24
24
 
25
- The epic manifest is Markdown with YAML frontmatter, at
26
- `docs/features/epics/<epic-slug>/epic-plan.md`. The frontmatter carries the fields that must be
27
- parsed deterministically; the Markdown body below the frontmatter carries free-text epic
28
- narrative (goal, scope, non-goals) that is not machine-parsed.
25
+ The epic manifest is the YAML frontmatter of the single epic home
26
+ `docs/features/epics/<epic-slug>/epic.md`. `epic.md` is the merged source of truth: its
27
+ frontmatter carries the fields that must be parsed deterministically, and the Markdown body
28
+ below the frontmatter carries the single free-text epic narrative (goal, scope, non-goals,
29
+ shared design, decomposition) that is not machine-parsed. `epic.md` is also the source from
30
+ which the epic GitHub issue body is generated.
29
31
 
30
32
  Frontmatter schema:
31
33
 
@@ -34,19 +36,37 @@ Frontmatter schema:
34
36
  epic: <epic-slug>
35
37
  integration_branch: epic/<epic-slug>-integration
36
38
  created_at: <iso8601>
39
+ # Optional additive SAFe-style intent block. Omit the whole block when unused; when
40
+ # present, epic_type and business_outcome_hypothesis are required and
41
+ # leading_indicators / nfrs are optional lists of strings.
42
+ intent:
43
+ epic_type: <business | enabler>
44
+ business_outcome_hypothesis: <measurable outcome the epic is expected to move>
45
+ leading_indicators: [<early validation signal>, ...]
46
+ nfrs: [<non-functional requirement>, ...]
37
47
  features:
38
- - feature_folder: <feature-folder-basename>
39
- issue_num: <int>
40
- depends_on: [<feature-folder-basename>, ...]
48
+ - issue_num: <int>
49
+ feature_folder: <resolvable-hint-basename>
50
+ depends_on: [<upstream-issue_num>, ...]
41
51
  ---
42
52
  ```
43
53
 
44
- - `feature_folder` is the canonical identifier: the exact active-feature-folder basename,
45
- matching the vocabulary already used by the per-feature checkpoint's `feature-folder` field.
46
- - `depends_on` is an array of `feature_folder` values that must each already exist as another
47
- entry in `features[]`. A `depends_on` entry that does not resolve to a defined `feature_folder`,
48
- or a duplicate `feature_folder` value, is a malformed manifest and is rejected before epic
49
- kickoff as a synthetic Blocking finding `epic-orchestrator` does not guess.
54
+ - `issue_num` is the primary key: the stable GitHub issue number for the child feature. The
55
+ DAG is keyed by `issue_num`, so it does not drift when a child is promoted from `active/` to
56
+ `completed/`.
57
+ - `feature_folder` is a resolvable hint, not a stable identifier. It may resolve to a concrete
58
+ path under `docs/features/active/<basename>` or `docs/features/completed/<basename>`; a
59
+ lifecycle prefix is stripped to the basename during resolution.
60
+ - `depends_on` is an array of `issue_num` values (legacy manifests may still use
61
+ `feature_folder` basenames). Each entry must resolve — via the union index of the
62
+ `issue_num` set plus the `feature_folder` set — to another entry in `features[]`. A
63
+ `depends_on` entry that does not resolve, or a duplicate `feature_folder` value, is a
64
+ malformed manifest and is rejected before epic kickoff as a synthetic Blocking finding —
65
+ `epic-orchestrator` does not guess.
66
+ - The optional `intent` block is additive and presence-gated: when present it is validated
67
+ (`epic_type` in {business, enabler}, non-empty `business_outcome_hypothesis`, string-list
68
+ `leading_indicators` / `nfrs`); when absent, validation is byte-identical to a manifest
69
+ without it.
50
70
 
51
71
  ## Wave Assignment
52
72
 
@@ -137,10 +157,14 @@ When `epic-orchestrator` kicks off a feature with a non-empty `depends_on`, the
137
157
  includes one literal citation line per dependency, appended after the epic-mode kickoff line
138
158
  above:
139
159
 
140
- > `Upstream context for <feature_folder>: depends on <dep_feature_folder> (spec: docs/features/active/<dep_feature_folder>/spec.md — or docs/features/completed/<dep_feature_folder>/spec.md if already promoted to completed; plan: docs/features/active/<dep_feature_folder>/plan.<ts>.md; merged as PR #<dep_pr_number>, commit <dep_merge_commit_sha>, into <integration_branch>).`
160
+ > `Upstream context for <issue_num>: depends on <dep_issue_num> (spec: <dep_resolved_folder>/spec.md; plan: <dep_resolved_folder>/plan.<ts>.md; merged as PR #<dep_pr_number>, commit <dep_merge_commit_sha>, into <integration_branch>).`
141
161
 
142
- `epic-orchestrator` resolves the concrete `<dep_...>` values from its own checkpoint's
143
- `features[]` records for each dependency before emitting the line, so the dependent feature's own
162
+ `epic-orchestrator` resolves each dependency by its stable `issue_num` against its own
163
+ checkpoint's `features[]` records, and resolves `<dep_resolved_folder>` to the dependency's
164
+ concrete `feature_folder` path — under `docs/features/active/` or `docs/features/completed/`
165
+ depending on the dependency's current lifecycle state at emit time — before emitting the line.
166
+ Because the DAG is keyed by `issue_num`, no active→completed path-drift workaround is needed: the
167
+ concrete path is resolved from the checkpoint's current state, so the dependent feature's own
144
168
  `orchestrator`/`atomic-planner` is told exactly which upstream artifacts are relevant rather than
145
169
  being expected to rediscover prior design decisions from the diff alone.
146
170
 
@@ -207,12 +231,13 @@ child worktree) issues `git worktree remove <worktree_path>`, gated by
207
231
 
208
232
  ## Documentation Maintenance Boundaries
209
233
 
210
- `epic-plan.md` (the manifest) and `epic-status.md` (a separate, epic-orchestrator-maintained
211
- status document) are kept distinct. `epic-plan.md`'s frontmatter is the human-authored, largely
212
- static input; automatic epic decomposition is out of scope, so this file is not repeatedly
213
- rewritten. `epic-orchestrator` instead maintains
214
- `docs/features/epics/<epic-slug>/epic-status.md`, regenerated (not hand-edited) from the epic
215
- checkpoint at each of the following boundaries, not only at final completion:
234
+ `epic.md` (the merged manifest + narrative source of truth) and `epic-status.md` (a separate,
235
+ epic-orchestrator-maintained status document) are kept distinct. `epic.md`'s frontmatter is the
236
+ human-authored, largely static input; automatic epic decomposition is out of scope, so this
237
+ file is not repeatedly rewritten. `epic-status.md` is a generated projection only: it is
238
+ regenerated (never hand-authored) from the epic checkpoint and is never the source of the DAG.
239
+ `epic-orchestrator` maintains `docs/features/epics/<epic-slug>/epic-status.md`, regenerated from
240
+ the epic checkpoint at each of the following boundaries, not only at final completion:
216
241
 
217
242
  - Epic kickoff — initial status table seeded from the manifest (one row per feature: wave,
218
243
  status `not_started`).
@@ -229,12 +254,14 @@ checkpoint JSON remains the durable, machine-authoritative source.
229
254
  ## Epic-Level Checkpoint
230
255
 
231
256
  `artifacts/orchestration/epic-orchestrator-state.json` carries `objective`, `route_id: "epic"`,
232
- `epic_feature_folder`, `epic_manifest_path`, `epic_status_doc_path`, `integration_branch`,
257
+ `epic_feature_folder`, `epic_manifest_path` (which points at
258
+ `docs/features/epics/<epic-slug>/epic.md`), `epic_status_doc_path`, `integration_branch`,
233
259
  `completed_steps`, `next_step`, `last_updated`, `current_wave`, `waves[]`, `features[]`,
234
260
  `epic_merge_pr`, and the three receipt arrays (`delegation_receipts[]`, `skill_receipts[]`,
235
261
  `mcp_call_receipts[]`) — the full schema is defined in `spec.md` §6 of this feature. The
236
262
  `merge_status` enum is: `not_started`, `worktree_created`, `pr_open`, `ci_green`,
237
- `merge_conflict`, `blocked_conflict_loop_limit`, `merged`, `worktree_removed`.
263
+ `merge_conflict`, `blocked_conflict_loop_limit`, `merged`, `worktree_removed`. The optional
264
+ `intent` object (projection of the `epic.md` intent block) is validated presence-gated.
238
265
 
239
266
  Every field needed to re-derive state durably on resume (`worktree_path`, `branch_name`,
240
267
  `pr_number`, `merge_status`) is re-derivable from `git worktree list --porcelain`, `git branch`,
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: identify-session-id
3
+ description: Resolve the current Claude Code session id (the root transcript filename stem) without human input, using an ordered fallback chain, and report which source supplied it. Use before any workflow that needs the running session's id (for example show-my-agent-tree).
4
+ allowed-tools:
5
+ - Read
6
+ - Bash
7
+ ---
8
+
9
+ # Identify Session Id
10
+
11
+ Resolve the current session's id — the root transcript filename stem under
12
+ `~/.claude/projects/<encoded-workspace>/` — without asking the human. Try the
13
+ sources in order and stop at the first that yields a non-empty id. Always
14
+ report which source was used.
15
+
16
+ ## Resolution Order
17
+
18
+ 1. **Environment variable (primary).** Read `CLAUDE_SESSION_ID` from the
19
+ environment with a single command, e.g.:
20
+ - Bash: `printf '%s' "$CLAUDE_SESSION_ID"`
21
+ - pwsh: `pwsh -NoProfile -Command 'Write-Output $env:CLAUDE_SESSION_ID'`
22
+
23
+ This variable is provisioned by the SessionStart hook
24
+ `.claude/hooks/persist-session-id.ps1` through the `CLAUDE_ENV_FILE`
25
+ channel. If it is non-empty, use it and report source `env:CLAUDE_SESSION_ID`.
26
+
27
+ 2. **State file (secondary).** Read `.claude/state/current-session-id`
28
+ (written by the same hook when `CLAUDE_ENV_FILE` is unset). If it exists and
29
+ is non-empty, use its trimmed contents and report source
30
+ `.claude/state/current-session-id`.
31
+
32
+ 3. **Newest transcript (tertiary heuristic).** List the root `*.jsonl` files
33
+ directly under `~/.claude/projects/<encodeWorkspacePath(cwd)>/` (the encoding
34
+ replaces every path separator and `:` with `-`), pick the one with the
35
+ newest modification time, and use its filename stem (without `.jsonl`).
36
+ Report source `newest-mtime transcript` and note that this heuristic can
37
+ pick the wrong sibling only when multiple concurrent sessions share one
38
+ workspace path.
39
+
40
+ ## Output
41
+
42
+ Report the resolved session id and the source that supplied it, for example:
43
+ `session_id = <id> (source: env:CLAUDE_SESSION_ID)`. If every source is empty,
44
+ state that the session id could not be resolved and which sources were checked.
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: show-my-agent-tree
3
+ description: Render the current session's subagent call tree and print it in the assistant reply. Use when the user asks to "show my agent tree" or otherwise wants to see the subagent hierarchy of the running session.
4
+ allowed-tools:
5
+ - Read
6
+ - Bash
7
+ - mcp__drm-copilot__render_subagent_tree
8
+ ---
9
+
10
+ # Show My Agent Tree
11
+
12
+ Render the subagent call tree for the current session and print it directly in
13
+ the assistant reply. This works identically in normal turns and in `/btw`
14
+ side-conversations and needs no VS Code host API.
15
+
16
+ ## Flow
17
+
18
+ 1. **Resolve the session id.** Follow the `identify-session-id` skill to obtain
19
+ the current session id and note which source supplied it.
20
+
21
+ 2. **Call the MCP tool.** Invoke `mcp__drm-copilot__render_subagent_tree` with:
22
+ - `session_id`: the id resolved in step 1.
23
+ - `workspace_root`: an explicit absolute path to the current workspace root
24
+ (do not rely on the tool's default; pass it explicitly).
25
+
26
+ On success the tool returns `ok: true`, a `summary` naming the session id
27
+ and resolved transcript path, and a `rendered_tree` string.
28
+
29
+ 3. **Print the tree.** Output the `rendered_tree` value in the assistant reply
30
+ inside a fenced code block, so the hierarchy is legible. Include the
31
+ `summary` line above it for context.
32
+
33
+ ## Error Handling
34
+
35
+ - If the tool returns `ok: false`, report the `summary` verbatim. An unknown
36
+ session id names the searched directories; a malformed session id names the
37
+ validation rule (`^[0-9A-Za-z-]{8,64}$`).
38
+ - If `identify-session-id` cannot resolve an id, report that and do not call
39
+ the tool.
@@ -28,6 +28,7 @@
28
28
  ".claude/hooks/enforce-pr-author-skill.ps1",
29
29
  ".claude/hooks/enforce-prd-feature-before-planner.ps1",
30
30
  ".claude/hooks/enforce-promotion-mcp-only.ps1",
31
+ ".claude/hooks/persist-session-id.ps1",
31
32
  ".claude/hooks/validate-bash.ps1",
32
33
  ".claude/hooks/validate-executor-output.ps1",
33
34
  ".claude/hooks/validate-feature-review-coverage.ps1",
@@ -55,6 +56,7 @@
55
56
  ".claude/skills/fill-feature-docs/SKILL.md",
56
57
  ".claude/skills/human-exception-runbook/example.runbook.md",
57
58
  ".claude/skills/human-exception-runbook/SKILL.md",
59
+ ".claude/skills/identify-session-id/SKILL.md",
58
60
  ".claude/skills/make-skill-template/SKILL.md",
59
61
  ".claude/skills/orchestrate/SKILL.md",
60
62
  ".claude/skills/policy-audit-template-usage/SKILL.md",
@@ -67,6 +69,7 @@
67
69
  ".claude/skills/review-epic/SKILL.md",
68
70
  ".claude/skills/review-feature/SKILL.md",
69
71
  ".claude/skills/review-staged/SKILL.md",
72
+ ".claude/skills/show-my-agent-tree/SKILL.md",
70
73
  ".claude/skills/skill-canonical-location-audit/SKILL.md",
71
74
  ".claude/skills/translate-copilot-to-claude/SKILL.md",
72
75
  ".claude/skills/update-status/SKILL.md",
@@ -0,0 +1,20 @@
1
+ <!--
2
+ GENERATED FILE — DO NOT HAND-AUTHOR.
3
+
4
+ epic-status.md is a generated projection of the epic checkpoint
5
+ (artifacts/orchestration/epic-orchestrator-state.json). It is regenerated by
6
+ the epic-orchestrator agent at each lifecycle boundary (epic kickoff, every
7
+ feature merge_status change, each wave transition, and final integration
8
+ merge). It is never the source of the dependency DAG and must never be edited
9
+ by hand — the manifest in epic.md and the epic checkpoint JSON are the
10
+ authoritative sources.
11
+ -->
12
+
13
+ # <epic-name> - Epic Status (generated)
14
+
15
+ This document is regenerated from the epic checkpoint and must not be
16
+ hand-authored. Any manual edit will be overwritten on the next regeneration.
17
+
18
+ | feature_folder | issue_num | wave_number | merge_status | pr_url | merge_commit_sha |
19
+ | --- | --- | --- | --- | --- | --- |
20
+ | _(populated on epic kickoff from the manifest, then updated in place)_ | | | | | |
@@ -0,0 +1,79 @@
1
+ ---
2
+ # Epic manifest (source of truth).
3
+ #
4
+ # This YAML frontmatter is the machine-readable manifest for the epic: the
5
+ # dependency DAG (parsed deterministically by the epic-orchestrator agent) and an
6
+ # optional SAFe-style intent block. The Markdown body below the frontmatter is the
7
+ # single human-authored narrative and is not machine-parsed.
8
+ #
9
+ # The DAG is keyed by stable `issue_num`. `feature_folder` is a resolvable hint
10
+ # that may point into docs/features/active/<folder> OR docs/features/completed/<folder>;
11
+ # it is not a stable identifier and changes when a child is promoted.
12
+ epic: <epic-slug>
13
+ integration_branch: epic/<epic-slug>-integration
14
+ created_at: <iso8601>
15
+
16
+ # Optional additive SAFe-style intent block. Omit the whole `intent` block when
17
+ # not used; when present, `epic_type` and `business_outcome_hypothesis` are
18
+ # required, and `leading_indicators` / `nfrs` are optional lists of strings.
19
+ intent:
20
+ epic_type: <business | enabler>
21
+ business_outcome_hypothesis: <the measurable outcome this epic is expected to move>
22
+ leading_indicators:
23
+ - <early signal that the hypothesis is being validated>
24
+ nfrs:
25
+ - <non-functional requirement the epic must satisfy>
26
+
27
+ # Manifest DAG. Primary key is `issue_num`. `depends_on` lists the `issue_num`
28
+ # values of upstream siblings (each must be another entry in `features[]`).
29
+ features:
30
+ - issue_num: <int>
31
+ feature_folder: <resolvable-hint-basename>
32
+ depends_on: []
33
+ - issue_num: <int>
34
+ feature_folder: <resolvable-hint-basename>
35
+ depends_on: [<upstream-issue_num>]
36
+ ---
37
+
38
+ # <epic-name> - Epic
39
+
40
+ - Issue: #<tracking-issue>
41
+ - Owner: <name>
42
+ - Last Updated: YYYY-MM-DD
43
+
44
+ ## Goal
45
+
46
+ State the epic objective and the measurable outcomes. Keep it user/impact
47
+ oriented, not implementation detail. This is the source from which the epic
48
+ GitHub issue body is generated.
49
+
50
+ ## Scope
51
+
52
+ Enumerate what is in scope for this epic. Reference the child features by their
53
+ `issue_num` so the narrative and the manifest DAG stay aligned.
54
+
55
+ ## Non-Goals
56
+
57
+ List what is explicitly out of scope so child features and reviewers do not
58
+ scope-creep into adjacent work.
59
+
60
+ ## Shared Design
61
+
62
+ Capture the cross-cutting design decisions every child feature must honor:
63
+
64
+ - Shared behaviors/algorithms that must stay aligned across children.
65
+ - Determinism/performance/compatibility guarantees.
66
+ - Data/artifact locations, formats, or tooling expectations.
67
+ - Quality gates (tests/lint/type-checks) required across all children.
68
+
69
+ ## Decomposition
70
+
71
+ Describe the child features and their ordering. Each child keeps its own git
72
+ branch/worktree and its own independent active/ -> completed/ lifecycle; this
73
+ section is the human-readable projection of the `features[]` DAG above.
74
+
75
+ - <Child feature A> (Issue #<id>) - wave 0
76
+ - <Child feature B> (Issue #<id>) - depends on #<id>
77
+
78
+ `epic-status.md` in this same directory is a generated projection of the epic
79
+ checkpoint; it is never the source of the DAG and is never hand-authored.
@@ -44,6 +44,10 @@
44
44
  '.claude/hooks/enforce-pr-author-skill.ps1'
45
45
  '.claude/hooks/validate-orchestrator-output.ps1'
46
46
  '.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1'
47
+ # Issue #334 added this SessionStart hook that persists the current session id;
48
+ # measured here so the new production hook is not excluded from coverage. The
49
+ # test suite dot-sources the file (guarded body) so line attribution is valid.
50
+ '.claude/hooks/persist-session-id.ps1'
47
51
  )
48
52
  ExcludedPath = @(
49
53
  '.claude/hooks/validate-feature-review-coverage.ps1' # Feature-review wrapper around repository evidence; not deterministic in normal unit-test execution.