@hegemonart/get-design-done 1.24.2 → 1.25.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 +41 -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 +396 -729
- package/README.zh-CN.md +480 -133
- package/SKILL.md +2 -0
- package/agents/prototype-gate.md +122 -0
- package/agents/quality-gate-runner.md +125 -0
- package/hooks/budget-enforcer.ts +132 -7
- 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/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/quality-gate-detect.cjs +126 -0
- package/skills/quality-gate/SKILL.md +222 -0
- package/skills/router/SKILL.md +29 -9
- 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,126 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// scripts/lib/quality-gate-detect.cjs — quality-gate detection chain.
|
|
4
|
+
//
|
|
5
|
+
// Phase 25 Plan 25-09: promotes the doc-only auto-detection logic from
|
|
6
|
+
// skills/quality-gate/SKILL.md (Step 1, D-06) into a small testable
|
|
7
|
+
// JS module. Pure function, no I/O, no clock.
|
|
8
|
+
//
|
|
9
|
+
// The 3-tier resolution order (D-06):
|
|
10
|
+
//
|
|
11
|
+
// Tier 1 — Authoritative config:
|
|
12
|
+
// If the (already-loaded) `.design/config.json#quality_gate.commands`
|
|
13
|
+
// array is non-empty, return it verbatim. Skip all later tiers.
|
|
14
|
+
//
|
|
15
|
+
// Tier 2 — Auto-detect from package.json#scripts:
|
|
16
|
+
// If the (already-loaded) package.json#scripts object exists and is
|
|
17
|
+
// non-empty, intersect its keys with the canonical allowlist and
|
|
18
|
+
// emit `npm run <script>` for each match. The allowlist (case-
|
|
19
|
+
// sensitive, exact match):
|
|
20
|
+
//
|
|
21
|
+
// lint
|
|
22
|
+
// typecheck (or `tsc` as a substitute when `typecheck` is absent)
|
|
23
|
+
// test
|
|
24
|
+
// chromatic
|
|
25
|
+
// test:visual
|
|
26
|
+
//
|
|
27
|
+
// Hard exclusions (never included even if present):
|
|
28
|
+
//
|
|
29
|
+
// test:e2e (too slow for a Stage 4.5 gate)
|
|
30
|
+
// test:integration (only excluded when a separate `test` exists)
|
|
31
|
+
//
|
|
32
|
+
// Tier 3 — Skip with notice:
|
|
33
|
+
// Returns an empty array. Caller emits a `quality_gate_skipped`
|
|
34
|
+
// event and writes a `<run/>` with status="skipped".
|
|
35
|
+
//
|
|
36
|
+
// Mirrors the table in skills/quality-gate/SKILL.md verbatim. When the
|
|
37
|
+
// SKILL.md prose changes, change this module in lockstep — the SKILL is
|
|
38
|
+
// the design intent, this is the executable encoding consumers can test
|
|
39
|
+
// against.
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Allowlisted script names (case-sensitive, exact match unless noted).
|
|
43
|
+
* Order matters: it determines the canonical command-list ordering, which
|
|
44
|
+
* in turn drives the deterministic `commands_run` field in events.jsonl
|
|
45
|
+
* and the STATE.md <run/> entry.
|
|
46
|
+
*/
|
|
47
|
+
const ALLOWLIST = Object.freeze([
|
|
48
|
+
'lint',
|
|
49
|
+
'typecheck',
|
|
50
|
+
'test',
|
|
51
|
+
'chromatic',
|
|
52
|
+
'test:visual',
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Hard exclusions. Even when present in package.json#scripts, these are
|
|
57
|
+
* never run by the quality gate — they are too slow / orthogonal to the
|
|
58
|
+
* gate's purpose. Excluding `test:integration` only matters when a
|
|
59
|
+
* separate `test` script exists; we encode that invariant in detect().
|
|
60
|
+
*/
|
|
61
|
+
const ALWAYS_EXCLUDED = Object.freeze(['test:e2e']);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Detection chain.
|
|
65
|
+
*
|
|
66
|
+
* @param {object} inputs
|
|
67
|
+
* @param {string[]|null|undefined} inputs.configCommands
|
|
68
|
+
* Value of `.design/config.json#quality_gate.commands`. `null` or
|
|
69
|
+
* empty array means "no config-side override; fall through to
|
|
70
|
+
* auto-detect". The caller is responsible for reading the file.
|
|
71
|
+
* @param {Record<string, string>|null|undefined} inputs.scripts
|
|
72
|
+
* Value of `package.json#scripts`. `null` means "no package.json".
|
|
73
|
+
* @returns {{commands: string[], tier: 1|2|3, reason?: string}}
|
|
74
|
+
* Detection result. `tier` is the tier that produced the
|
|
75
|
+
* commands (1 / 2 / 3 — see top of file). `reason` is populated
|
|
76
|
+
* on tier 3 only ("no commands resolved").
|
|
77
|
+
*/
|
|
78
|
+
function detect(inputs) {
|
|
79
|
+
const configCommands = inputs && inputs.configCommands;
|
|
80
|
+
const scripts = inputs && inputs.scripts;
|
|
81
|
+
|
|
82
|
+
// --- Tier 1: authoritative config wins. ---
|
|
83
|
+
if (Array.isArray(configCommands) && configCommands.length > 0) {
|
|
84
|
+
return { commands: configCommands.slice(), tier: 1 };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// --- Tier 2: auto-detect from package.json#scripts. ---
|
|
88
|
+
if (scripts && typeof scripts === 'object') {
|
|
89
|
+
const detected = autoDetect(scripts);
|
|
90
|
+
if (detected.length > 0) {
|
|
91
|
+
return { commands: detected, tier: 2 };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// --- Tier 3: nothing resolved → skip with notice. ---
|
|
96
|
+
return { commands: [], tier: 3, reason: 'no commands resolved' };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Apply the allowlist to a `scripts` map. Pure.
|
|
101
|
+
*/
|
|
102
|
+
function autoDetect(scripts) {
|
|
103
|
+
const out = [];
|
|
104
|
+
for (const name of ALLOWLIST) {
|
|
105
|
+
if (name === 'typecheck') {
|
|
106
|
+
// `typecheck` preferred; fall through to `tsc` only if absent.
|
|
107
|
+
if (Object.prototype.hasOwnProperty.call(scripts, 'typecheck')) {
|
|
108
|
+
out.push('npm run typecheck');
|
|
109
|
+
} else if (Object.prototype.hasOwnProperty.call(scripts, 'tsc')) {
|
|
110
|
+
out.push('npm run tsc');
|
|
111
|
+
}
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (Object.prototype.hasOwnProperty.call(scripts, name) && !ALWAYS_EXCLUDED.includes(name)) {
|
|
115
|
+
out.push(`npm run ${name}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = {
|
|
122
|
+
ALLOWLIST,
|
|
123
|
+
ALWAYS_EXCLUDED,
|
|
124
|
+
detect,
|
|
125
|
+
autoDetect,
|
|
126
|
+
};
|
|
@@ -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, 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,45 @@ 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"},
|
|
22
23
|
"estimated_cost_usd": 0.034,
|
|
23
24
|
"cache_hits": ["design-context-builder:abc123"]
|
|
24
25
|
}
|
|
25
26
|
```
|
|
26
|
-
- `path` enum: `fast` (single Haiku + no checkers), `quick` (Sonnet mappers + Haiku verify), `full` (Opus planners + full quality gates).
|
|
27
|
+
- `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.
|
|
28
|
+
- `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.
|
|
27
29
|
- `model_tier_overrides` merges agent frontmatter `default-tier` with `.design/budget.json.tier_overrides` — budget.json wins per D-04.
|
|
28
30
|
- `estimated_cost_usd` is the sum of per-spawn estimates using the D-06 formula and `reference/model-prices.md`.
|
|
29
31
|
- `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
32
|
|
|
31
33
|
## Path Selection Heuristic
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
|
36
|
-
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
35
|
+
The router emits both `path` (legacy 3-tier enum) and `complexity_class` (Phase 25 4-tier enum). The canonical mapping is:
|
|
36
|
+
|
|
37
|
+
| complexity_class | path | Behavior |
|
|
38
|
+
|------------------|------|----------|
|
|
39
|
+
| `S` | `fast` (short-circuited) | Skip router itself, skip cache-manager, skip telemetry write. Deterministic no-op decision. |
|
|
40
|
+
| `M` | `fast` | Single Haiku + no checkers. |
|
|
41
|
+
| `L` | `quick` | Sonnet mappers + Haiku verify. |
|
|
42
|
+
| `XL` | `full` | Opus planners + full quality gates. Recommends worktree-isolation default + mandatory inter-stage checkpoint + reflector auto-spawn. |
|
|
43
|
+
|
|
44
|
+
Bucket assignment:
|
|
45
|
+
|
|
46
|
+
| Signal | complexity_class | path |
|
|
47
|
+
|--------|------------------|------|
|
|
48
|
+
| Command is `/gdd:help`, `/gdd:stats`, `/gdd:note`, `/gdd:health`, single-Haiku skill | `S` | `fast` (short-circuited — see below) |
|
|
49
|
+
| Command is `/gdd:scan`, `/gdd:brief`, `/gdd:sketch`, `/gdd:spike`, `/gdd:fast` | `M` | `fast` |
|
|
50
|
+
| Command spawns exactly one agent (no orchestration), not in S list | `M` | `fast` |
|
|
51
|
+
| Command is `/gdd:explore`, `/gdd:discover`, standalone `/gdd:verify`, standalone `/gdd:plan` | `L` | `quick` |
|
|
52
|
+
| Command spawns parallel mappers but no planners/auditors (`/gdd:discover` in `--auto` mode) | `L` | `quick` |
|
|
53
|
+
| Command is `/gdd:next`, `/gdd:do`, `/gdd:autonomous`, end-to-end Brief→Verify, anything spawning planners + auditors + verifiers in series | `XL` | `full` |
|
|
54
|
+
| Command spawns planners, auditors, verifiers, or integration-checkers (`/gdd:plan`, `/gdd:verify`, `/gdd:audit`) and is not standalone | `XL` | `full` |
|
|
55
|
+
| `--dry-run` flag present on any command | downgrade one tier (XL→L→M→S; `path` follows the mapping table) |
|
|
56
|
+
|
|
57
|
+
### S-class short-circuit
|
|
58
|
+
|
|
59
|
+
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
60
|
|
|
41
61
|
## Cost Estimation Algorithm
|
|
42
62
|
|
|
@@ -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.
|