@hegemonart/get-design-done 1.24.2 → 1.26.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 +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +87 -0
- package/README.de.md +679 -0
- package/README.fr.md +679 -0
- package/README.it.md +679 -0
- package/README.ja.md +679 -0
- package/README.ko.md +679 -0
- package/README.md +399 -728
- package/README.zh-CN.md +480 -133
- package/SKILL.md +2 -0
- package/agents/README.md +60 -0
- package/agents/design-reflector.md +43 -0
- package/agents/gdd-intel-updater.md +34 -1
- package/agents/prototype-gate.md +122 -0
- package/agents/quality-gate-runner.md +125 -0
- package/hooks/budget-enforcer.ts +275 -11
- package/hooks/gdd-decision-injector.js +183 -3
- package/hooks/gdd-turn-closeout.js +238 -0
- package/hooks/hooks.json +10 -0
- package/package.json +5 -5
- package/reference/STATE-TEMPLATE.md +41 -0
- package/reference/config-schema.md +30 -0
- package/reference/model-prices.md +40 -19
- package/reference/prices/antigravity.md +21 -0
- package/reference/prices/augment.md +21 -0
- package/reference/prices/claude.md +42 -0
- package/reference/prices/cline.md +23 -0
- package/reference/prices/codebuddy.md +21 -0
- package/reference/prices/codex.md +25 -0
- package/reference/prices/copilot.md +21 -0
- package/reference/prices/cursor.md +21 -0
- package/reference/prices/gemini.md +25 -0
- package/reference/prices/kilo.md +21 -0
- package/reference/prices/opencode.md +23 -0
- package/reference/prices/qwen.md +25 -0
- package/reference/prices/trae.md +23 -0
- package/reference/prices/windsurf.md +21 -0
- package/reference/registry.json +107 -1
- package/reference/runtime-models.md +446 -0
- package/reference/schemas/runtime-models.schema.json +123 -0
- package/scripts/install.cjs +8 -0
- package/scripts/lib/budget-enforcer.cjs +446 -0
- package/scripts/lib/cost-arbitrage.cjs +294 -0
- package/scripts/lib/gdd-state/mutator.ts +454 -0
- package/scripts/lib/gdd-state/parser.ts +351 -1
- package/scripts/lib/gdd-state/types.ts +193 -0
- package/scripts/lib/install/installer.cjs +188 -11
- package/scripts/lib/install/parse-runtime-models.cjs +267 -0
- package/scripts/lib/install/runtimes.cjs +43 -0
- package/scripts/lib/quality-gate-detect.cjs +126 -0
- package/scripts/lib/runtime-detect.cjs +96 -0
- package/scripts/lib/tier-resolver.cjs +311 -0
- package/scripts/validate-frontmatter.ts +138 -1
- package/skills/quality-gate/SKILL.md +222 -0
- package/skills/router/SKILL.md +79 -10
- package/skills/sketch-wrap-up/SKILL.md +47 -2
- package/skills/spike-wrap-up/SKILL.md +41 -2
- package/skills/turn-closeout/SKILL.md +115 -0
- package/skills/verify/SKILL.md +22 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: quality-gate
|
|
3
|
+
description: "Stage 4.5 of the pipeline. Detects, runs, and classifies project quality commands (lint / typecheck / test / visual-regression) between /gdd:design and /gdd:verify; writes the most recent run to STATE.md <quality_gate>. Non-blocking on timeout (warn + proceed); failures spawn design-fixer until the loop converges or max_iters is reached."
|
|
4
|
+
tools: Read, Write, Edit, Bash, Grep, Glob, Task
|
|
5
|
+
color: amber
|
|
6
|
+
model: inherit
|
|
7
|
+
default-tier: haiku
|
|
8
|
+
tier-rationale: "Orchestration of pre-detected commands and a downstream Haiku classifier. The skill itself does no synthesis — Bash runs do all the work, the classifier agent owns the routing decision."
|
|
9
|
+
size_budget: M
|
|
10
|
+
parallel-safe: conditional-on-touches
|
|
11
|
+
typical-duration-seconds: 180
|
|
12
|
+
reads-only: false
|
|
13
|
+
writes:
|
|
14
|
+
- ".design/STATE.md"
|
|
15
|
+
- ".design/events.jsonl"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
@reference/shared-preamble.md
|
|
19
|
+
|
|
20
|
+
# quality-gate
|
|
21
|
+
|
|
22
|
+
## Role
|
|
23
|
+
|
|
24
|
+
You are the Stage 4.5 gate that runs between `/gdd:design` and `/gdd:verify`. You answer one question: *does this project's own quality tooling pass against the current working tree?*
|
|
25
|
+
|
|
26
|
+
You are NOT a design checker, an a11y checker, or a verifier. You are a thin façade over the project's existing `lint` / `typecheck` / `test` / visual-regression scripts. You exist so that the verify stage can refuse entry when those scripts fail (and so that the fix loop can be bounded and observable).
|
|
27
|
+
|
|
28
|
+
You write exactly two artifacts:
|
|
29
|
+
1. The `<quality_gate>` block in `.design/STATE.md` (one most-recent `<run/>` element).
|
|
30
|
+
2. Lifecycle events in `.design/events.jsonl` (per Step 6 below).
|
|
31
|
+
|
|
32
|
+
You never block on timeout. You never block on a "skipped" detection result. You only mark `status="fail"` when the fix loop reaches `max_iters` without converging — and even then it is the verify stage's job to refuse entry; YOU exit successfully so the user sees the report regardless.
|
|
33
|
+
|
|
34
|
+
## Configuration Surface
|
|
35
|
+
|
|
36
|
+
Read once at start, from `.design/config.json` (all keys optional; defaults documented):
|
|
37
|
+
|
|
38
|
+
| Key | Default | Purpose |
|
|
39
|
+
|-----|---------|---------|
|
|
40
|
+
| `quality_gate.commands` | `null` | Authoritative list of commands. When provided, skips auto-detection. Each entry is a string the shell can run (e.g. `"npm run lint"`). |
|
|
41
|
+
| `quality_gate.timeout_seconds` | `600` | Total wall-clock budget for Step 2. On timeout: warn + proceed (D-07). |
|
|
42
|
+
| `quality_gate.max_iters` | `3` | Hard cap on Step 4 fix-loop iterations. |
|
|
43
|
+
|
|
44
|
+
Missing config file is not an error — defaults apply.
|
|
45
|
+
|
|
46
|
+
## Step 1 — Detection chain
|
|
47
|
+
|
|
48
|
+
Per D-06, resolve the active command list with this 3-tier fallback. Stop at the first tier that produces ≥ 1 command:
|
|
49
|
+
|
|
50
|
+
### Tier 1 — Authoritative config
|
|
51
|
+
|
|
52
|
+
If `.design/config.json` carries `quality_gate.commands` and the array is non-empty, use it verbatim. Skip Tier 2 and Tier 3.
|
|
53
|
+
|
|
54
|
+
### Tier 2 — Auto-detect from `package.json#scripts`
|
|
55
|
+
|
|
56
|
+
If `package.json` exists at the project root, read its `scripts` object. Match script names against the following allowlist (case-sensitive, exact match unless noted):
|
|
57
|
+
|
|
58
|
+
| Script name | Notes |
|
|
59
|
+
|-------------|-------|
|
|
60
|
+
| `lint` | Always include if present. |
|
|
61
|
+
| `typecheck` | Always include if present. |
|
|
62
|
+
| `tsc` | Include if `typecheck` is absent (substitute, not duplicate). |
|
|
63
|
+
| `test` | Include if present. |
|
|
64
|
+
| `chromatic` | Include if present (visual-regression). |
|
|
65
|
+
| `test:visual` | Include if present (visual-regression). |
|
|
66
|
+
|
|
67
|
+
**Excluded by name** (intentionally — too slow for a Stage 4.5 gate):
|
|
68
|
+
- `test:e2e`
|
|
69
|
+
- `test:integration` (only if a separate `test` exists)
|
|
70
|
+
- Any script whose name starts with `dev:`, `build:`, `start:`.
|
|
71
|
+
|
|
72
|
+
For each matched script, the command to run is `npm run <script-name>` (use `pnpm run` or `yarn` only if the project's root carries a corresponding lockfile and the user's `.design/config.json` lists `quality_gate.package_manager`; otherwise default to `npm run` for portability).
|
|
73
|
+
|
|
74
|
+
If `package.json` does not exist, or `scripts` is empty, or no allowlisted name matches, advance to Tier 3.
|
|
75
|
+
|
|
76
|
+
### Tier 3 — Skip with notice
|
|
77
|
+
|
|
78
|
+
Emit a `quality_gate_skipped` event with `reason: "no commands resolved"` (Step 6). Write a `<run/>` element with `status="skipped"`, `commands_run=""`, `iteration=0`, `started_at` and `completed_at` set to the same timestamp. Exit successfully with status `skipped`. The verify-entry gate (Plan 25-07 territory) does NOT block on `skipped`.
|
|
79
|
+
|
|
80
|
+
## Step 2 — Parallel run
|
|
81
|
+
|
|
82
|
+
Open Step 2 by emitting `quality_gate_started` with the resolved command list (Step 6).
|
|
83
|
+
|
|
84
|
+
For each command produced by Step 1, spawn a **separate** `Bash` invocation; collect `{command, exit_code, stdout, stderr}` for each. Run them concurrently — the gate's wall-clock budget is the slowest command, not their sum.
|
|
85
|
+
|
|
86
|
+
The combined wall-clock budget is `quality_gate.timeout_seconds` (default 600). If the budget elapses before all commands complete:
|
|
87
|
+
|
|
88
|
+
1. Emit `quality_gate_timeout` with the names of commands that did not finish.
|
|
89
|
+
2. Mark `status="timeout"`, `commands_run=<comma-joined attempted names>`, and treat unfinished commands as having no failure to classify.
|
|
90
|
+
3. Skip Step 3 / Step 4 (no fix loop on timeout — it would just compound the slowness).
|
|
91
|
+
4. Proceed to Step 5 (STATE write) and Step 6 (final event).
|
|
92
|
+
5. **Exit successfully.** Verify entry treats `timeout` as a warn, not a block.
|
|
93
|
+
|
|
94
|
+
If all commands complete within budget, advance to Step 3.
|
|
95
|
+
|
|
96
|
+
## Step 3 — Classification
|
|
97
|
+
|
|
98
|
+
Spawn the `quality-gate-runner` agent via the `Task` tool. Pass an input payload of the shape:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"outputs": [
|
|
103
|
+
{"command": "npm run lint", "exit_code": 0, "stderr": ""},
|
|
104
|
+
{"command": "npm run typecheck", "exit_code": 1, "stderr": "<verbatim stderr>"},
|
|
105
|
+
{"command": "npm run test", "exit_code": 0, "stderr": ""}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The agent emits a single JSON object on stdout (see `agents/quality-gate-runner.md`):
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"status": "pass" | "fail",
|
|
115
|
+
"classified_failures": {
|
|
116
|
+
"lint": ["…"],
|
|
117
|
+
"type": ["…"],
|
|
118
|
+
"test": ["…"],
|
|
119
|
+
"visual": ["…"]
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
When `status === "pass"`, advance directly to Step 5 with `iteration` equal to the current loop counter (starts at `1` on the first pass).
|
|
125
|
+
|
|
126
|
+
When `status === "fail"`, advance to Step 4.
|
|
127
|
+
|
|
128
|
+
## Step 4 — Fix loop (D-08)
|
|
129
|
+
|
|
130
|
+
If `iteration >= quality_gate.max_iters` (default 3), the loop is exhausted:
|
|
131
|
+
- Emit `quality_gate_fail` with the final classified failures.
|
|
132
|
+
- Mark `status="fail"`, persist the final `iteration`, and proceed to Step 5.
|
|
133
|
+
- **Exit successfully.** Verify entry refuses on `status="fail"`; YOU do not throw.
|
|
134
|
+
|
|
135
|
+
Otherwise, increment `iteration` and emit `quality_gate_iteration` with the current value. Spawn the existing `design-fixer` agent (Phase 5) via `Task` with classified failures as context — pass the same shape produced by Step 3 plus the original `outputs[]` for verbatim error context. After the fixer returns, restart from Step 2 (re-run all commands; do not prune to "only the previously failing ones" — fixes can introduce regressions in formerly-clean commands).
|
|
136
|
+
|
|
137
|
+
The loop terminates when either Step 3 returns `status="pass"` or `iteration` reaches `max_iters`.
|
|
138
|
+
|
|
139
|
+
## Step 5 — STATE write
|
|
140
|
+
|
|
141
|
+
Open `.design/STATE.md`. Mutate the parsed state's `quality_gate` field to:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
{
|
|
145
|
+
run: {
|
|
146
|
+
started_at: <ISO 8601 — captured at Step 2 entry>,
|
|
147
|
+
completed_at: <ISO 8601 — now>,
|
|
148
|
+
status: <"pass" | "fail" | "timeout" | "skipped">,
|
|
149
|
+
iteration: <final loop counter>,
|
|
150
|
+
commands_run: <comma-joined names of commands that completed>,
|
|
151
|
+
extra_attrs: {},
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Persist via `mcp__gdd_state__set_quality_gate` (the underlying mutator wiring is named in this contract; the SDK MCP layer wraps every mutator method, so the surface inherits free from the parser/mutator extension landed in this plan). Until the MCP tool exists (Plan 25-07 surfaces it in the verify-stage integration), use the `apply()` mutator from `scripts/lib/gdd-state/mutator.ts` directly:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
apply(raw, (state) => {
|
|
160
|
+
state.quality_gate = { run };
|
|
161
|
+
return state;
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Either path is acceptable. The on-disk shape is identical.
|
|
166
|
+
|
|
167
|
+
## Step 6 — Event emission (D-09)
|
|
168
|
+
|
|
169
|
+
Emit lifecycle events to `.design/events.jsonl` via the existing `appendEvent()` surface exported from `scripts/lib/event-stream/index.ts` — the same module Phase 22 telemetry, the budget-enforcer, the read-injection scanner, and the gdd-state MCP server already write through. Do not roll a bespoke writer; the singleton in `event-stream/index.ts` is persist-first / broadcast-second and never throws on the persist path, which is the contract this skill relies on.
|
|
170
|
+
|
|
171
|
+
Import shape:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { appendEvent } from '../../scripts/lib/event-stream/index.ts';
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Each emission is a single `appendEvent({...})` call with `type` set to one of the six names in the table below. Pass the event-specific payload fields verbatim — `appendEvent` stamps `_meta` (pid, host, source) and the JSONL writer captures the canonical `ts` from the writer surface. The `cycle` and `stage` fields are stamped by the same path used elsewhere in Phase 22+ (consumers match on `type`; treat `ts`, `cycle`, `stage` as injected, not caller-supplied).
|
|
178
|
+
|
|
179
|
+
One event per JSONL line. Schema and lifecycle map:
|
|
180
|
+
|
|
181
|
+
| Event | When (lifecycle position) | Required fields |
|
|
182
|
+
|-------|---------------------------|-----------------|
|
|
183
|
+
| `quality_gate_started` | Step 2 entry — fired ONCE per skill invocation, immediately before any `Bash` spawn. Carries the resolved command list from Step 1 so downstream telemetry can correlate `started` → terminal event. | `commands` (string[]), `timeout_seconds` (number), `max_iters` (number) |
|
|
184
|
+
| `quality_gate_iteration` | Step 4 entry — fired ONCE per retry, with `iteration` set to the new (post-increment) loop counter. The first run is implicit (covered by `started`); only retries `≥ 2` emit `iteration`. | `iteration` (int ≥ 2) |
|
|
185
|
+
| `quality_gate_pass` | Step 3 returned `status: "pass"` — terminal happy path. Fires before Step 5 (STATE write) so a consumer tailing the stream sees the verdict before the on-disk run record. | `iteration` (final loop counter), `commands_run` (string[]) |
|
|
186
|
+
| `quality_gate_fail` | Step 4 reached `max_iters` without convergence — terminal failure path. The verify-entry gate (Step 2.5 of `skills/verify/SKILL.md`) is the sole consumer that *acts* on this; this skill exits successfully regardless. | `iteration` (final loop counter, equal to `max_iters`), `classified_failures` (object — same shape as `quality-gate-runner` agent output) |
|
|
187
|
+
| `quality_gate_timeout` | Step 2 wall-clock budget elapsed — terminal warn path (per D-07 verify treats this as a warning, not a block). Fires before Step 5 STATE write, same ordering as `pass`/`fail`. | `unfinished_commands` (string[]) |
|
|
188
|
+
| `quality_gate_skipped` | Step 1 Tier 3 fired (no commands resolved) — terminal no-op path. Fires before the synthetic `<run/>` is written to STATE.md. | `reason` (string — e.g. `"no commands resolved"`) |
|
|
189
|
+
|
|
190
|
+
All six events carry the standard `ts`, `cycle`, `stage` fields injected by `appendEvent` / the writer. Do not invent additional event names — the verify-entry gate, reflector, and Phase 22 telemetry consumers match on this exact list. Do not emit any of these names from any path other than the lifecycle positions above (e.g., do not emit `quality_gate_started` again on a Step 4 retry — that's what `quality_gate_iteration` is for).
|
|
191
|
+
|
|
192
|
+
**Failure-mode contract:** `appendEvent()` swallows persist failures internally. If the writer cannot open `.design/events.jsonl`, the skill MUST still proceed — the event stream is observability, not correctness. The STATE.md write in Step 5 is the durable record consumers MUST rely on; events.jsonl is the supplementary timeline.
|
|
193
|
+
|
|
194
|
+
## Output Contract
|
|
195
|
+
|
|
196
|
+
Emit a single JSON object on stdout summarizing the run for the caller:
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"status": "pass",
|
|
201
|
+
"iteration": 1,
|
|
202
|
+
"commands_run": ["npm run lint", "npm run typecheck", "npm run test"],
|
|
203
|
+
"started_at": "2026-04-29T10:00:00Z",
|
|
204
|
+
"completed_at": "2026-04-29T10:01:42Z"
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Schema:
|
|
209
|
+
- `status` — `pass | fail | timeout | skipped`.
|
|
210
|
+
- `iteration` — final loop counter; `0` for `skipped`.
|
|
211
|
+
- `commands_run` — array of command strings actually executed.
|
|
212
|
+
- `started_at` / `completed_at` — ISO 8601, copied from the STATE write.
|
|
213
|
+
|
|
214
|
+
The skill exits with shell exit code `0` on every terminal status — including `fail`. The verify-entry gate is the sole consumer of the `fail` status; this skill never throws to the orchestrator.
|
|
215
|
+
|
|
216
|
+
## Constraints
|
|
217
|
+
|
|
218
|
+
- **Do not** prune the command list across iterations — always re-run everything in Step 2.
|
|
219
|
+
- **Do not** spawn `quality-gate-runner` more than once per loop iteration. Spawn `design-fixer` more than once if and only if the loop iterates.
|
|
220
|
+
- **Do not** read or write any STATE block other than `<quality_gate>` and `<position>` (the latter only as required by the standard write contract; the gate is a checkpoint, not a stage transition, so `<position>` updates are limited to `last_checkpoint`).
|
|
221
|
+
- **Do not** invoke verify or design — Stage 4.5 sits strictly between them.
|
|
222
|
+
- Treat exit codes via the standard convention: `0` = clean; non-zero = failure to be classified. Do not interpret stderr content for the pass/fail decision — the agent does that classification, you do not.
|
package/skills/router/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gdd-router
|
|
3
|
-
description: "Routes a /gdd command to fast|quick|full path and returns {path, model_tier_overrides, estimated_cost_usd, cache_hits}. Deterministic — no model call. Invoked once at command entry before any Agent spawn. Read by hooks/budget-enforcer.js."
|
|
3
|
+
description: "Routes a /gdd command to fast|quick|full path + S|M|L|XL complexity_class and returns {path, complexity_class, model_tier_overrides, resolved_models, estimated_cost_usd, cache_hits}. Deterministic — no model call. Invoked once at command entry before any Agent spawn. Read by hooks/budget-enforcer.js."
|
|
4
4
|
argument-hint: "<intent-string> [<target-artifacts-csv>]"
|
|
5
5
|
tools: Read, Bash, Grep
|
|
6
6
|
---
|
|
@@ -18,25 +18,66 @@ You are a deterministic routing skill. You do not spawn agents. You read `.desig
|
|
|
18
18
|
```json
|
|
19
19
|
{
|
|
20
20
|
"path": "fast",
|
|
21
|
+
"complexity_class": "M",
|
|
21
22
|
"model_tier_overrides": {"design-verifier": "haiku"},
|
|
23
|
+
"resolved_models": {
|
|
24
|
+
"design-reflector": "gpt-5",
|
|
25
|
+
"design-context-checker": "gpt-5-nano",
|
|
26
|
+
"design-verifier": "gpt-5-nano"
|
|
27
|
+
},
|
|
22
28
|
"estimated_cost_usd": 0.034,
|
|
23
29
|
"cache_hits": ["design-context-builder:abc123"]
|
|
24
30
|
}
|
|
25
31
|
```
|
|
26
|
-
- `path` enum: `fast` (single Haiku + no checkers), `quick` (Sonnet mappers + Haiku verify), `full` (Opus planners + full quality gates).
|
|
27
|
-
- `
|
|
32
|
+
- `path` enum: `fast` (single Haiku + no checkers), `quick` (Sonnet mappers + Haiku verify), `full` (Opus planners + full quality gates). Stays unchanged for back-compat per D-05.
|
|
33
|
+
- `complexity_class` enum: `S | M | L | XL` (Phase 25 / D-04, D-05). Additive to `path` — existing consumers reading only `path` keep working. Mapping is documented in the Path Selection Heuristic table below.
|
|
34
|
+
- `model_tier_overrides` merges agent frontmatter `default-tier` with `.design/budget.json.tier_overrides` — budget.json wins per D-04. Enum stays `opus|sonnet|haiku` for back-compat across all 14 runtimes; consumers that need the **concrete** model name for the active runtime read `resolved_models` instead.
|
|
35
|
+
- `resolved_models` is a per-agent map of concrete model IDs for the runtime in use (Phase 26 / D-07). Keys are agent names; values are runtime-specific model strings (e.g. `"gpt-5"` under codex, `"gemini-2.5-pro"` under gemini, `"claude-opus-4-7"` under claude) or `null` when the resolver can supply no model (missing tier-map row, missing tier on the row). Additive to `model_tier_overrides` — existing consumers reading the tier-name map keep working unchanged; new consumers (budget-enforcer cost computation, cost telemetry, bandit posterior store) read `resolved_models` for runtime-correct cost. See **Runtime-aware model resolution** below for the computation contract.
|
|
28
36
|
- `estimated_cost_usd` is the sum of per-spawn estimates using the D-06 formula and `reference/model-prices.md`.
|
|
29
37
|
- `cache_hits` is a list of `{agent}:{input-hash}` strings that exist in `.design/cache-manifest.json` and are within TTL; emitting a hit lets the hook short-circuit that spawn per D-05.
|
|
30
38
|
|
|
39
|
+
### Output schema versioning
|
|
40
|
+
|
|
41
|
+
The router output contract is additive across phases. The current shape (Phase 26, v1.26.0) carries:
|
|
42
|
+
|
|
43
|
+
| Field | Added in | Status |
|
|
44
|
+
|-------|----------|--------|
|
|
45
|
+
| `path` | v1.10.1 (10.1-01) | stable |
|
|
46
|
+
| `model_tier_overrides` | v1.10.1 (10.1-01) | stable, enum unchanged |
|
|
47
|
+
| `estimated_cost_usd` | v1.10.1 (10.1-01) | stable |
|
|
48
|
+
| `cache_hits` | v1.10.1 (10.1-01) | stable |
|
|
49
|
+
| `complexity_class` | v1.25.0 (25-02) | stable, additive |
|
|
50
|
+
| `resolved_models` | v1.26.0 (26-04) | stable, additive |
|
|
51
|
+
|
|
52
|
+
Existing consumers reading any subset of the older fields keep working unchanged across these bumps — the schema is a strict superset at every phase boundary. New fields are documented inline in this skill rather than in a separate JSON-schema file (the SKILL is the contract — same convention Phase 25 followed for `complexity_class`).
|
|
53
|
+
|
|
31
54
|
## Path Selection Heuristic
|
|
32
55
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
|
36
|
-
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
56
|
+
The router emits both `path` (legacy 3-tier enum) and `complexity_class` (Phase 25 4-tier enum). The canonical mapping is:
|
|
57
|
+
|
|
58
|
+
| complexity_class | path | Behavior |
|
|
59
|
+
|------------------|------|----------|
|
|
60
|
+
| `S` | `fast` (short-circuited) | Skip router itself, skip cache-manager, skip telemetry write. Deterministic no-op decision. |
|
|
61
|
+
| `M` | `fast` | Single Haiku + no checkers. |
|
|
62
|
+
| `L` | `quick` | Sonnet mappers + Haiku verify. |
|
|
63
|
+
| `XL` | `full` | Opus planners + full quality gates. Recommends worktree-isolation default + mandatory inter-stage checkpoint + reflector auto-spawn. |
|
|
64
|
+
|
|
65
|
+
Bucket assignment:
|
|
66
|
+
|
|
67
|
+
| Signal | complexity_class | path |
|
|
68
|
+
|--------|------------------|------|
|
|
69
|
+
| Command is `/gdd:help`, `/gdd:stats`, `/gdd:note`, `/gdd:health`, single-Haiku skill | `S` | `fast` (short-circuited — see below) |
|
|
70
|
+
| Command is `/gdd:scan`, `/gdd:brief`, `/gdd:sketch`, `/gdd:spike`, `/gdd:fast` | `M` | `fast` |
|
|
71
|
+
| Command spawns exactly one agent (no orchestration), not in S list | `M` | `fast` |
|
|
72
|
+
| Command is `/gdd:explore`, `/gdd:discover`, standalone `/gdd:verify`, standalone `/gdd:plan` | `L` | `quick` |
|
|
73
|
+
| Command spawns parallel mappers but no planners/auditors (`/gdd:discover` in `--auto` mode) | `L` | `quick` |
|
|
74
|
+
| Command is `/gdd:next`, `/gdd:do`, `/gdd:autonomous`, end-to-end Brief→Verify, anything spawning planners + auditors + verifiers in series | `XL` | `full` |
|
|
75
|
+
| Command spawns planners, auditors, verifiers, or integration-checkers (`/gdd:plan`, `/gdd:verify`, `/gdd:audit`) and is not standalone | `XL` | `full` |
|
|
76
|
+
| `--dry-run` flag present on any command | downgrade one tier (XL→L→M→S; `path` follows the mapping table) |
|
|
77
|
+
|
|
78
|
+
### S-class short-circuit
|
|
79
|
+
|
|
80
|
+
When `complexity_class` would be `S`, the router itself **does not run** for that invocation — the deterministic skip list is encoded in the `/gdd:*` SKILL.md entry of the matching command. The budget-enforcer hook treats "no router decision payload + matching command name" as the S-class signal and skips enforcement entirely (no telemetry row, no cache lookup, no event emission). When the router *is* invoked explicitly (e.g., debugging) it still emits `complexity_class: "S"` in the JSON for observability, but the runtime path is the no-op.
|
|
40
81
|
|
|
41
82
|
## Cost Estimation Algorithm
|
|
42
83
|
|
|
@@ -50,6 +91,34 @@ for each agent in planned spawn graph:
|
|
|
50
91
|
return total
|
|
51
92
|
```
|
|
52
93
|
|
|
94
|
+
## Runtime-aware model resolution
|
|
95
|
+
|
|
96
|
+
The router emits `resolved_models` alongside `model_tier_overrides` so downstream consumers (budget-enforcer cost computation, Phase 22 cost telemetry, Phase 23.5 bandit posterior store) can read the **concrete model ID** for the active runtime without re-deriving it from the tier name. The resolution is per-agent and additive — `model_tier_overrides` keeps its `opus|sonnet|haiku` enum for back-compat across all 14 runtimes, and `resolved_models` runs the runtime-specific translation on top of it.
|
|
97
|
+
|
|
98
|
+
Computation contract (per D-07):
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
runtime = runtimeDetect.detect() ?? 'claude'
|
|
102
|
+
for each agent in planned spawn graph:
|
|
103
|
+
tier = resolve_tier(agent) # same merge as model_tier_overrides
|
|
104
|
+
resolved_models[agent] = tierResolver.resolve(runtime, tier)
|
|
105
|
+
# → concrete model string OR null
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Implementation surfaces (Phase 26 / Wave A):
|
|
109
|
+
|
|
110
|
+
- `scripts/lib/runtime-detect.cjs` — `detect() → runtime-id | null`. Reads the same `*_CONFIG_DIR` / `*_HOME` env-vars Phase 24's installer uses (single source of truth in `scripts/lib/install/runtimes.cjs`). Returns `null` when no recognized runtime env-var is set; the router falls back to `'claude'` so the resolver always has a runtime ID to work with.
|
|
111
|
+
- `scripts/lib/tier-resolver.cjs` — `resolve(runtime, tier, opts?) → model | null`. Translates `opus|sonnet|haiku` to the concrete model the runtime understands using the `reference/runtime-models.md` mapping (Phase 26 / Wave A). Fallback chain (D-04): runtime-specific entry → `claude` row default with `tier_resolution_fallback` event → `null` with `tier_resolution_failed` event. Never throws; `null` is a valid output the consumer must handle.
|
|
112
|
+
|
|
113
|
+
Per-agent emission rules:
|
|
114
|
+
|
|
115
|
+
- One key per agent in the planned spawn graph (same key set the cost-estimation loop iterates over). Keys MUST match agent names exactly so consumers can join `resolved_models` against `model_tier_overrides` and the spawn graph by name.
|
|
116
|
+
- Value is the concrete model string returned by `tier-resolver.resolve(runtime, tier)`.
|
|
117
|
+
- When the resolver returns `null` (missing tier-map row, missing tier, garbage input), the value is JSON `null` — NOT omitted, NOT the empty string. Consumers (budget-enforcer, telemetry) MUST handle `null`: typically by skipping the cost row for that spawn and emitting their own diagnostic event, never by crashing.
|
|
118
|
+
- When `complexity_class` is `S` and the router itself short-circuits (see **S-class short-circuit** above), no payload is emitted at all and `resolved_models` does not exist for that invocation — the budget-enforcer's "no router decision payload" branch already handles this case.
|
|
119
|
+
|
|
120
|
+
Back-compat assertion: a router invocation in a Claude runtime (or any environment where `runtime-detect.detect()` returns `null` and the router falls back to `'claude'`) produces `resolved_models` values that are the canonical Anthropic model IDs (`claude-opus-4-7`, `claude-sonnet-4-6`, `claude-haiku-4-5`) for the corresponding tiers. Pre-Phase-26 consumers that ignore `resolved_models` see the same `model_tier_overrides` they always saw (Plan 26-09 owns the runtime fixture diff that asserts this).
|
|
121
|
+
|
|
53
122
|
## Cache-Hit Detection
|
|
54
123
|
|
|
55
124
|
Delegate to `skills/cache-manager/SKILL.md` (Plan 10.1-02). The router lists candidate `{agent}:{input-hash}` tuples; the cache-manager confirms freshness against TTL from `budget.json.cache_ttl_seconds`.
|
|
@@ -62,11 +62,54 @@ Write `.design/sketches/<slug>/WINNER.md`:
|
|
|
62
62
|
**Project skill written to**: ./.claude/skills/design-<area>-conventions.md
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
## Step 7 —
|
|
65
|
+
## Step 7 — Append D-XX + `<prototyping>` outcome to STATE.md
|
|
66
|
+
|
|
67
|
+
Two coupled writes to `.design/STATE.md`. Both must succeed together so the
|
|
68
|
+
sketch resolution is discoverable from both `<decisions>` (read by all
|
|
69
|
+
downstream stages) and `<prototyping>` (read by planner-specific context via
|
|
70
|
+
the decision-injector).
|
|
71
|
+
|
|
72
|
+
Compute `D-XX` as the highest existing `D-NN` in `<decisions>` plus 1
|
|
73
|
+
(scan `<decisions>` for `D-\d+:` entries and take `max + 1`, zero-padded
|
|
74
|
+
to two digits — e.g. existing `D-07` → new entry is `D-08`). Use the same
|
|
75
|
+
`D-XX` value in both writes below.
|
|
76
|
+
|
|
77
|
+
**Write 1 — append a numbered decision under `<decisions>`:**
|
|
78
|
+
```
|
|
79
|
+
D-XX: sketch/<slug> — winner: variant-N — <one-line rationale> (locked)
|
|
80
|
+
Source: .design/sketches/<slug>/WINNER.md
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Write 2 — append a `<sketch>` child element under `<prototyping>`:**
|
|
84
|
+
```
|
|
85
|
+
<sketch slug="<slug>" cycle="<cycle>" decision="D-XX" status="resolved"/>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`<cycle>` is the current cycle id from `.design/STATE.md` frontmatter
|
|
89
|
+
(`cycle:` field; empty string is valid for Wave A single-cycle projects).
|
|
90
|
+
|
|
91
|
+
If a `<prototyping>` block does not yet exist in STATE.md, materialize it
|
|
92
|
+
between `<must_haves>` and `<connections>` per the STATE template, then
|
|
93
|
+
append the `<sketch …/>` line as its first child. The block is omitted on
|
|
94
|
+
fresh files and only appears once the first sketch / spike / skipped entry
|
|
95
|
+
lands.
|
|
96
|
+
|
|
97
|
+
If MCP `gdd_state` tools are available, prefer the typed mutators (these
|
|
98
|
+
wrap `scripts/lib/gdd-state/mutator.ts` and emit byte-identical output to
|
|
99
|
+
manual edits):
|
|
100
|
+
```
|
|
101
|
+
- mcp__gdd_state__add_decision({id: "D-XX", text: "sketch/<slug> — winner: variant-N — <rationale>", status: "locked"})
|
|
102
|
+
- mcp__gdd_state__add_prototyping({type: "sketch", slug: "<slug>", cycle: "<cycle>", decision: "D-XX", status: "resolved"})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Without MCP, edit `.design/STATE.md` directly with `Read` + `Write`,
|
|
106
|
+
inserting the two lines into the correct blocks.
|
|
107
|
+
|
|
108
|
+
## Step 8 — Update sketches SUMMARY.md
|
|
66
109
|
|
|
67
110
|
Append entry to `.design/sketches/SUMMARY.md` (create if missing):
|
|
68
111
|
```markdown
|
|
69
|
-
- <slug> (YYYY-MM-DD) — winner: variant-N — area: <area> — <one-line rationale>
|
|
112
|
+
- <slug> (YYYY-MM-DD) — winner: variant-N — area: <area> — D-XX — <one-line rationale>
|
|
70
113
|
```
|
|
71
114
|
|
|
72
115
|
## After writing
|
|
@@ -76,6 +119,8 @@ Append entry to `.design/sketches/SUMMARY.md` (create if missing):
|
|
|
76
119
|
Slug: <slug>
|
|
77
120
|
Winner: variant-N
|
|
78
121
|
Area: <area>
|
|
122
|
+
Decision recorded: D-XX
|
|
123
|
+
Prototyping entry: <sketch slug="<slug>" cycle="<cycle>" decision="D-XX" status="resolved"/>
|
|
79
124
|
Project skill: ./.claude/skills/design-<area>-conventions.md
|
|
80
125
|
━━━━━━━━━━━━━━━━━━━━━
|
|
81
126
|
```
|
|
@@ -53,9 +53,47 @@ D-XX: spike/<slug> — <verdict> — <recommendation>
|
|
|
53
53
|
Source: .design/spikes/<slug>/FINDINGS.md
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
(Increment D-XX from the highest existing number
|
|
56
|
+
(Increment D-XX from the highest existing number — scan `<decisions>` for
|
|
57
|
+
`D-\d+:` entries and take `max + 1`, zero-padded to two digits.)
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
If MCP `gdd_state` tools are available, prefer the typed mutator:
|
|
60
|
+
```
|
|
61
|
+
- mcp__gdd_state__add_decision({id: "D-XX", text: "spike/<slug> — <verdict> — <recommendation> — <one-line rationale>", status: "locked"})
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Step 6 — Append `<prototyping>` outcome to STATE.md
|
|
65
|
+
|
|
66
|
+
Coupled with the Step 5 decision write — both must succeed together so the
|
|
67
|
+
spike resolution is discoverable from both `<decisions>` (read by all
|
|
68
|
+
downstream stages) and `<prototyping>` (read by planner-specific context via
|
|
69
|
+
the decision-injector). Use the **same `D-XX`** as Step 5.
|
|
70
|
+
|
|
71
|
+
Append a `<spike>` child element under `<prototyping>` in `.design/STATE.md`:
|
|
72
|
+
```
|
|
73
|
+
<spike slug="<slug>" cycle="<cycle>" decision="D-XX" verdict="yes|no|partial" status="resolved"/>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`<cycle>` is the current cycle id from `.design/STATE.md` frontmatter
|
|
77
|
+
(`cycle:` field; empty string is valid for Wave A single-cycle projects).
|
|
78
|
+
`verdict` is the answer from Step 3 (`yes` / `no` / `partial`).
|
|
79
|
+
|
|
80
|
+
If a `<prototyping>` block does not yet exist in STATE.md, materialize it
|
|
81
|
+
between `<must_haves>` and `<connections>` per the STATE template, then
|
|
82
|
+
append the `<spike …/>` line as its first child. The block is omitted on
|
|
83
|
+
fresh files and only appears once the first sketch / spike / skipped entry
|
|
84
|
+
lands.
|
|
85
|
+
|
|
86
|
+
If MCP `gdd_state` tools are available, prefer the typed mutator (it wraps
|
|
87
|
+
`scripts/lib/gdd-state/mutator.ts` and emits byte-identical output to manual
|
|
88
|
+
edits):
|
|
89
|
+
```
|
|
90
|
+
- mcp__gdd_state__add_prototyping({type: "spike", slug: "<slug>", cycle: "<cycle>", decision: "D-XX", verdict: "<verdict>", status: "resolved"})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Without MCP, edit `.design/STATE.md` directly with `Read` + `Write`,
|
|
94
|
+
inserting the line into the `<prototyping>` block.
|
|
95
|
+
|
|
96
|
+
## Step 7 — Update spikes SUMMARY.md
|
|
59
97
|
|
|
60
98
|
Append entry to `.design/spikes/SUMMARY.md` (create if missing):
|
|
61
99
|
```markdown
|
|
@@ -69,6 +107,7 @@ Append entry to `.design/spikes/SUMMARY.md` (create if missing):
|
|
|
69
107
|
Slug: <slug>
|
|
70
108
|
Verdict: <verdict>
|
|
71
109
|
Decision recorded: D-XX
|
|
110
|
+
Prototyping entry: <spike slug="<slug>" cycle="<cycle>" decision="D-XX" verdict="<verdict>" status="resolved"/>
|
|
72
111
|
FINDINGS.md written.
|
|
73
112
|
━━━━━━━━━━━━━━━━━━━━
|
|
74
113
|
```
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gdd-turn-closeout
|
|
3
|
+
description: "Portable mirror of the gdd-turn-closeout Stop hook (D-11). Closes the events.jsonl gap at turn-end and surfaces a stage-completion or paused-mid-task nudge. Tail-called by orchestrator skills (/gdd:next, /gdd:design, /gdd:verify) at exit on the 13 non-Claude runtimes that lack a Stop hook surface. Idempotent, non-blocking, ≤10ms typical."
|
|
4
|
+
argument-hint: "(none — reads .design/STATE.md and .design/telemetry/events.jsonl from cwd)"
|
|
5
|
+
tools: Read, Bash
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# gdd-turn-closeout
|
|
9
|
+
|
|
10
|
+
## Role
|
|
11
|
+
|
|
12
|
+
You are a deterministic **closeout** skill. You close the per-turn telemetry gap on runtimes that don't expose a Stop event (codex, gemini, and 11 others). You are a code-level mirror of `hooks/gdd-turn-closeout.js` (D-10): same conditions, same idempotence, same emitted event shape. The only difference: the JS hook emits the nudge as `additionalContext` via the harness; this skill prints the nudge directly to the user.
|
|
13
|
+
|
|
14
|
+
**When to invoke:** orchestrator skills (`/gdd:next`, `/gdd:design`, `/gdd:verify`) tail-call this skill as their final step before returning, so the user sees a closing nudge that matches what Claude Code users see via the Stop hook. Adoption is incremental — each orchestrator can wire the tail-call independently; the skill exists as a stable, callable surface today.
|
|
15
|
+
|
|
16
|
+
## Invocation Contract
|
|
17
|
+
|
|
18
|
+
- **Input**: none. Operates on `.design/STATE.md` and `.design/telemetry/events.jsonl` in the current working directory.
|
|
19
|
+
- **Output**: at most one printed line — the nudge — or silent return.
|
|
20
|
+
- **Latency budget**: ≤10ms typical (matches D-10). Read **only** STATE.md and the tail of events.jsonl; never load the full event stream.
|
|
21
|
+
- **Idempotence**: if the most recent event line is already a `turn_end` for the current `(stage, task_progress)` tuple, skip the append but still print the nudge.
|
|
22
|
+
- **Non-blocking**: any I/O failure → silent return. This skill must never gate the user.
|
|
23
|
+
|
|
24
|
+
## Algorithm
|
|
25
|
+
|
|
26
|
+
Execute these steps **in order** and stop at the first early-return.
|
|
27
|
+
|
|
28
|
+
### Step 1 — Try to read STATE.md
|
|
29
|
+
|
|
30
|
+
Read `.design/STATE.md`. If the file is missing or unreadable: **return silently** (no print, no append). Mirrors the JS hook's "missing STATE.md" branch.
|
|
31
|
+
|
|
32
|
+
### Step 2 — Parse the `<position>` block
|
|
33
|
+
|
|
34
|
+
Lightweight-parse only the `<position>…</position>` block (the rest of STATE.md is irrelevant here). Extract `stage`, `status`, `task_progress`. A regex pass (`/<position>([\s\S]*?)<\/position>/` then per-line `key: value`) is sufficient — do **not** invoke the full STATE parser (cost overhead).
|
|
35
|
+
|
|
36
|
+
If `status != "in_progress"`: **return silently**. The pipeline is either initialized, completed, or blocked — no turn-end gap to close.
|
|
37
|
+
|
|
38
|
+
### Step 3 — Tail the last event line
|
|
39
|
+
|
|
40
|
+
Read **only the last 8 KiB** of `.design/telemetry/events.jsonl` (a single event line is ≪64 KiB). Treat all of these as "stale by definition":
|
|
41
|
+
- The file is missing.
|
|
42
|
+
- The file is empty.
|
|
43
|
+
- The last line fails to parse as JSON.
|
|
44
|
+
- The last line's `timestamp` is missing or unparseable.
|
|
45
|
+
|
|
46
|
+
Otherwise compute `now - last_event.timestamp`. If the gap is **<60 seconds**, the user is actively mid-turn — **return silently** (the next real event will close the gap naturally).
|
|
47
|
+
|
|
48
|
+
A reasonable Bash one-liner for the tail when running this skill in a runtime that lacks a Read-tail primitive: `tail -n 1 .design/telemetry/events.jsonl 2>/dev/null`.
|
|
49
|
+
|
|
50
|
+
### Step 4 — Idempotence check, then append
|
|
51
|
+
|
|
52
|
+
If the last event is already shaped `{type: "turn_end", stage: <same>, payload: {task_progress: <same>}}` for the **exact** `(stage, task_progress)` tuple from Step 2: **skip the append** but proceed to Step 5.
|
|
53
|
+
|
|
54
|
+
Otherwise append a single JSONL line to `.design/telemetry/events.jsonl`:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{"type":"turn_end","timestamp":"<ISO 8601 now>","sessionId":"<session-id-or-'turn-closeout'>","stage":"<stage>","payload":{"task_progress":"<N/M>"},"_meta":{"source":"gdd-turn-closeout-skill"}}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Create `.design/telemetry/` if missing. The append must be a single `appendFile`-equivalent call (the writer assumes append-atomicity per Plan 20-06).
|
|
61
|
+
|
|
62
|
+
### Step 5 — Print the nudge
|
|
63
|
+
|
|
64
|
+
Match `task_progress` against `^(\d+)/(\d+)$`:
|
|
65
|
+
|
|
66
|
+
- **Numerator equals denominator and denominator > 0** (e.g. `5/5`, stage-complete):
|
|
67
|
+
|
|
68
|
+
> Stage `<stage>` complete — run `/gdd:next` or `/gdd:reflect`
|
|
69
|
+
|
|
70
|
+
- **Otherwise** (mid-task, e.g. `3/7`, `0/0`, malformed):
|
|
71
|
+
|
|
72
|
+
> Stage `<stage>` paused mid-task — resume with `/gdd:resume`
|
|
73
|
+
|
|
74
|
+
Print exactly one of these two lines. No additional commentary, no explanations of what the skill did — the nudge is the user-facing surface.
|
|
75
|
+
|
|
76
|
+
## Failure Modes
|
|
77
|
+
|
|
78
|
+
Every step above has an explicit silent-return on failure. The skill must remain non-blocking under all conditions:
|
|
79
|
+
|
|
80
|
+
| Condition | Behavior |
|
|
81
|
+
|-----------|----------|
|
|
82
|
+
| `.design/STATE.md` missing or unreadable | Silent return |
|
|
83
|
+
| `<position>` block absent or malformed | Silent return |
|
|
84
|
+
| `status != "in_progress"` | Silent return |
|
|
85
|
+
| `.design/telemetry/events.jsonl` missing | Treat as stale → fall through to append + nudge |
|
|
86
|
+
| Last event line unparseable | Treat as stale → fall through |
|
|
87
|
+
| Last event timestamp <60s old | Silent return |
|
|
88
|
+
| Append fails (permission, disk full) | Print the nudge anyway; do not surface the I/O error |
|
|
89
|
+
| Any uncaught throw at any step | Silent return |
|
|
90
|
+
|
|
91
|
+
## Equivalence with the JS hook
|
|
92
|
+
|
|
93
|
+
This skill and `hooks/gdd-turn-closeout.js` MUST stay code-level equivalent. Specifically:
|
|
94
|
+
|
|
95
|
+
- Same four early-return branches (no STATE / not in_progress / fresh event / no-op).
|
|
96
|
+
- Same staleness threshold: **60 seconds**.
|
|
97
|
+
- Same idempotence guard: `(type=turn_end, stage, payload.task_progress)` triple.
|
|
98
|
+
- Same emitted event shape (only `_meta.source` differs: `gdd-turn-closeout` vs `gdd-turn-closeout-skill`, so reflector telemetry can distinguish hook-driven vs skill-driven turn-ends).
|
|
99
|
+
- Same nudge wording for both `N/N` and mid-task cases.
|
|
100
|
+
|
|
101
|
+
If you change one, change the other in the same plan. Plan 25-09's `tests/turn-closeout-hook.test.cjs` covers the JS hook; the parallel coverage for this skill rides on Plan 25-09's Phase 25 baseline.
|
|
102
|
+
|
|
103
|
+
## Non-Goals
|
|
104
|
+
|
|
105
|
+
- **Not a state writer.** This skill never edits STATE.md. The events.jsonl append is the only side effect.
|
|
106
|
+
- **Not a stage transition.** A `turn_end` event is a within-stage observation, not a state-machine move; downstream tools that gate on stage transitions ignore it.
|
|
107
|
+
- **Not a Stop-event harness.** Cross-runtime Stop-event support at the harness level is explicit out-of-scope for Phase 25 (see CONTEXT.md OOS section).
|
|
108
|
+
|
|
109
|
+
## Integration Point
|
|
110
|
+
|
|
111
|
+
The canonical tail-call sites (per D-11) are `/gdd:next`, `/gdd:design`, `/gdd:verify`. Each orchestrator's final step, immediately before returning to the user, should be:
|
|
112
|
+
|
|
113
|
+
> Invoke skill `gdd-turn-closeout`.
|
|
114
|
+
|
|
115
|
+
Tail-call wiring is intentionally not part of v1.25 (Plan 25-04 ships only the callable surface). Each orchestrator can adopt the wiring independently in a follow-up.
|
package/skills/verify/SKILL.md
CHANGED
|
@@ -18,6 +18,28 @@ tools: mcp__gdd_state__get, mcp__gdd_state__transition_stage, mcp__gdd_state__ad
|
|
|
18
18
|
|
|
19
19
|
1. `mcp__gdd_state__transition_stage` with `to: "verify"`.
|
|
20
20
|
2. `mcp__gdd_state__get` → snapshot `state`. Read `state.must_haves` — this is the verification checklist; each M-XX starts at `status: pending` and will be flipped to `pass` or `fail` as verification concludes.
|
|
21
|
+
|
|
22
|
+
#### Step 2.5 — Quality-gate gate (D-08, D-09)
|
|
23
|
+
|
|
24
|
+
Before resume detection, inspect `state.quality_gate` from the same snapshot. The Stage 4.5 quality-gate skill (see `skills/quality-gate/SKILL.md`) writes a single `<run/>` element capturing the most recent run; this step is the verify-side consumer of that result. Three branches, evaluated in order:
|
|
25
|
+
|
|
26
|
+
- **`state.quality_gate?.run?.status === "fail"`** → Refuse to advance. The fix loop in `quality-gate/SKILL.md` Step 4 reached `max_iters` without converging, and the verify stage MUST NOT paper over it. Print a blocker reason that includes the iteration count and the `commands_run` field from the run, then call:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
mcp__gdd_state__add_blocker({
|
|
30
|
+
stage: "verify",
|
|
31
|
+
text: "quality-gate failed at iteration <N> (commands: <commands_run>) — re-run /gdd:quality-gate before /gdd:verify"
|
|
32
|
+
})
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Exit immediately with the failure surface visible to the user. Do NOT call `mcp__gdd_state__update_progress` to open the verify stage; the gate refused entry, so the stage was never opened.
|
|
36
|
+
|
|
37
|
+
- **`state.quality_gate?.run?.status === "timeout"` OR `=== "skipped"`** → Print a one-line warning naming the status and the `commands_run` value, then continue normally. Per D-07 these are signals, not walls: a slow test suite must not hostage the pipeline, and a project with no detectable quality commands must not block verify entry. The warning surfaces the degraded state without halting.
|
|
38
|
+
|
|
39
|
+
- **`state.quality_gate?.run?.status === "pass"` OR `state.quality_gate === null`** → Continue silently. `pass` is the happy path; `null` means the gate has never been run for this cycle (the user skipped Stage 4.5 entirely, which is permitted — verify only *checks* the gate result, never *runs* the gate itself, per the 25-07 out-of-scope clause).
|
|
40
|
+
|
|
41
|
+
This step is a pure read against the snapshot already loaded in Step 2 — no extra MCP call is required.
|
|
42
|
+
|
|
21
43
|
3. Resume detection (read `state.position.status` from the snapshot):
|
|
22
44
|
- If `status==in_progress` and `.design/DESIGN-VERIFICATION.md` exists: RESUME — skip re-spawning agents, go to Step 2 (gap-response loop).
|
|
23
45
|
- Otherwise: call `mcp__gdd_state__update_progress` with `task_progress: "0/3"`, `status: "in_progress"` to open the stage, then proceed to Step 1.
|