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.
Files changed (47) hide show
  1. package/.claude-plugin/marketplace.json +20 -0
  2. package/.gitattributes +16 -0
  3. package/.nvmrc +1 -0
  4. package/LICENSE +27 -0
  5. package/README.md +200 -0
  6. package/docs/_internal/RELEASING.md +101 -0
  7. package/docs/_internal/STABILITY.md +62 -0
  8. package/docs/architecture.md +77 -0
  9. package/docs/assets/ucg-logo.svg +73 -0
  10. package/docs/gate-model.md +109 -0
  11. package/docs/getting-started.md +51 -0
  12. package/docs/health-check.md +61 -0
  13. package/docs/how-it-works.md +73 -0
  14. package/docs/index.md +25 -0
  15. package/docs/parallel-mode.md +33 -0
  16. package/docs/troubleshooting.md +56 -0
  17. package/docs/why-ultracode-goal.md +34 -0
  18. package/package.json +100 -0
  19. package/skills/.gitkeep +0 -0
  20. package/skills/module.yaml +12 -0
  21. package/skills/ultracode-goal/SKILL.md +75 -0
  22. package/skills/ultracode-goal/assets/execute-epic.workflow.js +208 -0
  23. package/skills/ultracode-goal/customize.toml +47 -0
  24. package/skills/ultracode-goal/references/define-done.md +41 -0
  25. package/skills/ultracode-goal/references/execute.md +90 -0
  26. package/skills/ultracode-goal/references/finalize.md +64 -0
  27. package/skills/ultracode-goal/references/gate.md +70 -0
  28. package/skills/ultracode-goal/references/health-check.md +332 -0
  29. package/skills/ultracode-goal/references/ingest-and-scope.md +46 -0
  30. package/skills/ultracode-goal/references/preflight.md +100 -0
  31. package/skills/ultracode-goal/scripts/gate_eval.py +280 -0
  32. package/skills/ultracode-goal/scripts/health_check_fp.py +196 -0
  33. package/skills/ultracode-goal/scripts/hooks/budget_stop.py +162 -0
  34. package/skills/ultracode-goal/scripts/hooks/guard_pretooluse.py +174 -0
  35. package/skills/ultracode-goal/scripts/preflight_check.py +399 -0
  36. package/tools/cli/commands/install.js +36 -0
  37. package/tools/cli/commands/status.js +161 -0
  38. package/tools/cli/commands/uninstall.js +175 -0
  39. package/tools/cli/commands/update.js +65 -0
  40. package/tools/cli/lib/ide-skills.js +218 -0
  41. package/tools/cli/lib/installer.js +226 -0
  42. package/tools/cli/lib/manifest.js +100 -0
  43. package/tools/cli/lib/platform-codes.yaml +223 -0
  44. package/tools/cli/lib/ui.js +353 -0
  45. package/tools/cli/lib/version-check.js +86 -0
  46. package/tools/cli/ucg-cli.js +45 -0
  47. package/tools/ucg-npx-wrapper.js +36 -0
