bmad-module-ultracode-goal 0.1.0
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/.claude-plugin/marketplace.json +20 -0
- package/.gitattributes +16 -0
- package/.nvmrc +1 -0
- package/LICENSE +27 -0
- package/README.md +200 -0
- package/docs/_internal/RELEASING.md +101 -0
- package/docs/_internal/STABILITY.md +62 -0
- package/docs/architecture.md +77 -0
- package/docs/assets/ucg-logo.svg +73 -0
- package/docs/gate-model.md +109 -0
- package/docs/getting-started.md +51 -0
- package/docs/health-check.md +61 -0
- package/docs/how-it-works.md +73 -0
- package/docs/index.md +25 -0
- package/docs/parallel-mode.md +33 -0
- package/docs/troubleshooting.md +56 -0
- package/docs/why-ultracode-goal.md +34 -0
- package/package.json +100 -0
- package/skills/.gitkeep +0 -0
- package/skills/module.yaml +12 -0
- package/skills/ultracode-goal/SKILL.md +75 -0
- package/skills/ultracode-goal/assets/execute-epic.workflow.js +208 -0
- package/skills/ultracode-goal/customize.toml +47 -0
- package/skills/ultracode-goal/references/define-done.md +41 -0
- package/skills/ultracode-goal/references/execute.md +90 -0
- package/skills/ultracode-goal/references/finalize.md +64 -0
- package/skills/ultracode-goal/references/gate.md +70 -0
- package/skills/ultracode-goal/references/health-check.md +332 -0
- package/skills/ultracode-goal/references/ingest-and-scope.md +46 -0
- package/skills/ultracode-goal/references/preflight.md +100 -0
- package/skills/ultracode-goal/scripts/gate_eval.py +280 -0
- package/skills/ultracode-goal/scripts/health_check_fp.py +196 -0
- package/skills/ultracode-goal/scripts/hooks/budget_stop.py +162 -0
- package/skills/ultracode-goal/scripts/hooks/guard_pretooluse.py +174 -0
- package/skills/ultracode-goal/scripts/preflight_check.py +399 -0
- package/tools/cli/commands/install.js +36 -0
- package/tools/cli/commands/status.js +161 -0
- package/tools/cli/commands/uninstall.js +175 -0
- package/tools/cli/commands/update.js +65 -0
- package/tools/cli/lib/ide-skills.js +218 -0
- package/tools/cli/lib/installer.js +226 -0
- package/tools/cli/lib/manifest.js +100 -0
- package/tools/cli/lib/platform-codes.yaml +223 -0
- package/tools/cli/lib/ui.js +353 -0
- package/tools/cli/lib/version-check.js +86 -0
- package/tools/cli/ucg-cli.js +45 -0
- package/tools/ucg-npx-wrapper.js +36 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ultracode-goal
|
|
3
|
+
description: Runs a BMAD Epic autonomously to a machine-checked Definition-of-Done. Use when the user requests to 'run an epic autonomously', 'execute this epic', 'ultracode goal', or 'autonomously deliver the epic'.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# UltraCode Goal
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
This skill autonomously delivers a BMAD Epic to a **machine-checked** Definition-of-Done. Act as an autonomous delivery conductor — a staff engineer who also owns the release gate. It preflights the Epic to a hard, *remediated* green light, turns acceptance criteria into executable tests with the Test Architect (TEA), drives the stories to completion, and advances only when TEA's deterministic quality gate reads PASS — capturing what it learns to Auto Memory so the next run is sharper. Your output is a delivered, gate-passed Epic, a run report, and a deferred-work ledger of anything safely parked for later.
|
|
11
|
+
|
|
12
|
+
Module `bmad-module-ultracode-goal`. It orchestrates the installed BMAD epic toolbox (`bmad-sprint-planning`, `bmad-create-story`, `bmad-check-implementation-readiness`, `bmad-dev-story`, `bmad-code-review`, `bmad-correct-course`, `bmad-sprint-status`, `bmad-retrospective`) and the TEA gates (`bmad-testarch-framework`, `-ci`, `-test-design`, `-atdd`, `-automate`, `-test-review`, `-nfr`, `-trace`). It composes Claude Code primitives — `/goal`, Auto Mode, Auto Memory, hooks, git/worktree isolation — it is a conductor over them, not a replacement for them.
|
|
13
|
+
|
|
14
|
+
## Conventions
|
|
15
|
+
|
|
16
|
+
- Bare paths (e.g. `references/preflight.md`) resolve from the skill root.
|
|
17
|
+
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
|
|
18
|
+
- `{project-root}`-prefixed paths resolve from the project working directory.
|
|
19
|
+
- `{skill-name}` resolves to the skill directory's basename.
|
|
20
|
+
|
|
21
|
+
## On Activation
|
|
22
|
+
|
|
23
|
+
### Step 1: Resolve the Workflow Block
|
|
24
|
+
|
|
25
|
+
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
|
|
26
|
+
|
|
27
|
+
If it fails, resolve the `workflow` block yourself by reading these three files in base → team → user order (scalars override, tables deep-merge, arrays append): `{skill-root}/customize.toml`, `{project-root}/_bmad/custom/{skill-name}.toml`, `{project-root}/_bmad/custom/{skill-name}.user.toml`. Read every customized value below as `{workflow.<name>}`.
|
|
28
|
+
|
|
29
|
+
Load config from `{project-root}/_bmad/config.yaml` and `config.user.yaml` (root + `bmm` section for `{planning_artifacts}`); fall back to `{project-root}/_bmad/bmm/config.yaml`. If config is missing, note that `bmad-bmb-setup` is available and continue with defaults. Load `{workflow.persistent_facts}` and greet in `{communication_language}`.
|
|
30
|
+
|
|
31
|
+
**Run modes.** Profile defaults to **production** (full TEA gates); `--light` runs the trace gate only. Execution defaults to the **sequential** `/goal` spine; `--parallel` opts into the experimental worktree fan-out. `-H` is headless. `--yes` skips Stage 1's open-floor invite and the launch confirm (the launch briefing still prints) — it **never** skips the hard preflight gate. `--retro` runs the close-out retrospective: interactive runs offer it at Epic close anyway, but headless runs it only when `--retro` was passed.
|
|
32
|
+
|
|
33
|
+
**Resume.** The workspace is this skill's run folder holding `.decision-log.md`. If one exists for the Epic, surface it with its last session date and offer to resume — the log recovers full state regardless of compaction. Otherwise create it at intent and append a session heading. On resume, re-enter Execute at the first story whose last logged gate verdict is not advance; advanced stories are not re-run; re-assert (do not rebuild) the Epic branch, hooks, and allowlist before continuing.
|
|
34
|
+
|
|
35
|
+
## Non-negotiables
|
|
36
|
+
|
|
37
|
+
These exist because the documented mechanics make the intuitive shortcut wrong. Do not optimize them away.
|
|
38
|
+
|
|
39
|
+
- **Completion is decided by `scripts/gate_eval.py` reading TEA's `gate-decision.json` — never your own judgment, and never the `/goal` evaluator alone.** That evaluator only sees the transcript; it cannot read the gate file. The JSON is the truth.
|
|
40
|
+
- **Launch the unattended run only when `scripts/preflight_check.py` returns green *after the remediation pass* (intervention budget == 0) and ultracode + Auto Mode are on.** Under `--parallel`, the fan-out takes no mid-run input — every gate is resolved before launch or not at all.
|
|
41
|
+
- **Only non-gate-blocking work defers** to `{workflow.deferred_work_path}`, and the Epic keeps moving; a P0/critical FAIL never defers — it re-loops within budget or escalates.
|
|
42
|
+
- **Rollback is git** — Epic branch off `{workflow.epic_branch_prefix}`, one commit per green story, worktree isolation. `/rewind` checkpoints miss Bash changes; do not rely on them.
|
|
43
|
+
- **Invariants live in PreToolUse hooks**, auto-merged into `.claude/settings.local.json` at preflight — not in memory, which is context, not enforcement.
|
|
44
|
+
|
|
45
|
+
## Stages
|
|
46
|
+
|
|
47
|
+
| # | Stage | Purpose | Location |
|
|
48
|
+
|---|-------|---------|----------|
|
|
49
|
+
| 1 | Ingest & Scope | Resolve the Epic + artifacts; confirm profile (production default / `--light`) | `references/ingest-and-scope.md` |
|
|
50
|
+
| 2 | Preflight | Auto-remediate ambers, then hard-gate on red; git branch, hooks, allowlist | `references/preflight.md` |
|
|
51
|
+
| 3 | Define Done | TEA test-design + per-story ATDD → executable acceptance tests | `references/define-done.md` |
|
|
52
|
+
| 4 | Execute | Sequential `/goal` spine (default) or `--parallel` worktree fan-out | `references/execute.md` |
|
|
53
|
+
| 5 | Gate | `gate_eval.py` verdict → advance / defer / reloop / escalate | `references/gate.md` |
|
|
54
|
+
| 6 | Finalize | Auto Memory capture, optional retrospective, decision-log audit, report, workflow health check | `references/finalize.md` |
|
|
55
|
+
|
|
56
|
+
Run the stages in order; each routes by the testable conditions stated in its file. The decision log is canonical memory — record scope, the preflight verdict, every gate outcome, and every deferral as you go.
|
|
57
|
+
|
|
58
|
+
## Headless
|
|
59
|
+
|
|
60
|
+
With `-H`, run non-interactively: infer scope, default to **production** (unless `--light`), never prompt — a secret that cannot be resolved becomes a red blocker, not a question — and let `.decision-log.md` absorb every assumption.
|
|
61
|
+
|
|
62
|
+
**One emit shape, every exit point.** Whether the run completes (Stage 6), or blocks early at ingest (Stage 1, e.g. epic unresolved), at preflight (Stage 2), or at a story escalation (Stage 6), emit this exact object — all five keys always present, `null` when that artifact was not produced, and `reason` carrying a one-line cause **only** when blocked:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{"status": "complete|blocked",
|
|
66
|
+
"skill": "ultracode-goal",
|
|
67
|
+
"decision_log": "<path to this run's .decision-log.md>",
|
|
68
|
+
"report": "<path to run-report.md, or null>",
|
|
69
|
+
"deferred_work": "<path to {workflow.deferred_work_path}, or null>",
|
|
70
|
+
"reason": "<one line, present only when blocked>"}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
An automator parses one schema regardless of where the run stopped; a blocked-before-report exit returns `report` and `deferred_work` as `null` rather than omitting them.
|
|
74
|
+
|
|
75
|
+
Runs that reach Stage 6 (complete or escalated) also run the terminal workflow health check before emitting — in headless it queues findings locally and never blocks the emit. Runs that block at Stage 1 or Stage 2 do not: there is no executed workflow surface to audit, and inventing findings there would be fabrication.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// EXPERIMENTAL — ultracode-goal --parallel worktree fan-out.
|
|
2
|
+
//
|
|
3
|
+
// This is the additive, opt-in parallel execution path (decision D4'/D7). The DEFAULT
|
|
4
|
+
// path is the sequential /goal spine in references/execute.md; this script only runs when
|
|
5
|
+
// the operator passes --parallel. It shares the SAME truth sources as the sequential path:
|
|
6
|
+
// the deterministic gate is scripts/gate_eval.py reading TEA's gate-decision.json (never
|
|
7
|
+
// the model, never the transcript-only /goal evaluator), and the same PreToolUse + Stop
|
|
8
|
+
// hooks merged into .claude/settings.local.json at preflight enforce the invariants.
|
|
9
|
+
//
|
|
10
|
+
// WHY EXPERIMENTAL: this path leans on workflow<->skill interplay the Claude Code docs
|
|
11
|
+
// leave under-specified — whether worktree-isolated agents can reliably drive BMAD/TEA
|
|
12
|
+
// skills, and how concurrent stories interact with the single per-repo auto-memory
|
|
13
|
+
// directory (all worktrees of one git repo SHARE one ~/.claude/projects/<repo>/memory/,
|
|
14
|
+
// a known cross-writer collision risk). There is no mid-run user input here: every gate
|
|
15
|
+
// is resolved before launch or not at all. Validate empirically before trusting it
|
|
16
|
+
// unattended; on any doubt, fall back to the sequential spine.
|
|
17
|
+
//
|
|
18
|
+
// Available workflow primitives (top-level await + return supported):
|
|
19
|
+
// meta — pure literal exported below (no runtime values)
|
|
20
|
+
// phase(title) — declare the current phase for the run log/UI
|
|
21
|
+
// log(msg) — append to the run log
|
|
22
|
+
// parallel([fns]) — run zero-arg thunks concurrently, returns results array
|
|
23
|
+
// pipeline(items, ...stageFns) — map items through ordered stages
|
|
24
|
+
// agent(prompt, { label, phase, schema, isolation }) — spawn a subagent; isolation:
|
|
25
|
+
// 'worktree' runs it in .claude/worktrees/ on its own branch so
|
|
26
|
+
// concurrent stories never overwrite each other's working tree.
|
|
27
|
+
|
|
28
|
+
export const meta = {
|
|
29
|
+
name: 'ultracode-goal-execute',
|
|
30
|
+
description:
|
|
31
|
+
'EXPERIMENTAL --parallel path: fan a BMAD Epic out across worktree-isolated per-story agents (dev-story un-skipping ATDD, tests/lint/build, production test-review + code-review, commit at green), gate each story with gate_eval.py, then run one epic-level trace gate. Shares the sequential spine gate + hooks.',
|
|
32
|
+
phases: [
|
|
33
|
+
{ title: 'Stories', detail: 'Per-story worktree-isolated dev -> verify -> review -> commit -> gate_eval, batched to max_concurrency' },
|
|
34
|
+
{ title: 'EpicGate', detail: 'Epic-level TEA trace gate over the merged result via gate_eval.py' },
|
|
35
|
+
],
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// args (supplied by the skill when it invokes this workflow):
|
|
40
|
+
// epic — Epic id/string
|
|
41
|
+
// stories[] — ordered list of in-scope stories (id + path/context)
|
|
42
|
+
// profile — 'production' (full TEA + reviews) | 'light' (trace gate only)
|
|
43
|
+
// paths.{trace_output, deferred_work, implementation_artifacts, tea_config, skill_root}
|
|
44
|
+
// skill_root is the resolved {skill-root} — the skill threads it in
|
|
45
|
+
// because this .js is run by the dynamic-workflow runtime, which has
|
|
46
|
+
// no {skill-root} resolver, so the gate_eval.py path must arrive absolute.
|
|
47
|
+
// max_concurrency — cap on simultaneous worktree agents; the GOVERNING value is the
|
|
48
|
+
// passed parallel_max_concurrency ({workflow.parallel_max_concurrency},
|
|
49
|
+
// customize.toml default 8). The literal 4 below is only a fallback when
|
|
50
|
+
// the skill invokes this workflow without supplying max_concurrency.
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
const {
|
|
53
|
+
epic,
|
|
54
|
+
stories = [],
|
|
55
|
+
profile = 'production',
|
|
56
|
+
paths = {},
|
|
57
|
+
max_concurrency = 4,
|
|
58
|
+
} = args
|
|
59
|
+
|
|
60
|
+
const traceOutput = paths.trace_output
|
|
61
|
+
const deferredWork = paths.deferred_work
|
|
62
|
+
const implArtifacts = paths.implementation_artifacts
|
|
63
|
+
const teaConfig = paths.tea_config
|
|
64
|
+
// Resolved {skill-root}; substituted into the gate_eval.py prompt strings below so the
|
|
65
|
+
// spawned worktree agent receives an absolute script path, not an unresolved brace token.
|
|
66
|
+
const skillRoot = paths.skill_root
|
|
67
|
+
const isProduction = profile === 'production'
|
|
68
|
+
const concurrency = Math.max(1, Number(max_concurrency) || 1)
|
|
69
|
+
|
|
70
|
+
// Verdict mapping is OWNED by gate_eval.py — this schema only captures what the agent
|
|
71
|
+
// reports back from running it. The agent MUST NOT recompute TEA thresholds or decide the
|
|
72
|
+
// verdict itself; it runs the script and returns the script's stdout fields verbatim.
|
|
73
|
+
const STORY_SCHEMA = {
|
|
74
|
+
type: 'object',
|
|
75
|
+
additionalProperties: false,
|
|
76
|
+
required: ['story', 'verdict', 'gate_status', 'committed'],
|
|
77
|
+
properties: {
|
|
78
|
+
story: { type: 'string', description: 'the story id' },
|
|
79
|
+
verdict: { type: 'string', enum: ['advance', 'defer', 'reloop', 'escalate'], description: 'verbatim gate_eval.py verdict' },
|
|
80
|
+
gate_status: { type: 'string', description: 'verbatim gate_eval.py gate_status (PASS|CONCERNS|FAIL|WAIVED|NOT_EVALUATED)' },
|
|
81
|
+
committed: { type: 'boolean', description: 'true only if tests/lint/build were green AND a single commit was made on the story branch' },
|
|
82
|
+
branch: { type: 'string', description: 'the worktree branch this story was developed on' },
|
|
83
|
+
evidence: { type: 'string', description: 'short summary of printed test/lint/build evidence' },
|
|
84
|
+
deferred: {
|
|
85
|
+
type: 'array',
|
|
86
|
+
description: 'non-gate-blocking items to append to the deferred-work ledger; NEVER a P0/critical FAIL',
|
|
87
|
+
items: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
additionalProperties: false,
|
|
90
|
+
required: ['source', 'severity', 'reason', 'suggested_action'],
|
|
91
|
+
properties: {
|
|
92
|
+
source: { type: 'string', enum: ['gate', 'code-review', 'nfr', 'decision'] },
|
|
93
|
+
severity: { type: 'string', enum: ['low', 'med', 'high'] },
|
|
94
|
+
reason: { type: 'string' },
|
|
95
|
+
suggested_action: { type: 'string' },
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
reasons: { type: 'array', items: { type: 'string' }, description: 'gate_eval.py reasons[]' },
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const EPICGATE_SCHEMA = {
|
|
104
|
+
type: 'object',
|
|
105
|
+
additionalProperties: false,
|
|
106
|
+
required: ['verdict', 'gate_status'],
|
|
107
|
+
properties: {
|
|
108
|
+
verdict: { type: 'string', enum: ['advance', 'defer', 'reloop', 'escalate'] },
|
|
109
|
+
gate_status: { type: 'string' },
|
|
110
|
+
reasons: { type: 'array', items: { type: 'string' } },
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function storyId(s, i) {
|
|
115
|
+
if (typeof s === 'string') return s
|
|
116
|
+
return s.id || s.story || s.path || `story-${i + 1}`
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function storyContext(s) {
|
|
120
|
+
if (typeof s === 'string') return s
|
|
121
|
+
return [s.path ? `path: ${s.path}` : '', s.context || s.title || ''].filter(Boolean).join('\n')
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// One worktree-isolated agent per story. The pipeline below batches these so no more than
|
|
125
|
+
// `concurrency` run at once; within a story the steps are strictly ordered.
|
|
126
|
+
function runStory(s, i) {
|
|
127
|
+
const id = storyId(s, i)
|
|
128
|
+
const reviewStep = isProduction
|
|
129
|
+
? `4. PRODUCTION reviews (skip under --light): run bmad-testarch-test-review then bmad-code-review on this story's diff. P0/critical findings are NOT deferrable — they must be fixed in this worktree before commit (re-loop within {workflow.max_turns_per_story}). Non-critical findings go into the "deferred" array.\n`
|
|
130
|
+
: `4. LIGHT profile: skip test-review/code-review (trace gate only).\n`
|
|
131
|
+
|
|
132
|
+
return agent(
|
|
133
|
+
`You are delivering ONE story of BMAD Epic "${epic}" inside an ISOLATED git worktree (isolation: worktree). ` +
|
|
134
|
+
`Your working tree is private — other stories run concurrently in their own worktrees on their own branches, so commit only your own story's changes. ` +
|
|
135
|
+
`This is the EXPERIMENTAL --parallel path; there is NO mid-run human input. Profile: ${profile}.\n\n` +
|
|
136
|
+
`STORY: ${id}\n${storyContext(s)}\n\n` +
|
|
137
|
+
`Execute these steps IN ORDER, printing real evidence at each step (the printed evidence keeps the run auditable, but the GATE is gate_eval.py, not your judgment):\n` +
|
|
138
|
+
`1. bmad-create-story (Create mode only — never Resume) to fully context the story file if it is not already complete.\n` +
|
|
139
|
+
`2. bmad-dev-story to implement it, UN-SKIPPING the red-phase ATDD acceptance tests for this story (remove the test.skip markers that define the Definition-of-Done) so they actually run.\n` +
|
|
140
|
+
`3. Run and PRINT the project's test, lint, and build commands. Once all three are green, write the tests-ran marker file ${implArtifacts}/.tests-ran-${id} (exact name the PreToolUse hook checks, with ULTRACODE_STORY_ID=${id} for this worktree) so the hook permits the commit. Do NOT proceed to commit unless all three are green.\n` +
|
|
141
|
+
reviewStep +
|
|
142
|
+
`5. COMMIT AT GREEN: make exactly ONE commit on this worktree's branch capturing the story. The PreToolUse hook will deny the commit on a protected branch or without the tests-ran marker — that is expected enforcement, satisfy it rather than bypass it. Set committed=true only if the commit succeeded on green.\n` +
|
|
143
|
+
`6. GATE THIS STORY — run EXACTLY:\n` +
|
|
144
|
+
` uv run ${skillRoot}/scripts/gate_eval.py --trace-output ${traceOutput} --profile ${profile}` +
|
|
145
|
+
(isProduction ? ` --nfr <nfr-assessment.md> --test-review <test-review.md>` : ``) + `\n` +
|
|
146
|
+
` (in production, first run bmad-testarch-automate to backfill coverage, then bmad-testarch-trace, then bmad-testarch-nfr so the gate file exists before you read it). ` +
|
|
147
|
+
`Return the script's verdict, gate_status, and reasons VERBATIM. Do not recompute TEA thresholds and do not override the verdict.\n\n` +
|
|
148
|
+
`INVARIANT: a P0/critical FAIL NEVER goes into "deferred" — fix-and-reloop within budget, or let the verdict be reloop/escalate. Only non-gate-blocking items (CONCERNS, non-critical review/NFR findings, parked decisions) belong in "deferred".\n\n` +
|
|
149
|
+
`Return ONLY the JSON object matching the schema. No prose outside it.`,
|
|
150
|
+
{ label: `story:${id}`, phase: 'Stories', schema: STORY_SCHEMA, isolation: 'worktree' }
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Phase 1: per-story fan-out, batched to honor max_concurrency.
|
|
156
|
+
// pipeline() maps each concurrency-sized chunk through one stage that fans the chunk out
|
|
157
|
+
// with parallel(); chunks run sequentially, stories within a chunk run in parallel.
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
phase('Stories')
|
|
160
|
+
log(`Epic ${epic}: ${stories.length} story(ies), profile=${profile}, concurrency=${concurrency} (EXPERIMENTAL --parallel)`)
|
|
161
|
+
|
|
162
|
+
const indexed = stories.map((s, i) => ({ s, i }))
|
|
163
|
+
const batches = []
|
|
164
|
+
for (let b = 0; b < indexed.length; b += concurrency) {
|
|
165
|
+
batches.push(indexed.slice(b, b + concurrency))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const batchResults = await pipeline(
|
|
169
|
+
batches,
|
|
170
|
+
(batch) => parallel(batch.map(({ s, i }) => () => runStory(s, i)))
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
const perStory = batchResults.flat().filter(Boolean)
|
|
174
|
+
log(`Stories complete: ${perStory.map((r) => `${r.story}=${r.verdict}/${r.gate_status}`).join(', ')}`)
|
|
175
|
+
|
|
176
|
+
// Collect non-gate-blocking deferrals for the ledger (the skill, not this script, writes
|
|
177
|
+
// the markdown table at {workflow.deferred_work_path} using the ledger schema).
|
|
178
|
+
const deferred = []
|
|
179
|
+
for (const r of perStory) {
|
|
180
|
+
for (const d of r.deferred || []) {
|
|
181
|
+
deferred.push({ story: r.story, ...d })
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Phase 2: epic-level trace gate over the whole epic once every story has landed.
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
phase('EpicGate')
|
|
189
|
+
const epicGate = await agent(
|
|
190
|
+
`All stories of BMAD Epic "${epic}" have been delivered on their worktree branches. ` +
|
|
191
|
+
`Run the EPIC-LEVEL TEA quality gate over the consolidated epic` +
|
|
192
|
+
(isProduction ? ` (run bmad-testarch-automate -> bmad-testarch-trace -> bmad-testarch-nfr first)` : ` (run bmad-testarch-trace first)`) +
|
|
193
|
+
`, then run EXACTLY:\n` +
|
|
194
|
+
` uv run ${skillRoot}/scripts/gate_eval.py --trace-output ${traceOutput} --profile ${profile}` +
|
|
195
|
+
(isProduction ? ` --nfr <nfr-assessment.md> --test-review <test-review.md>` : ``) + `\n` +
|
|
196
|
+
`Return the script's verdict, gate_status, and reasons VERBATIM. Do not recompute thresholds or override the verdict.\n` +
|
|
197
|
+
`Per-story outcomes for context: ${JSON.stringify(perStory.map((r) => ({ story: r.story, verdict: r.verdict, gate_status: r.gate_status })))}\n\n` +
|
|
198
|
+
`Return ONLY the JSON object matching the schema.`,
|
|
199
|
+
{ label: `epic-gate:${epic}`, phase: 'EpicGate', schema: EPICGATE_SCHEMA }
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
log(`Epic gate: ${epicGate ? `${epicGate.verdict}/${epicGate.gate_status}` : 'unavailable'}; deferred items: ${deferred.length}`)
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
perStory: perStory.map((r) => ({ story: r.story, verdict: r.verdict, gate_status: r.gate_status })),
|
|
206
|
+
epicGate,
|
|
207
|
+
deferred,
|
|
208
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Customization for ultracode-goal.
|
|
2
|
+
# Resolved at activation via resolve_customization.py (base -> team -> user).
|
|
3
|
+
# Teams/users override in {project-root}/_bmad/custom/ultracode-goal.toml (and .user.toml)
|
|
4
|
+
# without editing this file.
|
|
5
|
+
|
|
6
|
+
[workflow]
|
|
7
|
+
activation_steps_prepend = []
|
|
8
|
+
activation_steps_append = []
|
|
9
|
+
persistent_facts = ["file:{project-root}/**/project-context.md"]
|
|
10
|
+
|
|
11
|
+
# --- Paths: TEA + BMAD artifacts ---
|
|
12
|
+
tea_config_path = "{project-root}/_bmad/tea/config.yaml"
|
|
13
|
+
trace_output_dir = "{project-root}/_bmad-output/test-artifacts/traceability"
|
|
14
|
+
implementation_artifacts = "{project-root}/_bmad-output/implementation-artifacts"
|
|
15
|
+
deferred_work_path = "{project-root}/_bmad-output/implementation-artifacts/deferred-work.md"
|
|
16
|
+
|
|
17
|
+
# --- Git isolation + guardrails ---
|
|
18
|
+
epic_branch_prefix = "ultracode/epic-"
|
|
19
|
+
protected_branches = ["main", "master"]
|
|
20
|
+
|
|
21
|
+
# --- Budget guardrails (runaway-proofing) ---
|
|
22
|
+
max_turns_per_story = 25
|
|
23
|
+
story_token_budget = 1500000
|
|
24
|
+
|
|
25
|
+
# --- Experimental --parallel worktree fan-out ---
|
|
26
|
+
# 8 chosen under the platform's 16-concurrent ceiling; the .js default is only a fallback when unset.
|
|
27
|
+
parallel_max_concurrency = 8
|
|
28
|
+
|
|
29
|
+
# --- Commands pre-populated to the allowlist before an unattended run ---
|
|
30
|
+
allowlist_commands = ["git status", "git add -A", "git commit", "npm test", "npm run lint", "npm run build", "pytest", "uv run"]
|
|
31
|
+
|
|
32
|
+
# --- Hook fired at epic completion (prompt or shell command); empty = none ---
|
|
33
|
+
on_epic_complete = ""
|
|
34
|
+
|
|
35
|
+
# --- Health check (workflow self-improvement; opt-out + autonomy-safe) ---
|
|
36
|
+
# The terminal Finalize step reflects on the run and can file genuine workflow
|
|
37
|
+
# friction as fingerprint-deduped GitHub issues. Honesty-by-default: zero
|
|
38
|
+
# findings is the expected, healthy outcome. See docs/health-check.md.
|
|
39
|
+
health_check_enabled = true
|
|
40
|
+
health_check_repo = "armelhbobdad/bmad-module-ultracode-goal"
|
|
41
|
+
health_check_seen_cache = "$HOME/.ultracode-goal/health-check-seen.json"
|
|
42
|
+
health_check_queue_path = "{project-root}/_bmad-output/ultracode-goal/improvement-queue"
|
|
43
|
+
# In unattended/headless runs there is no human at the [Y]/[N]/[E] gate.
|
|
44
|
+
# false (default) = queue findings locally, NEVER live-submit without a human.
|
|
45
|
+
# true = additionally live-submit `bug`-severity findings unattended (still
|
|
46
|
+
# deduped + seen-cache-guarded). friction/gap never auto-submit.
|
|
47
|
+
health_check_autosubmit = false
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Stage 3 — Define Done
|
|
2
|
+
|
|
3
|
+
Turn the Epic's acceptance criteria into **executable, red-phase acceptance tests** before any production code is written. The output of this stage is a per-story, machine-checkable Definition-of-Done: a generated `atdd-checklist` plus failing (`test.skip`) acceptance tests that Stage 4 will un-skip and drive to green. Run entirely in `{communication_language}`; TEA-produced documents are written in `{document_output_language}`.
|
|
4
|
+
|
|
5
|
+
This stage runs only after Stage 2 preflight returns green (intervention budget == 0). Preflight has already scaffolded the test framework (`bmad-testarch-framework`) and generated any missing acceptance criteria (`bmad-create-story`), so the framework is present and every in-scope story has *some* ACs by the time you arrive here. This stage hardens that into per-story executable tests.
|
|
6
|
+
|
|
7
|
+
## Per Epic — Test Design (once)
|
|
8
|
+
|
|
9
|
+
Run `bmad-testarch-test-design` in **Epic-Level Mode** for the Epic. This is the risk-and-priority backbone the rest of the run reads from. Its job here:
|
|
10
|
+
|
|
11
|
+
- **Risk matrix** — genuine risks (not features) with unique IDs, classified by category (TECH / SEC / PERF / DATA / BUS / OPS), each scored probability × impact (1–3 each); score ≥6 is high-priority and must carry a mitigation. This is what `bmad-testarch-trace` later resolves the gate against, so it must exist before Execute.
|
|
12
|
+
- **Priority classification P0–P3** — P0 = blocks core flow + high risk + no workaround. The gate Stage 5 reads (`gate_eval.py`) keys P0, P1, and overall thresholds to these priorities; those thresholds are hardcoded in TEA and TEA-owned. Do **not** restate or recompute the percentages; just ensure the test-design assigns the priorities honestly.
|
|
13
|
+
- **NFR thresholds** — for every NFR in scope, extract the concrete threshold from PRD / architecture / ADRs / epics / stories. Unknown thresholds are marked `UNKNOWN` and converted into a risk, assumption, or deferred item — **never guessed**. In production these feed `bmad-testarch-nfr` at the gate; a missing threshold that should have a value is a deferral candidate, not a fabrication.
|
|
14
|
+
|
|
15
|
+
Epic-Level Mode is selected by the presence of `sprint-status.yaml` (preflight reports `sprint_status_present`); if test-design prompts for system-vs-epic, choose **epic**. Force **Create** mode if it offers Resume/Validate/Edit — those are interactive and stall an unattended run.
|
|
16
|
+
|
|
17
|
+
A `CONCERNS`-grade gap in the test plan (e.g. an `UNKNOWN` NFR threshold that does not block any P0 story) appends to `{workflow.deferred_work_path}` and the Epic keeps moving. A genuine red gap — no risk coverage for a P0 flow — is not deferrable; resolve it before proceeding to per-story work.
|
|
18
|
+
|
|
19
|
+
## Per Story — Create then ATDD
|
|
20
|
+
|
|
21
|
+
For every **in-scope** story, in sprint order:
|
|
22
|
+
|
|
23
|
+
1. **`bmad-create-story`** — produce the dedicated story file with full implementation context and *clear, testable* acceptance criteria. If preflight already generated this story, treat the change request as a refinement, not a re-create. Vague ACs are the single most common cause of the next step halting — sharpen them here.
|
|
24
|
+
|
|
25
|
+
2. **`bmad-testarch-atdd`** — generate the red-phase acceptance test scaffolds for that story. This writes:
|
|
26
|
+
- `atdd-checklist-{story_key}.md` under the TEA test-artifacts directory — the per-story checklist mapping each AC to its planned test(s).
|
|
27
|
+
- The acceptance test files themselves, **every test marked `test.skip()`** (TDD red phase). ATDD verifies this and errors if any generated test is not skipped — that is correct and expected; the tests are *supposed* to fail/skip until Stage 4 implements the feature and un-skips them.
|
|
28
|
+
|
|
29
|
+
`bmad-testarch-atdd` **HARD HALTS** if the story lacks clear acceptance criteria or the framework is not configured. Both are preconditions this stage's step 1 and Stage 2 preflight exist to satisfy — if atdd halts anyway, the story's ACs are still too vague: loop back to `bmad-create-story` for that story and re-run atdd. Do not hand a story to Execute with a halted or absent atdd-checklist.
|
|
30
|
+
|
|
31
|
+
Force **Create** mode (not Resume/Validate/Edit) for the same unattended reason.
|
|
32
|
+
|
|
33
|
+
## Exit condition (testable)
|
|
34
|
+
|
|
35
|
+
Stage 3 is complete, and Stage 4 may begin, when **all three** hold:
|
|
36
|
+
|
|
37
|
+
- The Epic has a `bmad-testarch-test-design` plan with a populated risk matrix, P0–P3 priorities assigned, and every in-scope NFR threshold either resolved or explicitly marked `UNKNOWN`/deferred.
|
|
38
|
+
- **Every in-scope story** has a story file with clear acceptance criteria.
|
|
39
|
+
- **Every in-scope story** has a generated `atdd-checklist-{story_key}.md` and red-phase (`test.skip`) acceptance tests on disk.
|
|
40
|
+
|
|
41
|
+
If any in-scope story is missing its atdd-checklist, this stage is not done — Execute has nothing executable to drive that story to. Record in `.decision-log.md`: the test-design verdict, each story's AC + checklist status, and any deferral appended to `{workflow.deferred_work_path}`.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Stage 4 — Execute
|
|
2
|
+
|
|
3
|
+
Drive each in-scope story from its red-phase acceptance tests (generated in Stage 3) to a green, committed state. This stage **produces and prints evidence**; it does **not** decide completion. The completion verdict belongs to Stage 5 (`scripts/gate_eval.py` reading TEA's `gate-decision.json`) — never the `/goal` transcript evaluator, which can only see what you surface and cannot read the gate file. Run prose in `{communication_language}`.
|
|
4
|
+
|
|
5
|
+
Two paths. **Sequential `/goal` spine is the DEFAULT.** `--parallel` worktree fan-out is an additive, **EXPERIMENTAL** opt-in. Both share the same hooks and the same Stage 5 gate.
|
|
6
|
+
|
|
7
|
+
Preconditions (asserted, not assumed): you are on the Epic branch (`{workflow.epic_branch_prefix}…`, off a `{workflow.protected_branches}` branch), PreToolUse + Stop hooks are active in `.claude/settings.local.json`, the allowlist is pre-populated, and every in-scope story has an `atdd-checklist-{story_key}.md` with `test.skip` acceptance tests. If any is false, return to Stage 2.
|
|
8
|
+
|
|
9
|
+
**On resume, re-enter Execute at the first story whose last logged gate verdict is not advance; advanced stories are not re-run; re-assert (do not rebuild) the Epic branch, hooks, and allowlist before continuing.**
|
|
10
|
+
|
|
11
|
+
## Default — Sequential `/goal` spine
|
|
12
|
+
|
|
13
|
+
For each in-scope story, in sprint order, run this loop:
|
|
14
|
+
|
|
15
|
+
0. **Set the current story** so the PreToolUse hook can locate this story's marker: write the story id to `{workflow.implementation_artifacts}/.current-story` (or export `ULTRACODE_STORY_ID=<story_id>` for the run). The guard reads the story id from `ULTRACODE_STORY_ID`, falling back to that `.current-story` file — without one set, it cannot find the marker and denies every commit.
|
|
16
|
+
1. `bmad-create-story` (if not already current) → `bmad-dev-story` implementing the feature and **un-skipping** the story's ATDD acceptance tests (remove `test.skip()`). Before delegating, check the run's `.decision-log.md` for an **Operator notes** entry naming this story (a pre-launch hint like "watch the auth flow in story 3") and surface that note into the `bmad-dev-story` / review context for this story so the operator's last words actually reach the work.
|
|
17
|
+
2. Run the tests, lint, and build, and **PRINT the raw output** (pass/fail counts, lint result, build result) into the transcript. This printed evidence is what keeps the run judgeable mid-flight and what the `/goal` driver reads to pace itself — but it is *evidence*, not the gate. **Once all three go green, write the tests-ran marker** `{workflow.implementation_artifacts}/.tests-ran-<story_id>` (mirroring `assets/execute-epic.workflow.js` step 3) — the PreToolUse guard denies the step-4 commit unless this marker exists for the current story, so write it only after a real green run.
|
|
18
|
+
3. **Production profile only:** `bmad-testarch-test-review`, then `bmad-code-review`. A `CONCERNS`-grade or non-critical finding appends to `{workflow.deferred_work_path}` and the story proceeds. A **P0 / critical FAIL never defers** — re-loop within the turn budget or escalate.
|
|
19
|
+
4. **Commit at green** (one commit per green story). The PreToolUse hook blocks a commit on a protected branch and blocks a commit when no "tests-ran" marker exists for the current story, so step 0 must have set the current story and step 2 must have actually run and written `{workflow.implementation_artifacts}/.tests-ran-<story_id>`.
|
|
20
|
+
|
|
21
|
+
**Sub-skill halt catch-all.** If any orchestrated sub-skill blocks on interactive input mid-run, treat it as escalate for that story — write the escalation marker and stop; do not answer its prompt blind.
|
|
22
|
+
|
|
23
|
+
### Authoring the `/goal` condition
|
|
24
|
+
|
|
25
|
+
Wrap the loop in a single `/goal` whose condition is **under 4000 characters** and encodes the per-story Definition-of-Done as the success criterion, with an explicit escape clause. The condition must:
|
|
26
|
+
|
|
27
|
+
- State the DoD in checkable terms: ATDD acceptance tests for the story un-skipped and **passing**, lint clean, build green, and (production) test-review + code-review passed — mirroring the printed evidence so the transcript-only evaluator can corroborate it.
|
|
28
|
+
- Include the literal escape: **"…or stop after {workflow.max_turns_per_story} turns."** This is the in-condition runaway guard. The `budget_stop.py` Stop hook is belt-and-suspenders — it tracks turns/tokens against `{workflow.max_turns_per_story}` / `{workflow.story_token_budget}` and writes an escalation marker when exceeded — but a Stop hook **cannot force `/goal` to halt mid-condition**, so the turn clause inside the condition is the real bound. State it.
|
|
29
|
+
- Make explicit that **passing the condition is not completion** — the authoritative verdict is Stage 5's `gate_eval.py`. The `/goal` condition gets a story to a *plausibly* done, evidence-printed state; the gate decides.
|
|
30
|
+
|
|
31
|
+
Keep it under the character limit by referencing the story's `atdd-checklist` and printed test output rather than restating ACs verbatim.
|
|
32
|
+
|
|
33
|
+
After the spine finishes (all stories committed-at-green, or a story hit its turn bound and wrote an escalation marker), proceed to Stage 5 for the deterministic per-story and epic-level gate.
|
|
34
|
+
|
|
35
|
+
## Experimental — `--parallel` worktree fan-out
|
|
36
|
+
|
|
37
|
+
Only when the user passed `--parallel`. Invoke the saved dynamic workflow `assets/execute-epic.workflow.js` (registered as `/ultracode-goal-execute`) with:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
args = {
|
|
41
|
+
epic, # Epic id
|
|
42
|
+
stories, # in-scope story ids/keys, sprint order
|
|
43
|
+
profile, # "production" | "light"
|
|
44
|
+
paths: {
|
|
45
|
+
trace_output: {workflow.trace_output_dir},
|
|
46
|
+
deferred_work: {workflow.deferred_work_path},
|
|
47
|
+
implementation_artifacts: {workflow.implementation_artifacts},
|
|
48
|
+
tea_config: {workflow.tea_config_path},
|
|
49
|
+
skill_root: {skill-root} # resolved absolute skill dir; the .js has no
|
|
50
|
+
# {skill-root} resolver, so it must arrive pre-resolved
|
|
51
|
+
},
|
|
52
|
+
max_concurrency: {workflow.parallel_max_concurrency}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Pass `paths.skill_root` as the **resolved** `{skill-root}` (the absolute install dir). The workflow runtime executes the `.js` directly and has no `{skill-root}` resolver, so it substitutes this value into the `gate_eval.py` invocations it emits to each subagent; without it the spawned worktree agent receives a literal `{skill-root}` and must guess the script's path.
|
|
57
|
+
|
|
58
|
+
Each story runs isolated in its own worktree: `bmad-dev-story` (un-skip ATDD) → run tests/lint/build → (production) `bmad-testarch-test-review` → `bmad-code-review` → commit at green → per-story `gate_eval`. After all stories, an epic-level trace gate. The workflow returns `{ perStory: [{story, verdict, gate_status}], epicGate, deferred: […] }`, which feeds Stage 5/6.
|
|
59
|
+
|
|
60
|
+
**This path is experimental — be explicit about its limits and do not silently rely on it:**
|
|
61
|
+
|
|
62
|
+
- **No mid-run input.** The fan-out takes no interactive input once launched; every blocker must be resolved at preflight or not at all. This is why launch requires post-remediation budget == 0.
|
|
63
|
+
- **Shared Auto Memory.** Concurrent worktrees write to **one** Auto Memory directory — there is no per-worktree isolation of learned facts; expect interleaving.
|
|
64
|
+
- **Under-documented interplay.** The workflow↔skill handoff (how args bind, how subagents inherit the allowlist and hooks) is not fully specified by the platform docs; treat its behavior as empirically validated, not guaranteed.
|
|
65
|
+
- **No `run-status.json` heartbeat.** Worktree agents cannot reliably write one shared snapshot (each worktree sees its own copy of `{workflow.implementation_artifacts}`), so this path does not write it — progress is watched via the workflow progress view (`/workflows`) and its run log, and the launch briefing says so.
|
|
66
|
+
|
|
67
|
+
### Graceful degradation
|
|
68
|
+
|
|
69
|
+
If dynamic workflows are **unavailable** (wrong Claude Code version, workflows feature off, or the saved command does not resolve), **fall back to the sequential `/goal` spine above** and log a one-line note in `.decision-log.md` recording why `--parallel` degraded. The Epic still ships; it just ships sequentially.
|
|
70
|
+
|
|
71
|
+
## Record
|
|
72
|
+
|
|
73
|
+
Log to `.decision-log.md`: the path taken (sequential / parallel / degraded-fallback), per-story turn-budget outcomes, every commit-at-green, every deferral appended to `{workflow.deferred_work_path}`, and any escalation marker. The printed test/lint/build evidence stays in the transcript; the decision log carries the durable account across compaction.
|
|
74
|
+
|
|
75
|
+
## Run-status heartbeat
|
|
76
|
+
|
|
77
|
+
As the spine advances — each time you move to a new story, log a gate verdict, or spend a re-loop — write `{workflow.implementation_artifacts}/run-status.json` so an automator (or an anxious human) polling a long/headless run has something structured to read instead of prose. Overwrite it in place; it is a single live snapshot, not an append log:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{"epic": "<epic id>",
|
|
81
|
+
"story": "<current story id>",
|
|
82
|
+
"index": <1-based position of the current story>,
|
|
83
|
+
"total": <in-scope story count>,
|
|
84
|
+
"last_verdict": "<advance|defer|reloop|escalate, or null before the first gate>",
|
|
85
|
+
"reloop_count": <re-loops spent so far this run>,
|
|
86
|
+
"profile": "production|light",
|
|
87
|
+
"updated": "<ISO-8601 timestamp>"}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
This is the file the Stage 2 launch briefing points the operator at ("where to watch"); Stage 6 (finalize) records the terminal state into it when the run closes.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Stage 6 — Finalize
|
|
2
|
+
|
|
3
|
+
**Goal:** Make the run pay off for the next one. Capture what was learned to the right durable store, optionally run a retrospective, audit the decision log, produce a run report, and surface the deferred-work ledger. This is the terminal stage — reached after the Epic-level gate advanced, or when a story escalated and the run is `blocked`. Converse in `{communication_language}`; the run report and ledger are written in `{document_output_language}`.
|
|
4
|
+
|
|
5
|
+
## Capture to Auto Memory — deliberately
|
|
6
|
+
|
|
7
|
+
Auto Memory is passive by default; drive it on purpose so the learnings land in the right place and the next run starts sharper. Decide each learning's home:
|
|
8
|
+
|
|
9
|
+
- **Machine-local learnings** (this repo's build quirks, a flaky-test workaround, a path that differs from convention, a TEA config gotcha) → tell the session to **`remember X`** so it persists to Auto Memory. Keep `MEMORY.md` terse — a short index that points to per-topic files; put the detail in the topic file, not the index.
|
|
10
|
+
- **Team standards** (a convention every contributor and future run should follow — a lint rule, a commit-message shape, a forbidden pattern) → write to `{project-root}/CLAUDE.md` or `{project-root}/.claude/rules`, not to machine-local memory. CLAUDE.md is context the whole team and every agent inherits; Auto Memory is yours alone.
|
|
11
|
+
|
|
12
|
+
The split matters: a team standard buried in machine-local memory never reaches the team, and a machine-local quirk in CLAUDE.md pollutes shared context. Route each learning by who needs it.
|
|
13
|
+
|
|
14
|
+
## Optional retrospective (`--retro`)
|
|
15
|
+
|
|
16
|
+
Interactive runs **offer** the retrospective at Epic close (skip it by default if the user declines); headless runs it **only when `--retro` was passed**. When run, `bmad-retrospective` covers the Epic, and its lessons feed back through the capture step above — durable conclusions go to memory or CLAUDE.md by the same machine-local-vs-team split. It is additive, not required to close the run.
|
|
17
|
+
|
|
18
|
+
## Decision-log audit
|
|
19
|
+
|
|
20
|
+
Walk every entry in this run's `.decision-log.md` (scope, preflight verdict, each gate verdict, each deferral, every assumption). Each must resolve to one of three: **captured in the run report** (a primary decision or outcome the user takes away), **captured in the addendum** (a parked alternative or rejected option that needs a home but not the report), or **explicitly marked process noise** (set aside, not silently dropped). End with a shared accounting of how the run's reasoning was handled — not a one-sided polish.
|
|
21
|
+
|
|
22
|
+
## Run report
|
|
23
|
+
|
|
24
|
+
Produce a report (write it as a peer of `.decision-log.md` in the run folder, e.g. `run-report.md`) covering:
|
|
25
|
+
|
|
26
|
+
- Epic and profile (production / `--light`), branch off `{workflow.epic_branch_prefix}`, sequential vs `--parallel`.
|
|
27
|
+
- Per-story outcome: gate_status and verdict (advance / defer / reloop / escalate), and any re-loops spent against budget.
|
|
28
|
+
- The Epic-level gate result.
|
|
29
|
+
- Budget consumed vs `{workflow.max_turns_per_story}` / `{workflow.story_token_budget}`.
|
|
30
|
+
- Learnings captured and where they went (memory vs CLAUDE.md).
|
|
31
|
+
- A pointer to the deferred-work ledger and its open-item count.
|
|
32
|
+
|
|
33
|
+
## Record the terminal run-status
|
|
34
|
+
|
|
35
|
+
Execute maintains the heartbeat `{workflow.implementation_artifacts}/run-status.json` as the spine advances (shape: `{epic, story, index, total, last_verdict, reloop_count, profile, updated}`). At close, write its **terminal** state — the final story/index, the Epic-level `last_verdict` (`advance` when complete, the escalating story's verdict when blocked), and a fresh `updated` timestamp — so a poller reading the file after the run sees the settled outcome, not a stale mid-run snapshot.
|
|
36
|
+
|
|
37
|
+
## Surface the deferred-work ledger
|
|
38
|
+
|
|
39
|
+
Show the user **this run's Epic heading** from the ledger at `{workflow.deferred_work_path}` — the open items under that one heading, their severity and suggested actions — so nothing parked during the run is invisible at handoff. The ledger holds one heading per Epic across runs; do not surface other Epics' parked work. If the file (or this Epic's heading) does not exist, say so plainly: nothing was deferred this run.
|
|
40
|
+
|
|
41
|
+
## Epic-complete hook
|
|
42
|
+
|
|
43
|
+
This hook fires **only when the Epic-level gate verdict was `advance`** (a `complete` run). On a `blocked` run — a story escalated and the Epic never advanced — skip this step entirely; a "notify success" command must not fire on a blocked Epic.
|
|
44
|
+
|
|
45
|
+
When the Epic advanced, run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_epic_complete`
|
|
46
|
+
|
|
47
|
+
If the resolved `{workflow.on_epic_complete}` is non-empty, follow it as the final terminal instruction (a prompt to run or a shell command) before exiting.
|
|
48
|
+
|
|
49
|
+
## Headless output
|
|
50
|
+
|
|
51
|
+
In headless (`-H`), compose the final JSON, run the Workflow health check (below) in its unattended queue-only mode, then emit the JSON and stop. `status` is `complete` when the Epic-level gate advanced, or `blocked` when a story escalated. This is the **same five-key shape every headless exit point honors** (Stage 1 first-touch / already-done blocks, Stage 2 preflight block, and this Stage 6 final emit): all five keys are **always present**, with `report` and `deferred_work` set to `null` when not produced, and `reason` carrying a one-line cause only when `blocked` (`null` otherwise). An early `blocked` exit that produced no report still emits `"report": null` — never a missing key — so a caller parsing the documented shape never raises a KeyError:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{"status": "complete|blocked",
|
|
55
|
+
"skill": "ultracode-goal",
|
|
56
|
+
"decision_log": "<path to this run's .decision-log.md>",
|
|
57
|
+
"report": "<path to run-report.md, or null>",
|
|
58
|
+
"deferred_work": "<path to {workflow.deferred_work_path}, or null>",
|
|
59
|
+
"reason": "<one line when blocked, else null>"}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Workflow health check (terminal)
|
|
63
|
+
|
|
64
|
+
After the run-status is settled and (in headless) the JSON is composed but **before** the final emit/exit, load `references/health-check.md`, read it fully, and execute it. This is the true terminal step for every run that reached Stage 6 — both a `complete` run and a `blocked` (escalated) run, since the workflow drove real work either way and genuine friction is observable. In headless it runs in its unattended queue-only mode and **never blocks the emit**; see that file's routing rules. Do not perform any other action between this section and executing the health check.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Stage 5 — Gate
|
|
2
|
+
|
|
3
|
+
**Goal:** Decide whether a completed story (or, after the last story, the Epic) advances — by a deterministic artifact read, not by judgment. `scripts/gate_eval.py` reads TEA's `gate-decision.json` and returns a routing verdict. You execute the route. Converse in `{communication_language}`; the deferred-work ledger is written in `{document_output_language}`.
|
|
4
|
+
|
|
5
|
+
This is the completion authority. The `/goal` evaluator only sees the transcript; it cannot read the gate file. Never substitute your own read of "the tests look green" or the evaluator's transcript-only verdict for this script. The JSON is the truth.
|
|
6
|
+
|
|
7
|
+
## Backfill the gate evidence (production only)
|
|
8
|
+
|
|
9
|
+
The gate reads artifacts TEA produces. In **production**, before running the gate, generate the evidence in order so the gate has something current to read:
|
|
10
|
+
|
|
11
|
+
1. `bmad-testarch-automate` — backfill coverage for code that landed during Execute.
|
|
12
|
+
2. `bmad-testarch-trace` — (re)build the traceability matrix and write the gate decision.
|
|
13
|
+
3. `bmad-testarch-nfr` — audit NFR evidence; produces `nfr-assessment.md`.
|
|
14
|
+
|
|
15
|
+
`bmad-testarch-test-review` runs in Execute per story; if you do not have a current `test-review.md` for the story, run it now too. In **`--light`**, skip all of the above and run only `bmad-testarch-trace`, then the gate with `--profile light` (trace gate only — no NFR/review AND).
|
|
16
|
+
|
|
17
|
+
## Run the gate
|
|
18
|
+
|
|
19
|
+
Production:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
uv run {skill-root}/scripts/gate_eval.py --trace-output {workflow.trace_output_dir} --profile production --nfr {nfr-assessment.md} --test-review {test-review.md}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Light:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
uv run {skill-root}/scripts/gate_eval.py --trace-output {workflow.trace_output_dir} --profile light
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Resolve `{nfr-assessment.md}` and `{test-review.md}` to the paths TEA wrote them to (under `{workflow.trace_output_dir}` or the TEA output root); pass the production-only flags only in production. The script reads `gate-decision.json` (resolving its filename from the trace report frontmatter, falling back to the `e2e-trace-summary.json` gate fields when the slim file is absent — that fallback is **not** a failure). It returns JSON:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{"verdict": "advance|defer|reloop|escalate",
|
|
35
|
+
"gate_status": "PASS|CONCERNS|FAIL|WAIVED|NOT_EVALUATED",
|
|
36
|
+
"p0_status": "...", "p1_status": "...", "overall_status": "...",
|
|
37
|
+
"nfr_status": "...", "review_score": 0, "reasons": ["..."]}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Do not recompute TEA thresholds or re-judge `gate_status` — read it as given. The script already ANDs the production signals (an `advance` is downgraded to `reloop` if `nfr-assessment.md` overallStatus is FAIL, or `test-review.md` score < 80 or recommendation is Block). Record the full verdict JSON and its `reasons` in `.decision-log.md` for this story.
|
|
41
|
+
|
|
42
|
+
## Route on the verdict
|
|
43
|
+
|
|
44
|
+
- **`advance`** (gate_status PASS or WAIVED) → the story passes. Move to the next story; when the last story passes, run the Epic-level trace gate the same way, then proceed to Stage 6 (`references/finalize.md`).
|
|
45
|
+
|
|
46
|
+
- **`defer`** (gate_status CONCERNS, or non-critical code-review / NFR findings that did not flip the gate) → append the open items to the ledger at `{workflow.deferred_work_path}` using the schema below, then **advance** anyway. The Epic keeps moving; the parked work is visible.
|
|
47
|
+
|
|
48
|
+
- **`reloop`** (gate_status FAIL, or a production signal downgraded an advance) → run `bmad-correct-course` to diagnose and adjust, then re-run the story (back through Execute, `references/execute.md`) — **within the remaining turn/token budget**. Re-run the gate after. If the re-loop would exceed `{workflow.max_turns_per_story}` or `{workflow.story_token_budget}`, treat it as `escalate` instead.
|
|
49
|
+
|
|
50
|
+
- **`escalate`** (gate_status NOT_EVALUATED — the gate could not be read — or budget exhausted on a FAIL) → **stop.** Do not advance, do not defer the failing item. Record the reason and the verdict JSON in `.decision-log.md`. In an attended run, surface the blocker to the user. In headless, this is a `blocked` outcome — emit the JSON in Stage 6 (`references/finalize.md`).
|
|
51
|
+
|
|
52
|
+
**INVARIANT — a P0/critical FAIL never defers.** A failing gate (or a P0/P1/overall threshold miss) is `reloop` or `escalate`, never `defer`. Only non-gate-blocking work (CONCERNS, non-critical findings, parked decisions) is allowed onto the ledger. If you find yourself about to write a FAIL or a critical finding to the ledger, you are violating the gate — re-loop within budget or escalate instead.
|
|
53
|
+
|
|
54
|
+
If any orchestrated sub-skill blocks on interactive input mid-run, treat it as `escalate` for that story — write the escalation marker and stop; do not answer its prompt blind.
|
|
55
|
+
|
|
56
|
+
## Deferred-work ledger schema
|
|
57
|
+
|
|
58
|
+
Append to `{workflow.deferred_work_path}` (create on first use). One heading per Epic, then a row per parked item; `status` is `open` at write time:
|
|
59
|
+
|
|
60
|
+
```markdown
|
|
61
|
+
# Deferred Work — <epic>
|
|
62
|
+
|
|
63
|
+
| id | source | severity | story | reason | suggested_action | status |
|
|
64
|
+
|----|--------|----------|-------|--------|------------------|--------|
|
|
65
|
+
| d1 | gate | low | <story-id> | <why parked> | <what to do later> | open |
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- `source` ∈ `gate` (CONCERNS), `code-review` (non-critical finding), `nfr` (non-FAIL finding), `decision` (parked decision).
|
|
69
|
+
- `severity` ∈ `low`, `med`, `high`. A `high` that maps to a gate FAIL or a P0/critical finding does **not** belong here — re-loop or escalate it.
|
|
70
|
+
- `id` is unique within the Epic heading (append `d2`, `d3`, … ); never rewrite existing rows.
|