@@ -0,0 +1,332 @@
1
+ ---
2
+ # UltraCode-Goal self-improvement health check — captures real workflow
3
+ # friction as GitHub issues. (No name/description keys: those belong only
4
+ # in SKILL.md frontmatter; this is a stage reference, not a skill.)
5
+ healthCheckRepo: '{workflow.health_check_repo}'
6
+ localFallbackFolder: '{workflow.health_check_queue_path}'
7
+ seenCachePath: '{workflow.health_check_seen_cache}'
8
+ liveSubmitSeverities: ['bug'] # friction/gap go local-queue-default with explicit opt-in
9
+ enabled: '{workflow.health_check_enabled}'
10
+ autosubmit: '{workflow.health_check_autosubmit}'
11
+ ---
12
+
13
+ # Health Check: Workflow Self-Improvement
14
+
15
+ This is the terminal step of Stage 6 (Finalize). Finalize loads it by bare path (`references/health-check.md`); it is fully self-contained and chains nowhere. Reflect on the ultracode-goal run that just completed; if real friction, bugs, or gaps were encountered in the *workflow instructions* (not the user's code), capture them as structured findings for review and optional submission as GitHub issues. Zero overhead for clean runs: if nothing went wrong, log one line and exit.
16
+
17
+ ## Role
18
+
19
+ You are a self-improvement auditor — honest, precise, evidence-based. You report ONLY what you actually experienced executing the ultracode-goal stages during THIS run. You are NOT a creative writer looking for things to say.
20
+
21
+ ## Anti-fabrication rules
22
+
23
+ - **DO NOT FABRICATE ISSUES.** If the workflow ran smoothly, say so and exit. Inventing issues to appear thorough is a SYSTEM FAILURE.
24
+ - Only report issues you **ACTUALLY encountered** while following the stage instructions during THIS run.
25
+ - Every finding MUST cite the **specific stage reference file and section** where the issue occurred.
26
+ - If you are unsure whether something was a real issue or your own confusion, DO NOT report it.
27
+ - **Reporting zero issues is the EXPECTED outcome** for a well-designed workflow.
28
+
29
+ ## 0. Announce arrival
30
+
31
+ Display, in `{communication_language}`: "Running a quick self-improvement check on this run. If nothing rough came up, I will close out immediately."
32
+
33
+ In headless (`-H`), skip the display and log: "headless: skipped health-check arrival announcement". The line is informational, not a commitment gate — proceed to §0.5 without waiting.
34
+
35
+ ## 0.5. Enabled gate
36
+
37
+ If `{workflow.health_check_enabled}` is **false**, the health check is off. Log one line — "health-check disabled (health_check_enabled=false); skipping" — and **exit immediately**. Do nothing else: no reflection, no files, no JSON side effects. In headless this returns control so Finalize emits its final JSON unchanged.
38
+
39
+ Otherwise continue to §1.
40
+
41
+ ## 1. Read run context
42
+
43
+ From this run's `.decision-log.md` and session context, identify:
44
+
45
+ - **Stage(s) executed** — which of `ingest-and-scope`, `preflight`, `define-done`, `execute`, `gate`, `finalize` you actually followed.
46
+ - **Friction points** — moments where a stage instruction was unclear, wrong, contradictory, or silent on a scenario you hit.
47
+
48
+ The finding's `stage` is whichever of the six you were following when the issue bit you.
49
+
50
+ ## 2. Reflect on execution
51
+
52
+ Silently review the run. Ask:
53
+
54
+ - Did a stage instruction lead me astray or cause unnecessary back-and-forth?
55
+ - Was a stage ambiguous, so I guessed rather than followed clear guidance?
56
+ - Did I hit a scenario no stage accounted for?
57
+ - Were any stage instructions wrong or contradictory?
58
+
59
+ If the answer to ALL is "no", display "Health Check: clean run. No workflow issues to report." and **STOP** — the run is done. In headless, log "health-check: clean run, no findings" and return.
60
+
61
+ ## 3. Severity definitions
62
+
63
+ Classify each genuine finding:
64
+
65
+ - **`bug`** — Stage instructions were wrong or contradictory.
66
+ - **`friction`** — Stage worked but was unclear, ambiguous, or caused unnecessary back-and-forth.
67
+ - **`gap`** — A scenario arose that the workflow did not account for at all.
68
+
69
+ ## 4. Route by run mode
70
+
71
+ The routing fork is the run mode, decided once here.
72
+
73
+ ### 4a. Attended routing (HALT gate)
74
+
75
+ In an attended run there is a human at the keyboard. Present every finding, then HALT.
76
+
77
+ Present each finding:
78
+
79
+ | Field | Value |
80
+ |-------|-------|
81
+ | Severity | `bug` / `friction` / `gap` |
82
+ | Stage | one of the six |
83
+ | Step File | `skills/ultracode-goal/references/{stage}.md` |
84
+ | Section | the stable section-heading slug (never line numbers) |
85
+
86
+ Then ask:
87
+
88
+ "Submit these findings?
89
+
90
+ - **[Y]** Yes — submit all findings
91
+ - **[N]** No — discard all findings
92
+ - **[E]** Edit — let me revise before submitting
93
+
94
+ You are the final filter. Reject any finding that does not reflect a real issue you observed."
95
+
96
+ **HALT and wait for input.**
97
+
98
+ - **[Y]** → proceed to §5 (severity routing).
99
+ - **[N]** → display "Findings discarded. Workflow complete." and STOP.
100
+ - **[E]** → let the user keep, modify, or remove findings; re-present the revised list and ask again.
101
+
102
+ ### 4-headless. Unattended routing (no human at the gate)
103
+
104
+ In headless (`-H`) there is no human to answer [Y]/[N]/[E], so that gate is **bypassed deterministically** — never block waiting for input:
105
+
106
+ - If `{workflow.health_check_autosubmit}` is **false** (the default): **queue EVERY finding locally** (§5c). Never live-submit in an unattended run with autosubmit off.
107
+ - If `{workflow.health_check_autosubmit}` is **true**: **live-submit `bug` findings only** (§5a) — still script-fingerprinted, remote-dedup-searched, and seen-cache-guarded; **friction and gap always queue** (§5c).
108
+
109
+ The health check must **NEVER block or delay the final headless JSON emit**. It is fire-and-queue: do the routing above, then return so Finalize emits its JSON. If `gh` is unavailable at any live-submit step, fall through to §5c — never stall.
110
+
111
+ ## 5. Severity routing (attended [Y] path)
112
+
113
+ Route each confirmed finding:
114
+
115
+ - **`bug`** → live-submit (§5a). High signal, priority for maintainers.
116
+ - **`friction` / `gap`** → local queue by default (§5c). These are the most subjective categories and produce the most near-duplicates. Ask **once per session**: "Also submit the {N} friction/gap finding(s) as GitHub issues? [y/N]" — only on explicit affirm do they route through §5a.
117
+
118
+ ### 5a. Live-submit: fingerprint, dedup, create
119
+
120
+ For each finding routed to live-submit:
121
+
122
+ **1. Compute the fingerprint via the script** — never inline shell. The fingerprint and seen-cache are computed and managed deterministically by `health_check_fp.py`:
123
+
124
+ ```
125
+ uv run {skill-root}/scripts/health_check_fp.py fingerprint \
126
+ --severity {severity} --stage {stage} --section-slug {slug}
127
+ ```
128
+
129
+ → `{"fp": "fp-xxxxxxx", "tuple": "<exact hashed input>"}`. The `section-slug` is a kebab-case stable heading slug (e.g. `verdict-mapping`), **never line numbers** — they drift when files are edited.
130
+
131
+ **Install-mode-invariant dedup (explicit rule):** the script's `workflow` component is `ultracode-goal/{stage}` and its `step_file` component is ALWAYS the source-repo form `skills/ultracode-goal/references/{stage}.md`, regardless of where the skill is installed (`_bmad/` tree vs. dev checkout). The same defect therefore dedups to the same `fp` across every install. Do not substitute the installed path.
132
+
133
+ **2. Check the seen-cache via the script:**
134
+
135
+ ```
136
+ uv run {skill-root}/scripts/health_check_fp.py seen --fp {fp} --cache {seenCachePath}
137
+ ```
138
+
139
+ → `{"seen": true, "record": {...}}` means this user already handled this fingerprint on this machine — skip submission silently and log: `"fp-xxxxxxx: already handled on {record.date}, {record.issue_url} — skipping"`. `{"seen": false, "record": null}` means proceed.
140
+
141
+ **3. Check GitHub CLI** with `gh auth status`. If it fails, fall through to §5c (offline fallback).
142
+
143
+ **4. Remote dedup search** — one deterministic call:
144
+
145
+ ```
146
+ gh search issues --repo {healthCheckRepo} --state open "{fp} in:title" --json number,url,title --limit 1
147
+ ```
148
+
149
+ **5a-i. If a matching open issue exists**, present:
150
+
151
+ > "Matching report found: #{N} — {title}
152
+ >
153
+ > Your finding has the same fingerprint `{fp}`. Options:
154
+ > - **[R]** React (👍) on the existing issue — silent upvote, no comment
155
+ > - **[C]** React + comment with YOUR environment/evidence delta (only if it materially differs)
156
+ > - **[N]** Create a new issue anyway — only if you are certain this is a distinct defect
157
+ > - **[S]** Skip — do not submit this finding"
158
+
159
+ Execute the choice:
160
+
161
+ - **R:** `gh api -X POST /repos/{healthCheckRepo}/issues/{N}/reactions -f content='+1'`, then record `reacted`.
162
+ - **C:** the reaction call, then `gh issue comment {N} --body "{minimal env+delta body}"` (the Environment table plus ONE sentence on what differs; no session narrative), then record `commented`.
163
+ - **N:** proceed to §5a-ii.
164
+ - **S:** record nothing.
165
+
166
+ Record the outcome via the script:
167
+
168
+ ```
169
+ uv run {skill-root}/scripts/health_check_fp.py record --fp {fp} --cache {seenCachePath} \
170
+ --issue-url {url} --action reacted|commented --date {YYYY-MM-DD}
171
+ ```
172
+
173
+ **5a-ii. If no matching open issue exists** — create one.
174
+
175
+ **First, ensure the `{fp}` label exists** — it is per-fingerprint, so the first reporter of any defect creates a brand-new label, and `gh issue create --label {fp}` hard-fails if the label is missing. Guard it idempotently:
176
+
177
+ ```
178
+ gh label create "{fp}" --repo {healthCheckRepo} --color "ededed" \
179
+ --description "Health-check fingerprint dedup key" 2>/dev/null || true
180
+ ```
181
+
182
+ The `|| true` makes it idempotent: if the label already exists, `gh label create` exits non-zero and we proceed unharmed. The other labels (`health-check`, `workflow-improvement`, `bug`/`friction`/`gap`) are pre-created repo labels and need no guard.
183
+
184
+ **Then create the issue:**
185
+
186
+ ```
187
+ gh issue create --repo {healthCheckRepo} \
188
+ --title "[health-check][{severity}][{fp}] {workflow}: {short description}" \
189
+ --label "health-check,workflow-improvement,{severity},{fp}" \
190
+ --body "{structured body below}"
191
+ ```
192
+
193
+ The `{workflow}` in the title is `ultracode-goal/{stage}`. The `{fp}` appears in both title (human-readable) and label (server-side filterable) so maintainers can query all reports for a defect via the `fp-*` label without parsing title text.
194
+
195
+ After creation, record the mapping via the script so this user never re-reports the fingerprint:
196
+
197
+ ```
198
+ uv run {skill-root}/scripts/health_check_fp.py record --fp {fp} --cache {seenCachePath} \
199
+ --issue-url {created-url} --action created --date {YYYY-MM-DD}
200
+ ```
201
+
202
+ **Writing rules — non-negotiable:**
203
+
204
+ - **One issue per finding.** Two independent problems → two issues.
205
+ - **Respect length budgets.** Finding, Expected, Actual, Impact, Suggested Fix are **each ONE sentence**. Evidence is 2-5 bullets, not prose.
206
+ - **Quote, do not paraphrase.** In Evidence, cite the exact `file:line` with the quoted text in quotes.
207
+ - **Never narrate the session.** The reader wants the defect, not the story. If a sentence starts with "During my run…", delete it.
208
+ - **If unsure it is a real issue, do not submit it.**
209
+
210
+ **Issue body format:**
211
+
212
+ ```markdown
213
+ ## Workflow
214
+ ultracode-goal/{stage}
215
+
216
+ ## Step File
217
+ `skills/ultracode-goal/references/{stage}.md`
218
+
219
+ ## Severity
220
+ `{bug | friction | gap}`
221
+
222
+ ## Fingerprint
223
+ `{fp}`
224
+
225
+ ## Finding
226
+ <!-- ONE sentence: what is the problem? -->
227
+
228
+ ## Expected
229
+ <!-- ONE sentence: what did the stage instruct or imply should happen? -->
230
+
231
+ ## Actual
232
+ <!-- ONE sentence: what did you observe instead? -->
233
+
234
+ ## Evidence
235
+ <!-- 2-5 bulleted `file:line` citations with quoted text. No narrative. -->
236
+ - `skills/ultracode-goal/references/{stage}.md:NN` — "quoted text from the file"
237
+
238
+ ## Impact
239
+ <!-- ONE sentence: what did this cost in THIS run? -->
240
+
241
+ ## Suggested Fix
242
+ <!-- ONE sentence, ONE recommendation. -->
243
+
244
+ ## Environment
245
+ | Field | Value |
246
+ |-------|-------|
247
+ | Date | {ISO date} |
248
+ | OS | {e.g. Ubuntu 24.04, macOS 15.2, Windows 11} |
249
+ | AI Editor | {e.g. Claude Code, Cursor} |
250
+ | Model | {e.g. Claude Opus 4.6, Claude Sonnet 4.6} |
251
+ | Profile | {production \| light} |
252
+ | Run mode | {attended \| headless} |
253
+ | Module Version | {resolved per the order below, else N/A} |
254
+ ```
255
+
256
+ **Module Version resolution order** (first hit wins): `{project-root}/_bmad/ucg/VERSION` → `{skill-root}/VERSION` → `.claude-plugin/marketplace.json` `plugins[0].version` → `package.json` `version` → `N/A`.
257
+
258
+ After creating all issues, display: "{N} issue(s) created on {healthCheckRepo}:" then each URL, then "Workflow complete."
259
+
260
+ ### 5c. Local queue (gh unavailable, autosubmit-off headless, friction/gap default, or [S])
261
+
262
+ Findings that did not go live — `gh` unavailable, an unattended run with autosubmit off, the user declined the friction/gap opt-in, or the user chose **[S]** — are written one file per finding to `{workflow.health_check_queue_path}/`.
263
+
264
+ Even when queuing, **still compute the fingerprint via the script** (so the queue file carries the same `fp`) and **record it** with `--action queued` so a later live submission of the same defect dedups against it:
265
+
266
+ ```
267
+ uv run {skill-root}/scripts/health_check_fp.py record --fp {fp} --cache {seenCachePath} \
268
+ --issue-url "" --action queued --date {YYYY-MM-DD}
269
+ ```
270
+
271
+ **Filename:** `hc-ultracode-goal-{stage}-{YYYYMMDD-HHmmss}.md`.
272
+
273
+ **File content:** the same structured body as §5a, prefixed with YAML frontmatter:
274
+
275
+ ```yaml
276
+ ---
277
+ type: workflow-health-finding
278
+ workflow: ultracode-goal/{stage}
279
+ step_file: skills/ultracode-goal/references/{stage}.md
280
+ severity: {bug | friction | gap}
281
+ fingerprint: {fp-xxxxxxx}
282
+ date: {ISO date}
283
+ ---
284
+ ```
285
+
286
+ When `gh auth status` failed, after writing the files display:
287
+
288
+ "{N} finding(s) saved locally:" then each path, then:
289
+
290
+ "GitHub CLI is not available. To submit these as issues, run:
291
+ `gh issue create --repo {healthCheckRepo} --title \"[title]\" --body-file {file-path}`
292
+
293
+ Or open one at <https://github.com/{healthCheckRepo}/issues/new/choose>
294
+
295
+ Workflow complete."
296
+
297
+ In an unattended run, do not display prompts — just write the files, log the count and paths, and return.
298
+
299
+ ## CRITICAL STEP COMPLETION NOTE
300
+
301
+ This is the **terminal step of Finalize**. After it returns — clean run, findings submitted, queued, or discarded — the ultracode-goal run is **fully done**; there is nothing further to load. **In headless, Finalize emits the final five-key JSON AFTER this step returns** — so this step must never block, prompt, or stall waiting for input, and must never mutate or delay that emit.
302
+
303
+ ## SUCCESS / FAILURE metrics
304
+
305
+ ### SUCCESS
306
+
307
+ - The enabled gate (§0.5) short-circuits to a one-line exit when `health_check_enabled` is false.
308
+ - Clean runs exit immediately with no findings (the most common outcome).
309
+ - Findings cite a specific stage file and section-slug with real `file:line` evidence.
310
+ - In attended runs the [Y]/[N]/[E] gate is presented before any submission.
311
+ - In headless runs the gate is bypassed deterministically: autosubmit-off queues every finding; autosubmit-on live-submits `bug` only and queues friction/gap.
312
+ - Severity routing respected: only `bug` submits live by default; friction/gap need the explicit per-session opt-in (attended) or autosubmit (headless).
313
+ - Fingerprint and seen-cache are computed/managed by `health_check_fp.py` — never inline shell — and are install-mode-invariant.
314
+ - The `fp-*` label is ensured idempotently (`gh label create … || true`) before `gh issue create`.
315
+ - Remote dedup search runs before every live submission; matches get reactions/delta-comments, not duplicates.
316
+ - Local fallback files carry the fingerprint and clear manual-submission instructions.
317
+ - The run ends cleanly and never blocks the headless JSON emit.
318
+
319
+ ### FAILURE
320
+
321
+ - Fabricating issues not actually encountered during the run.
322
+ - Reporting vague issues without a stage-file citation ("the workflow was confusing").
323
+ - Skipping the attended review gate, or creating issues without confirmation in an attended run.
324
+ - **Live-publishing an unreviewed friction/gap finding in an unattended run, or blocking the headless JSON emit on the health check.**
325
+ - Creating a new issue when a matching `fp-*` open issue exists, without explicit [N].
326
+ - Computing the fingerprint with inline shell or the installed path instead of the script's install-mode-invariant form.
327
+ - Using LLM-judged "similarity" in place of the deterministic fingerprint.
328
+ - Not recording to the seen-cache, causing the same user to re-report identical fingerprints.
329
+ - Not providing the local fallback when `gh` is unavailable.
330
+ - Continuing to load steps after this one (it is terminal).
331
+
332
+ **Master rule:** honesty is the only policy. Zero findings is the expected, healthy outcome. Fabricating issues to appear thorough undermines the entire self-improvement system and is a SYSTEM FAILURE.
@@ -0,0 +1,46 @@
1
+ # Stage 1 — Ingest & Scope
2
+
3
+ Resolve **which** Epic this run delivers, locate its BMAD artifacts, lock the profile, and record both to the run's `.decision-log.md`. This is the cheap stage that prevents an expensive autonomous run from targeting the wrong Epic. Converse in `{communication_language}`.
4
+
5
+ The operator is the expert on intent; you are the expert on what the artifacts say. Open the floor — invite them to name the Epic and drop any context (a story ID, a branch, a paste of the Epic body, "the one we discussed yesterday") — then fill only the gaps from the files. With `--yes`, skip this open-floor invite and resolve scope from the inputs and files directly (the hard preflight gate in Stage 2 still runs). In headless (`-H`), skip the conversation: infer scope from the inputs, log every assumption, never prompt.
6
+
7
+ ## First-touch reality check
8
+
9
+ Before resolving anything, confirm this is even a BMAD project. If the `_bmad/` config **and** `sprint-status.yaml` **and** any Epic are **all** absent, this does not look like a BMAD project — say so plainly, point at `bmad-bmb-setup` (to scaffold the module) and `bmad-sprint-planning` (to generate the sprint plan), and **stop**. Never carry a wrong-repo invocation into preflight. In headless (`-H`), emit the blocked JSON (per SKILL.md Headless) with `reason` "not a BMAD project". This is the only absence that hard-stops at Stage 1; once any one of the three is present, proceed and let the resolution rules below judge the rest.
10
+
11
+ ## Resolve the Epic and its artifacts
12
+
13
+ A run targets exactly one Epic, identified by its number/id. Find its artifacts under the resolved config paths (these already contain `{project-root}` — never re-prefix):
14
+
15
+ - **`sprint-status.yaml`** at `{workflow.implementation_artifacts}/sprint-status.yaml` — the authority on which Epics and stories exist and their status (`backlog` / `in-progress` / `ready-for-dev` / `review` / `done`). This is also the file whose mere presence steers TEA test-design to Epic-Level in Stage 2; note its path now, do not create it here.
16
+ - **Epic + story files** under `{workflow.implementation_artifacts}` (the `story_location` BMAD writes to). Stories for an Epic share its number prefix.
17
+ - **PRD and ADR / architecture** under `{planning_artifacts}` — the product and architecture decisions the Epic implements. Stage 2's semantic scan reads these to detect undecided questions; here you only confirm they exist and cover this Epic.
18
+
19
+ Resolution order:
20
+
21
+ 1. If the operator named an Epic, take it. Otherwise read `sprint-status.yaml` and pick the obvious in-flight Epic (single `in-progress`, or the next `backlog` after the last `done`). If genuinely ambiguous, list the candidates with their status and ask. Headless: pick the lowest-numbered not-`done` Epic and log the choice plus the rejected candidates.
22
+ 2. Confirm the Epic has a body (acceptance-bearing stories or an Epic file). A title-only Epic with no stories is not blocking here — Stage 2 generates missing stories/ACs via `bmad-create-story`. Note it so Stage 2 expects remediation.
23
+ 3. **Already-done short-circuit.** If every in-scope story for the resolved Epic is already `done`, do not carry a no-op Epic into preflight: surface "this Epic is already complete — re-run anyway?" and proceed only on a yes. Headless: emit the blocked JSON (per SKILL.md Headless) with `reason` "epic already complete".
24
+ 4. If `sprint-status.yaml` is absent or `{planning_artifacts}` has no PRD/ADR for this Epic, that is not a hard stop at this stage — record the gap; Stage 2 preflight reports it mechanically and the semantic scan judges whether the missing planning artifact is a true RED (undecided product/architecture) or a benign absence.
25
+
26
+ Do not open story or planning files for deep reading here — note their paths so Stage 2 and the TEA stages scan them. Reading them now bloats context ahead of delegation.
27
+
28
+ ## Confirm the profile
29
+
30
+ Profile defaults to **production** — the full TEA chain (test-design + atdd + automate + test-review + nfr + trace + ci) wired as gates. `--light` downscopes to the trace gate only. Headless is always production unless `--light` was passed explicitly.
31
+
32
+ Surface the default and let the operator downscope: "Production (full TEA gates) unless you want `--light` (trace gate only)." One soft-gate touch — "Anything else on scope before I preflight?" — then move on. Don't re-derive the profile later; Stages 3 and 5 read what you lock here.
33
+
34
+ Also note execution mode for the log: **sequential** `/goal` spine (default) or `--parallel` (experimental worktree fan-out). It does not change scope, but the log should carry it so the run is reconstructable after compaction.
35
+
36
+ ## Operator notes
37
+
38
+ This is the operator's last chance to drop a pre-launch hint before they walk away — "watch the auth flow in story 3", "the payments mock is flaky", "story 5's AC is looser than it reads". This is the capture-don't-interrupt case: record each as a named **Operator notes** entry in `.decision-log.md`, tagged with the story it concerns where one is named, rather than letting it float as loose prose. Execute (Stage 4) reads these and surfaces the relevant note into that story's `bmad-dev-story` / review context, so a hint given now actually reaches the unattended run. There may be none — do not invent them; just leave the channel open.
39
+
40
+ ## Record scope + profile
41
+
42
+ The run folder holding `.decision-log.md` is this run's workspace and canonical memory; compaction can drop everything else. Append a dated session entry capturing: the chosen Epic (id + title), the resolved artifact paths (`sprint-status.yaml`, Epic/stories, PRD/ADR), the profile and why (default vs. operator override), execution mode, any gaps noted for Stage 2 to remediate, any **Operator notes** captured (tagged by story), and — in headless — every inference made in place of the operator.
43
+
44
+ ## Progression
45
+
46
+ Proceed to `references/preflight.md` once the decision log records: a single resolved Epic id, the located (or explicitly-noted-missing) `sprint-status.yaml` / Epic-stories / PRD-ADR paths, and the locked profile. If the Epic cannot be resolved to one id, stop and ask (headless: emit the blocked JSON per SKILL.md Headless with `reason` "epic unresolved") — never preflight an ambiguous target.
@@ -0,0 +1,100 @@
1
+ # Stage 2 — Preflight (the autonomy gate)
2
+
3
+ This is the load-bearing gate. After this stage the run goes **unattended**: a sequential `/goal` spine or an experimental `--parallel` worktree fan-out, neither of which takes mid-run human input. So everything that would otherwise stall or ask a question must be resolved **now**, or the run must refuse to launch. The posture is **hard gate with auto-remediation**: the script reports mechanical blockers, this stage clears the remediable ones, you add the semantic judgment the script cannot, and launch happens only when the post-remediation intervention budget is zero. Converse in `{communication_language}`; produced markers/docs use `{document_output_language}`.
4
+
5
+ ## 1. Run the mechanical check
6
+
7
+ ```
8
+ uv run {skill-root}/scripts/preflight_check.py --project-root {project-root} --epic <id> --tea-config {workflow.tea_config_path} --impl-artifacts {workflow.implementation_artifacts} --protected-branch <name> [--protected-branch <name> …]
9
+ ```
10
+
11
+ Qualify the script path with `{skill-root}` (per SKILL.md Conventions a bare `scripts/…` resolves from the skill root, but the conductor runs from the project working directory) so it resolves regardless of cwd. Emit one `--protected-branch <name>` per entry in `{workflow.protected_branches}` (the flag is `action='append'`); without it the check falls back to its hardcoded `main, master` and silently no-ops a `protected_branches` override — the mechanical gate would then fail to flag being on a custom protected branch.
12
+
13
+ `<id>` is the Epic resolved in Stage 1. The script is **plumbing only** — it parses tool versions, git state, and file existence, and reports TEA config flags. It returns JSON:
14
+
15
+ ```
16
+ {green: bool, budget: int,
17
+ blockers: [{id, kind, severity, detail, remediable: bool}],
18
+ checks: {cc_version, goal_ok, workflows_ok, automemory_ok, git_branch, git_clean,
19
+ framework_present, test_artifacts_dirs, sprint_status_present,
20
+ project_context_count, tea_flags}}
21
+ ```
22
+
23
+ `budget` is the count of mechanical blockers. The script does **not** decide semantic intervention — that is your job in step 3. Treat `green: true` from the script as *necessary but not sufficient*.
24
+
25
+ ## 2. Auto-remediation pass
26
+
27
+ Clear each remediable blocker, then **re-run the check** so the budget reflects the fixes. Remediate:
28
+
29
+ - **Test framework absent** (`framework_present: false`): scaffold it via `bmad-testarch-framework` (Playwright/Cypress per `tea_flags`). ATDD in Stage 3 hard-halts without a configured framework — this must exist before launch.
30
+ - **CI quality pipeline absent** (production profile only): if no CI quality pipeline exists yet, scaffold it via `bmad-testarch-ci` **after** the framework exists — `bmad-testarch-ci` halts if the framework is absent, so the strict order is framework → ci. This is one-time infra (force **Create** mode); the pipeline embeds TEA's coverage thresholds so the gate also lives durably in CI, while the per-epic loop gate remains `gate_eval.py` in Stage 5. Skip under `--light`.
31
+ - **Missing acceptance criteria / story** for an in-scope story: generate via `bmad-create-story` so each story carries clear, testable ACs. TEA atdd and trace both need them; a story without ACs has nothing to trace and will stall.
32
+ - **Test-artifacts dirs absent** (`test_artifacts_dirs` incomplete): pre-create the trace/test-design/test-review output dirs (`{workflow.trace_output_dir}` and its siblings under the TEA `test_artifacts` root) so TEA writes land deterministically and `gate_eval.py` finds them in Stage 5.
33
+ - **`project-context.md` count != 1** (`project_context_count`): ensure exactly **one** `project-context.md` exists (generate via `bmad-generate-project-context` if zero; if multiple, this is ambiguous AI-rules state — resolve to one before launch). `{workflow.persistent_facts}` globs this file; duplicates poison Auto Memory grounding.
34
+ - **`sprint-status.yaml` absent** (`sprint_status_present: false`): ensure it is present (via `bmad-sprint-planning`). Its presence makes TEA test-design **auto-select Epic-Level** and skip its interactive System/Epic mode prompt — without it, Stage 3 stalls on a question no one is there to answer.
35
+ - **TEA mode**: force **Create** mode for every TEA workflow this run. Resume/Validate/Edit are interactive and will halt an unattended run. Set/confirm whatever the TEA config exposes (`tea_execution_mode`) so no workflow resumes a prior session.
36
+ - **Secrets / credentials** a story needs (test env keys, API tokens): **interactive** — prompt **once**, now, and capture them out of git. **Headless** — do not prompt; an unresolvable secret becomes a RED blocker (step 4), never a deferred question.
37
+
38
+ Log each remediation to `.decision-log.md` as you do it. After remediating, run the script from step 1 again and read the new `budget`.
39
+
40
+ ## 3. Semantic intervention scan (the part the script cannot do)
41
+
42
+ The script counts mechanical facts; it cannot read a PRD and tell that a product decision is still open, or read an ADR and tell that an architecture choice is unresolved. Do that now. Scan the Epic's stories, PRD, and ADR/architecture (paths noted in Stage 1) for **undecided product or architecture decisions** that an autonomous run would have to *guess*:
43
+
44
+ - open questions, "TBD" / "TODO: decide" / "to be determined" / "(?)" placeholders on a load-bearing requirement,
45
+ - contradictions between PRD and ADR,
46
+ - acceptance criteria that presuppose a decision no artifact actually makes,
47
+ - a story whose "done" is undefinable from the artifacts.
48
+
49
+ Any such item is **RED** — it cannot be auto-remediated, because the fix is a human decision, and an unattended run guessing it produces confidently wrong work. A purely cosmetic gap is not RED. Record each RED finding with its source and the exact decision needed.
50
+
51
+ ## 4. Hard gate
52
+
53
+ **Launch only when ALL hold:**
54
+
55
+ - post-remediation script `budget == 0` (every mechanical blocker cleared),
56
+ - the semantic scan found **no RED** (no undecided product/architecture, no unresolvable secret),
57
+ - **ultracode** session effort and **Auto Mode** are on (gated to Opus/Sonnet 4.6+; required for unattended xhigh + auto-workflow execution).
58
+
59
+ If any fails: write the blockers — mechanical and semantic — to `.decision-log.md` with what each needs to clear, and **STOP**. Do not launch a partially-ready run; a single guessed architecture decision corrupts the whole Epic. In **headless**, instead emit the blocked JSON in the canonical five-key shape (every key always present; `report` and `deferred_work` are `null` because the run blocked before producing them):
60
+
61
+ ```json
62
+ {"status": "blocked",
63
+ "skill": "ultracode-goal",
64
+ "decision_log": "<path to this run's .decision-log.md>",
65
+ "report": null,
66
+ "deferred_work": null,
67
+ "reason": "<first/most-severe blocker, one line>"}
68
+ ```
69
+
70
+ The log carries the full blocker list.
71
+
72
+ ## 5. Arm the environment (only when the gate passes)
73
+
74
+ Do these in order; each must be asserted, not assumed:
75
+
76
+ - **Epic branch.** Create the working branch off `{workflow.epic_branch_prefix}<epic-id>` from a clean tree. Rollback for this run is git (per-story commits, worktree isolation under `--parallel`) — `/rewind` checkpoints miss Bash-driven changes, so the branch is the real undo. If the tree is dirty (`git_clean: false`), resolve before branching.
77
+ - **Hooks.** Idempotently merge the **PreToolUse** guard (`scripts/hooks/guard_pretooluse.py`) and the **Stop** budget hook (`scripts/hooks/budget_stop.py`) into `{project-root}/.claude/settings.local.json` (gitignored, machine-local, honored after the workspace trust dialog). Re-merge every run — do not assume a prior run left them. Then **assert they are active** (present in resolved settings); invariants that live only in memory are context, not enforcement, and memory does not block a `git commit`.
78
+ - **Inject the hook env from the resolved scalars.** Both hooks read config from env first ("env wins so the conductor can inject per run") and fall back to hardcoded defaults (`main/master`, `25`, `1_500_000`, `ultracode/epic-`) otherwise — so a `customize.toml` override of any of these **silently no-ops at the enforcement layer** unless you pass it through. Set these on the hook commands (in the `settings.local.json` hook `command`, e.g. `KEY=value uv run …`) or in the process env the hooks inherit:
79
+ - `ULTRACODE_PROTECTED_BRANCHES={workflow.protected_branches}` (comma-separated)
80
+ - `ULTRACODE_IMPL_ARTIFACTS={workflow.implementation_artifacts}`
81
+ - `ULTRACODE_MAX_TURNS={workflow.max_turns_per_story}`
82
+ - `ULTRACODE_TOKEN_BUDGET={workflow.story_token_budget}`
83
+ - `ULTRACODE_EPIC_BRANCH_PREFIX={workflow.epic_branch_prefix}`
84
+ - **Allowlist.** Pre-populate the tool allowlist with `{workflow.allowlist_commands}` so the unattended run (and any fan-out subagents, which inherit the allowlist) can run tests/lint/build/commit without a permission prompt that no one is there to approve.
85
+
86
+ ### Launch briefing (interactive only)
87
+
88
+ This is the moment the human leaves the loop. Before the first unattended action — **interactive runs only; headless skips this subsection entirely** — surface a one-screen briefing so the operator decides "should I let go right now?" with eyes open:
89
+
90
+ - **What is about to run unattended:** the Epic (id + title), in-scope story count, profile (production / `--light`), and the Epic branch (`{workflow.epic_branch_prefix}<epic-id>`).
91
+ - **Worst-case envelope:** up to `story count × {workflow.max_turns_per_story}` turns — a smart default from context already in hand, so a first-timer can calibrate launch-now vs. launch-after-lunch.
92
+ - **The autonomy line:** state plainly — *"from here I will not ask you anything."*
93
+ - **Kill switch:** Ctrl-C, or delete the Epic branch — and note that `/rewind` will not help (its checkpoints miss the Bash-driven changes that make up the run, the same reason the branch is the real undo).
94
+ - **Where to watch:** the run's `.decision-log.md` (prose account); on the sequential spine, `{workflow.implementation_artifacts}/run-status.json` (the machine-readable heartbeat Execute updates as the spine advances); under `--parallel`, watch the workflow progress view (`/workflows`) and its run log instead — the fan-out's worktree agents do not write `run-status.json`.
95
+
96
+ Then **one soft confirm** to cross the line. With `--yes`, skip the confirm and launch straight through — but **still print the briefing** so the operator has the record. Headless never reaches this subsection.
97
+
98
+ ## Progression
99
+
100
+ Proceed to `references/define-done.md` only after the gate passed AND the Epic branch, both hooks (asserted active), and the allowlist are in place — all recorded in `.decision-log.md`. If the gate did not pass, the run has stopped here (interactive) or returned blocked JSON (headless); there is no progression.