@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.
Files changed (60) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +87 -0
  4. package/README.de.md +679 -0
  5. package/README.fr.md +679 -0
  6. package/README.it.md +679 -0
  7. package/README.ja.md +679 -0
  8. package/README.ko.md +679 -0
  9. package/README.md +399 -728
  10. package/README.zh-CN.md +480 -133
  11. package/SKILL.md +2 -0
  12. package/agents/README.md +60 -0
  13. package/agents/design-reflector.md +43 -0
  14. package/agents/gdd-intel-updater.md +34 -1
  15. package/agents/prototype-gate.md +122 -0
  16. package/agents/quality-gate-runner.md +125 -0
  17. package/hooks/budget-enforcer.ts +275 -11
  18. package/hooks/gdd-decision-injector.js +183 -3
  19. package/hooks/gdd-turn-closeout.js +238 -0
  20. package/hooks/hooks.json +10 -0
  21. package/package.json +5 -5
  22. package/reference/STATE-TEMPLATE.md +41 -0
  23. package/reference/config-schema.md +30 -0
  24. package/reference/model-prices.md +40 -19
  25. package/reference/prices/antigravity.md +21 -0
  26. package/reference/prices/augment.md +21 -0
  27. package/reference/prices/claude.md +42 -0
  28. package/reference/prices/cline.md +23 -0
  29. package/reference/prices/codebuddy.md +21 -0
  30. package/reference/prices/codex.md +25 -0
  31. package/reference/prices/copilot.md +21 -0
  32. package/reference/prices/cursor.md +21 -0
  33. package/reference/prices/gemini.md +25 -0
  34. package/reference/prices/kilo.md +21 -0
  35. package/reference/prices/opencode.md +23 -0
  36. package/reference/prices/qwen.md +25 -0
  37. package/reference/prices/trae.md +23 -0
  38. package/reference/prices/windsurf.md +21 -0
  39. package/reference/registry.json +107 -1
  40. package/reference/runtime-models.md +446 -0
  41. package/reference/schemas/runtime-models.schema.json +123 -0
  42. package/scripts/install.cjs +8 -0
  43. package/scripts/lib/budget-enforcer.cjs +446 -0
  44. package/scripts/lib/cost-arbitrage.cjs +294 -0
  45. package/scripts/lib/gdd-state/mutator.ts +454 -0
  46. package/scripts/lib/gdd-state/parser.ts +351 -1
  47. package/scripts/lib/gdd-state/types.ts +193 -0
  48. package/scripts/lib/install/installer.cjs +188 -11
  49. package/scripts/lib/install/parse-runtime-models.cjs +267 -0
  50. package/scripts/lib/install/runtimes.cjs +43 -0
  51. package/scripts/lib/quality-gate-detect.cjs +126 -0
  52. package/scripts/lib/runtime-detect.cjs +96 -0
  53. package/scripts/lib/tier-resolver.cjs +311 -0
  54. package/scripts/validate-frontmatter.ts +138 -1
  55. package/skills/quality-gate/SKILL.md +222 -0
  56. package/skills/router/SKILL.md +79 -10
  57. package/skills/sketch-wrap-up/SKILL.md +47 -2
  58. package/skills/spike-wrap-up/SKILL.md +41 -2
  59. package/skills/turn-closeout/SKILL.md +115 -0
  60. package/skills/verify/SKILL.md +22 -0
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /**
4
+ * hooks/gdd-turn-closeout.js — Stop event hook (Plan 25-04, D-10).
5
+ *
6
+ * Fires when the assistant turn ends. Closes the events.jsonl gap at turn-end
7
+ * and surfaces a stage-completion or paused-mid-task nudge as additionalContext
8
+ * when the user is mid-pipeline and the last event is stale (>60s old).
9
+ *
10
+ * Contract (D-10):
11
+ * stdin : { tool_name, tool_input, cwd, ... } — Claude harness Stop payload
12
+ * stdout : { continue: true, hookSpecificOutput: { hookEventName: "Stop", additionalContext } }
13
+ * or { continue: true } when no nudge is warranted
14
+ * exit : always 0 (non-blocking — never gate the user's next turn)
15
+ * latency : ≤10ms typical (reads STATE.md + tails events.jsonl only)
16
+ * idempotent: re-running on the same (stage, task_progress) tuple after a
17
+ * turn_end has already been written is a no-op append-skip.
18
+ *
19
+ * Logic:
20
+ * 1. Try to read `.design/STATE.md`. Missing/unreadable → exit cleanly.
21
+ * 2. Lightweight-parse the <position> block (regex, no full state parser).
22
+ * If status != "in_progress" → exit cleanly.
23
+ * 3. Tail the last line of `.design/telemetry/events.jsonl`. Missing file
24
+ * counts as "stale by definition" (no events at all is exactly the gap
25
+ * this hook closes).
26
+ * 4. If last event is <60s old → exit cleanly (no gap to fill).
27
+ * 5. If last event is already a turn_end for the SAME (stage, task_progress)
28
+ * tuple → idempotent no-op (skip the append, still emit the nudge).
29
+ * 6. Else: append `{type: "turn_end", timestamp, sessionId, stage, payload:
30
+ * {task_progress}}` to events.jsonl.
31
+ * 7. Build additionalContext nudge:
32
+ * - task_progress matches `N/N` (stage complete) → "Stage <stage>
33
+ * complete — run /gdd:next or /gdd:reflect"
34
+ * - else → "Stage <stage> paused mid-task — resume with /gdd:resume"
35
+ *
36
+ * Out of scope (per Plan 25-04):
37
+ * - Wiring this hook into hooks.json (Plan 25-08).
38
+ * - Tail-calling from orchestrator skills — see skills/turn-closeout/SKILL.md
39
+ * for the portable mirror used by non-Claude runtimes.
40
+ */
41
+
42
+ const fs = require('fs');
43
+ const path = require('path');
44
+
45
+ const STALE_AFTER_MS = 60_000;
46
+ const TAIL_BYTES = 8_192; // last 8 KiB is plenty for one event line (<<64KiB cap)
47
+
48
+ /**
49
+ * Lightweight parse of the `<position>` block in STATE.md. Returns the fields
50
+ * we care about, or null if the block isn't present / well-formed.
51
+ *
52
+ * We intentionally avoid the full parser at scripts/lib/gdd-state/parser.ts
53
+ * because (a) it requires TS execution and (b) its overhead would blow the
54
+ * 10ms budget. The position block is k=v lines so a regex pass is fine.
55
+ */
56
+ function parsePosition(stateMd) {
57
+ const m = stateMd.match(/<position>([\s\S]*?)<\/position>/);
58
+ if (!m) return null;
59
+ const body = m[1];
60
+ const fields = {};
61
+ for (const line of body.split(/\r?\n/)) {
62
+ const kv = line.match(/^\s*([a-z_]+)\s*:\s*(.*?)\s*$/);
63
+ if (kv) fields[kv[1]] = kv[2];
64
+ }
65
+ if (!fields.stage || !fields.status) return null;
66
+ return {
67
+ stage: fields.stage,
68
+ status: fields.status,
69
+ task_progress: fields.task_progress || '0/0',
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Read the last line of a JSONL file without loading the whole file.
75
+ * Returns null if the file is missing, empty, or unreadable.
76
+ */
77
+ function tailLastLine(filePath) {
78
+ let fd;
79
+ try {
80
+ fd = fs.openSync(filePath, 'r');
81
+ const stat = fs.fstatSync(fd);
82
+ if (stat.size === 0) return null;
83
+ const readLen = Math.min(TAIL_BYTES, stat.size);
84
+ const buf = Buffer.alloc(readLen);
85
+ fs.readSync(fd, buf, 0, readLen, stat.size - readLen);
86
+ const tail = buf.toString('utf8');
87
+ // Trim any trailing newlines, then take the substring after the last newline.
88
+ const trimmed = tail.replace(/\s+$/, '');
89
+ const idx = trimmed.lastIndexOf('\n');
90
+ return idx === -1 ? trimmed : trimmed.slice(idx + 1);
91
+ } catch {
92
+ return null;
93
+ } finally {
94
+ if (fd !== undefined) {
95
+ try { fs.closeSync(fd); } catch { /* swallow */ }
96
+ }
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Decide whether the last-event line is "stale" — older than STALE_AFTER_MS,
102
+ * or unparseable / absent (which we also treat as stale, since the absence of
103
+ * a recent end-of-turn marker is exactly the condition this hook closes).
104
+ *
105
+ * Returns { stale: boolean, lastEvent: object|null }.
106
+ */
107
+ function classifyLastEvent(lastLine, nowMs) {
108
+ if (!lastLine) return { stale: true, lastEvent: null };
109
+ let ev;
110
+ try { ev = JSON.parse(lastLine); } catch { return { stale: true, lastEvent: null }; }
111
+ const ts = ev && typeof ev.timestamp === 'string' ? Date.parse(ev.timestamp) : NaN;
112
+ if (!Number.isFinite(ts)) return { stale: true, lastEvent: ev };
113
+ return { stale: nowMs - ts > STALE_AFTER_MS, lastEvent: ev };
114
+ }
115
+
116
+ /**
117
+ * Idempotence guard: if the most-recent line is already a turn_end for the
118
+ * exact (stage, task_progress) tuple, skip the append. Returns true if a
119
+ * duplicate-append should be suppressed.
120
+ */
121
+ function isDuplicateTurnEnd(lastEvent, stage, taskProgress) {
122
+ if (!lastEvent || lastEvent.type !== 'turn_end') return false;
123
+ if (lastEvent.stage !== stage) return false;
124
+ const lastProgress = lastEvent.payload && lastEvent.payload.task_progress;
125
+ return lastProgress === taskProgress;
126
+ }
127
+
128
+ /**
129
+ * Build the user-facing nudge string. `N/N` (numerator==denominator) signals
130
+ * stage-complete; anything else is paused-mid-task.
131
+ */
132
+ function buildNudge(stage, taskProgress) {
133
+ const m = taskProgress.match(/^(\d+)\s*\/\s*(\d+)$/);
134
+ const stageComplete = !!(m && m[1] === m[2] && Number(m[2]) > 0);
135
+ return stageComplete
136
+ ? `Stage ${stage} complete — run /gdd:next or /gdd:reflect`
137
+ : `Stage ${stage} paused mid-task — resume with /gdd:resume`;
138
+ }
139
+
140
+ /**
141
+ * Resolve a session id for the appended event. The Stop hook payload may
142
+ * include `session_id`; if absent (older harness or test fixtures) fall back
143
+ * to a synthetic marker so the line still parses against the BaseEvent shape.
144
+ */
145
+ function resolveSessionId(payload) {
146
+ return (payload && (payload.session_id || payload.sessionId)) || 'turn-closeout';
147
+ }
148
+
149
+ /**
150
+ * Append a single turn_end event to events.jsonl. Best-effort — any I/O
151
+ * failure is swallowed (the nudge still surfaces; we don't gate on writes).
152
+ */
153
+ function appendTurnEnd(eventsPath, stage, taskProgress, sessionId, nowIso) {
154
+ const event = {
155
+ type: 'turn_end',
156
+ timestamp: nowIso,
157
+ sessionId,
158
+ stage,
159
+ payload: { task_progress: taskProgress },
160
+ _meta: { source: 'gdd-turn-closeout' },
161
+ };
162
+ try {
163
+ const dir = path.dirname(eventsPath);
164
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
165
+ fs.appendFileSync(eventsPath, JSON.stringify(event) + '\n', { flag: 'a' });
166
+ } catch {
167
+ /* swallow — non-blocking */
168
+ }
169
+ }
170
+
171
+ function emitContinue(additionalContext) {
172
+ const out = additionalContext
173
+ ? { continue: true, hookSpecificOutput: { hookEventName: 'Stop', additionalContext } }
174
+ : { continue: true };
175
+ try { process.stdout.write(JSON.stringify(out)); } catch { /* swallow */ }
176
+ }
177
+
178
+ async function main() {
179
+ // Drain stdin even if we don't end up using it; the harness sends a JSON
180
+ // payload and orphaned EPIPE on close has bitten other hooks.
181
+ let buf = '';
182
+ try {
183
+ for await (const chunk of process.stdin) buf += chunk;
184
+ } catch { /* swallow */ }
185
+
186
+ let payload = {};
187
+ try { payload = buf.trim() ? JSON.parse(buf) : {}; } catch { payload = {}; }
188
+
189
+ const cwd = (payload && payload.cwd) || process.cwd();
190
+ const statePath = path.join(cwd, '.design', 'STATE.md');
191
+ const eventsPath = path.join(cwd, '.design', 'telemetry', 'events.jsonl');
192
+
193
+ // --- Branch 1: STATE.md missing or unreadable → silent continue.
194
+ let stateMd;
195
+ try { stateMd = fs.readFileSync(statePath, 'utf8'); }
196
+ catch { return emitContinue(null); }
197
+
198
+ // --- Branch 2: <position> not parseable or status != in_progress → silent continue.
199
+ const position = parsePosition(stateMd);
200
+ if (!position || position.status !== 'in_progress') return emitContinue(null);
201
+
202
+ const nowMs = Date.now();
203
+ const nowIso = new Date(nowMs).toISOString();
204
+
205
+ // --- Branch 3: last event is fresh (<60s) → silent continue (no gap).
206
+ const lastLine = tailLastLine(eventsPath);
207
+ const { stale, lastEvent } = classifyLastEvent(lastLine, nowMs);
208
+ if (!stale) return emitContinue(null);
209
+
210
+ // --- Branch 4: stale → idempotent append (skip if duplicate) + emit nudge.
211
+ if (!isDuplicateTurnEnd(lastEvent, position.stage, position.task_progress)) {
212
+ appendTurnEnd(
213
+ eventsPath,
214
+ position.stage,
215
+ position.task_progress,
216
+ resolveSessionId(payload),
217
+ nowIso,
218
+ );
219
+ }
220
+
221
+ emitContinue(buildNudge(position.stage, position.task_progress));
222
+ }
223
+
224
+ main().catch(() => {
225
+ // Never block the user's next turn — even on catastrophic failure.
226
+ try { process.stdout.write(JSON.stringify({ continue: true })); } catch { /* swallow */ }
227
+ });
228
+
229
+ // Exposed for unit tests (Plan 25-09). Intentionally no public runtime surface
230
+ // beyond the stdin/stdout contract above.
231
+ module.exports = {
232
+ parsePosition,
233
+ tailLastLine,
234
+ classifyLastEvent,
235
+ isDuplicateTurnEnd,
236
+ buildNudge,
237
+ STALE_AFTER_MS,
238
+ };
package/hooks/hooks.json CHANGED
@@ -100,6 +100,16 @@
100
100
  }
101
101
  ]
102
102
  }
103
+ ],
104
+ "Stop": [
105
+ {
106
+ "hooks": [
107
+ {
108
+ "type": "command",
109
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gdd-turn-closeout.js\""
110
+ }
111
+ ]
112
+ }
103
113
  ]
104
114
  }
105
115
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hegemonart/get-design-done",
3
- "version": "1.24.2",
4
- "description": "A Claude Code plugin for systematic design improvement",
3
+ "version": "1.26.0",
4
+ "description": "A design-quality pipeline for AI coding agents: brief, plan, implement, and verify UI work against your design system.",
5
5
  "author": "Hegemon",
6
6
  "homepage": "https://github.com/hegemonart/get-design-done",
7
7
  "repository": {
@@ -53,11 +53,11 @@
53
53
  "gdd-sdk": "node --experimental-strip-types scripts/lib/cli/index.ts"
54
54
  },
55
55
  "devDependencies": {
56
- "@types/node": "^22.0.0",
56
+ "@types/node": "^25.6.0",
57
57
  "ajv-cli": "^5.0.0",
58
58
  "ajv-formats": "^3.0.1",
59
59
  "json-schema-to-typescript": "^15.0.0",
60
- "typescript": "^5.5.0"
60
+ "typescript": "^6.0.3"
61
61
  },
62
62
  "keywords": [
63
63
  "claude",
@@ -84,7 +84,7 @@
84
84
  "hooks": "hooks/hooks.json",
85
85
  "dependencies": {
86
86
  "@anthropic-ai/claude-agent-sdk": "^0.2.119",
87
- "@clack/prompts": "^0.7.0",
87
+ "@clack/prompts": "^1.2.0",
88
88
  "@modelcontextprotocol/sdk": "^1.0.0"
89
89
  },
90
90
  "optionalDependencies": {
@@ -55,6 +55,25 @@ skipped_stages: ""
55
55
  <!-- Valid status values: pending | pass | fail -->
56
56
  </must_haves>
57
57
 
58
+ <prototyping>
59
+ <!-- Phase 25: appended by sketch-wrap-up / spike-wrap-up + the prototype-gate. -->
60
+ <!-- Three child element types, each on its own line: -->
61
+ <!-- <sketch slug="…" cycle="…" decision="D-XX" status="resolved"/> -->
62
+ <!-- <spike slug="…" cycle="…" decision="D-XX" verdict="yes|no|partial" status="resolved"/> -->
63
+ <!-- <skipped at="explore|plan" cycle="…" reason="…"/> -->
64
+ <!-- The block is omitted entirely on fresh files; add it only when the first -->
65
+ <!-- sketch / spike / skipped entry is appended. -->
66
+ </prototyping>
67
+
68
+ <quality_gate>
69
+ <!-- Phase 25 (Plan 25-03): written by the quality-gate skill (Stage 4.5). -->
70
+ <!-- Houses a single most-recent <run/> entry — append-mode would be overkill. -->
71
+ <!-- Format: -->
72
+ <!-- <run started_at="…" completed_at="…" status="pass|fail|timeout|skipped" iteration="N" commands_run="lint,typecheck,test"/> -->
73
+ <!-- The block is omitted entirely on fresh files; add it only when the first -->
74
+ <!-- gate completion overwrites the entry. -->
75
+ </quality_gate>
76
+
58
77
  <connections>
59
78
  <!-- Detected at scan entry or via /gdd:connections; updated if connections become available mid-pipeline. -->
60
79
  <!-- Format: <connection_name>: <available | unavailable | not_configured> -->
@@ -149,6 +168,28 @@ Discover stage populates with observable behaviors. Verify stage updates status.
149
168
  - `[description]`: testable behavior or artifact
150
169
  - `status`: `pending` (default), `pass` (verify confirmed), `fail` (verify rejected)
151
170
 
171
+ ### `<prototyping>`
172
+
173
+ Phase 25 surface (D-01). A checkpoint log — NOT a stage. Tracks sketch and spike outcomes plus cycle-scoped skip suppressions for the prototype gate.
174
+
175
+ - `<sketch slug=… cycle=… decision=D-XX status=resolved/>` — written by `sketch-wrap-up` after a sketch resolves into a D-XX decision.
176
+ - `<spike slug=… cycle=… decision=D-XX verdict=yes|no|partial status=resolved/>` — written by `spike-wrap-up` after a spike resolves; `verdict` captures the answer.
177
+ - `<skipped at=… cycle=… reason=…/>` — written by the prototype gate when the user declines to sketch/spike at a firing point. Cycle-scoped suppression (D-02): a `<skipped/>` entry suppresses re-asking for the rest of the named cycle.
178
+
179
+ The block is **optional** — fresh STATE.md files do not carry it. The serializer omits the block entirely when no entries exist; appending the first entry is what materializes the block.
180
+
181
+ ### `<quality_gate>`
182
+
183
+ Phase 25 surface (Plan 25-03 / D-06..D-09). Captures the most recent run of the Stage 4.5 quality gate (lint / typecheck / test / visual-regression) between Design and Verify. The block houses a single self-closing `<run/>` element — append-mode is overkill, so each gate completion overwrites the entry.
184
+
185
+ - `started_at` — ISO 8601 at which the parallel command run entered.
186
+ - `completed_at` — ISO 8601 at which the gate produced its terminal status.
187
+ - `status` — `pass | fail | timeout | skipped`. `pass` clears the verify-entry gate; `fail` blocks; `timeout` warns + proceeds (D-07); `skipped` indicates the detection chain resolved zero commands.
188
+ - `iteration` — non-negative integer fix-loop count (D-08). `1` = single clean pass; `N === max_iters` with `status === 'fail'` = bounded exhaustion.
189
+ - `commands_run` — comma-separated names of the commands actually executed in Step 2 (e.g., `lint,typecheck,test`). Empty string when `status === 'skipped'`.
190
+
191
+ The block is **optional** — fresh STATE.md files do not carry it. The serializer omits the block entirely when `quality_gate === null`; the SKILL writes the first `<run/>` to materialize it.
192
+
152
193
  ### `<connections>`
153
194
 
154
195
  One line per external connection. Detected at scan entry via MCP availability probes.
@@ -179,6 +179,36 @@ TTL driving `.design/cache-manifest.json` entry expiry per D-08 Layer B.
179
179
 
180
180
  `enforce | warn | log` per D-11. `enforce` (default) is D-02 behavior; `warn` prints warnings but allows spawn; `log` is advisory-only (useful for adoption on existing projects mid-flight).
181
181
 
182
+ ### `class_caps_usd` (Phase 25 / D-05, optional)
183
+
184
+ Optional per-class per-spawn cap map. Lets you set tighter caps for trivial commands and looser caps for autonomous flows independently. Read by `hooks/budget-enforcer.ts` when the router decision payload (`tool_input.context.router_decision.complexity_class`) is present. Falls back to `per_task_cap_usd` when the field is absent OR no router decision is supplied (full back-compat with pre-Phase-25 callers).
185
+
186
+ ```json
187
+ {
188
+ "class_caps_usd": {
189
+ "S": 0.05,
190
+ "M": 0.50,
191
+ "L": 1.50,
192
+ "XL": 5.00
193
+ }
194
+ }
195
+ ```
196
+
197
+ Schema:
198
+
199
+ ```ts
200
+ class_caps_usd?: { S?: number; M?: number; L?: number; XL?: number }
201
+ ```
202
+
203
+ Resolution order for the per-spawn cap:
204
+
205
+ 1. If `complexity_class` is in `tool_input.context.router_decision` AND `class_caps_usd[class]` is a positive finite number → use it.
206
+ 2. Otherwise → use `per_task_cap_usd`.
207
+
208
+ Class `S` is special: when `complexity_class === "S"` is supplied to the hook, enforcement is skipped entirely (no cap check, no auto-downgrade) — class-S commands typically short-circuit the router upstream so this hook never runs at all; the explicit S handling is the defensive path. See `skills/router/SKILL.md` for the canonical `S → fast (short-circuited)`, `M → fast`, `L → quick`, `XL → full` mapping.
209
+
210
+ `per_phase_cap_usd` is unchanged by this field — phase-cumulative enforcement always uses the global per-phase cap regardless of class.
211
+
182
212
  ## Bootstrap behavior
183
213
 
184
214
  If `.design/budget.json` is missing when any `/gdd:*` command runs, `scripts/bootstrap.sh` writes the Default Config values (per D-12). Don't block the spawn — defaults are sensible.
@@ -1,25 +1,35 @@
1
- # Model Prices — Static Price Table
1
+ # Model Prices — Router
2
2
 
3
- **Source of truth for `est_cost_usd` calculations** in the router (`skills/router/SKILL.md`) and budget-enforcer hook (`hooks/budget-enforcer.js`). Anthropic-only pricing. Update the table here when prices change downstream calculators read this file.
3
+ **Phase 26 D-08 router.** This file used to carry a single Anthropic-only price table. As of v1.26.0 it links to per-runtime sub-tables — one file per runtime under `reference/prices/`. Budget-enforcer + cost-aggregator load the sub-table for the active runtime (resolved via `scripts/lib/runtime-detect.cjs`) and tag every `events.jsonl` cost row with the runtime ID.
4
4
 
5
- ## Pricing (USD per 1M tokens)
5
+ For the model→tier mapping (which model name corresponds to opus/sonnet/haiku per runtime), see `reference/runtime-models.md`.
6
6
 
7
- | Model | Tier | input_per_1m | output_per_1m | cached_input_per_1m |
8
- |-------|------|--------------|---------------|----------------------|
9
- | claude-haiku-4-5 | haiku | 1.00 | 5.00 | 0.10 |
10
- | claude-sonnet-4-7 | sonnet | 3.00 | 15.00 | 0.30 |
11
- | claude-opus-4-7 | opus | 15.00 | 75.00 | 1.50 |
7
+ ## Per-runtime sub-tables
8
+
9
+ | Runtime | Path | Status |
10
+ |---------|------|--------|
11
+ | Claude Code | [`reference/prices/claude.md`](./prices/claude.md) | canonical (v1.26.0) |
12
+ | OpenAI Codex CLI | [`reference/prices/codex.md`](./prices/codex.md) | seed (v1.26.0; provenance `<TODO>`) |
13
+ | Google Gemini CLI | [`reference/prices/gemini.md`](./prices/gemini.md) | seed (v1.26.0; provenance `<TODO>`) |
14
+ | Alibaba Qwen CLI | [`reference/prices/qwen.md`](./prices/qwen.md) | seed (v1.26.0; provenance `<TODO>`) |
15
+ | Kilo Code | [`reference/prices/kilo.md`](./prices/kilo.md) | stub |
16
+ | GitHub Copilot CLI | [`reference/prices/copilot.md`](./prices/copilot.md) | stub |
17
+ | Cursor | [`reference/prices/cursor.md`](./prices/cursor.md) | stub |
18
+ | Windsurf | [`reference/prices/windsurf.md`](./prices/windsurf.md) | stub |
19
+ | Antigravity | [`reference/prices/antigravity.md`](./prices/antigravity.md) | stub |
20
+ | Augment Code | [`reference/prices/augment.md`](./prices/augment.md) | stub |
21
+ | Trae | [`reference/prices/trae.md`](./prices/trae.md) | stub |
22
+ | CodeBuddy | [`reference/prices/codebuddy.md`](./prices/codebuddy.md) | stub |
23
+ | Cline | [`reference/prices/cline.md`](./prices/cline.md) | stub |
24
+ | OpenCode | [`reference/prices/opencode.md`](./prices/opencode.md) | stub |
12
25
 
13
- ## size_budget conservative token ranges
26
+ **Sub-table format:** every file under `reference/prices/` carries the same canonical header row:
14
27
 
15
- Agent frontmatter carries `size_budget: S|M|L|XL`. The router uses these conservative token ranges to compute a pre-spawn `est_cost_usd` without a live model call:
28
+ ```
29
+ | Model | Tier | input_per_1m | output_per_1m | cached_input_per_1m |
30
+ ```
16
31
 
17
- | size_budget | input_tokens (conservative max) | output_tokens (conservative max) |
18
- |-------------|----------------------------------|-----------------------------------|
19
- | S | 4000 | 1000 |
20
- | M | 10000 | 2500 |
21
- | L | 25000 | 6000 |
22
- | XL | 60000 | 15000 |
32
+ Extra columns may be appended at the right edge by runtime adapter authors without breaking the parser (forward-compatible).
23
33
 
24
34
  ## Estimator formula
25
35
 
@@ -29,9 +39,20 @@ est_cost_usd =
29
39
  + (output_tokens / 1_000_000) * output_per_1m
30
40
  ```
31
41
 
32
- When `cache_hit: true` (see D-08), the hook re-runs the formula with `cached_input_per_1m` in place of `input_per_1m` for the input portion.
42
+ When `cache_hit: true`, the formula re-runs with `cached_input_per_1m` in place of `input_per_1m` for the input portion. See `skills/router/SKILL.md` (D-08) for the cache-hit semantics.
43
+
44
+ ## Fallback chain (D-08)
45
+
46
+ When a cost lookup misses (model not present in the runtime's sub-table, or runtime sub-table is a stub), `scripts/lib/budget-enforcer.cjs` falls back to `reference/prices/claude.md` and emits a `cost_lookup_fallback` event. This keeps the pipeline running on stub runtimes while authority-watcher (Phase 13.2) flags drift for follow-up.
47
+
48
+ If `claude.md` ALSO misses the model, the spawn proceeds with `cost_usd: null` and a `cost_lookup_failed` event — the existing fail-open contract from Phase 20-13.
49
+
50
+ ## Transitional fallback (v1.25 and earlier)
51
+
52
+ For v1.25.x and earlier the single Anthropic price table lived inline in this file. That table is preserved at `reference/prices/claude.md` byte-for-byte (as of the v1.26.0 split, modulo the surrounding prose). Hooks/skills that pinned to specific row strings should rebase those references to the new path.
33
53
 
34
54
  ## Update protocol
35
55
 
36
- 1. Pricing change: update the table above; commit as `chore(reference): update Anthropic pricing YYYY-MM-DD`.
37
- 2. size_budget revision: requires a Phase 11 reflector proposal under `[FRONTMATTER]` scope; do not hand-edit agent ranges.
56
+ 1. Pricing change for a single runtime: edit only that runtime's file in `reference/prices/`. Commit as `chore(reference/prices/<runtime>): update <runtime> pricing YYYY-MM-DD`.
57
+ 2. New runtime added to the 14-runtime map (`scripts/lib/install/runtimes.cjs` + `reference/runtime-models.md`): create `reference/prices/<runtime>.md`, add a row to the table above, and add a `reference/registry.json` entry under `type: "data"`.
58
+ 3. size_budget revisions: requires a Phase 11 reflector proposal under `[FRONTMATTER]` scope. Token ranges are runtime-neutral and live in `reference/prices/claude.md` as the canonical reference.
@@ -0,0 +1,21 @@
1
+ # Antigravity — Price Table (stub)
2
+
3
+ **Runtime:** `antigravity` (Antigravity)
4
+ **Phase 26 D-08 sub-table — STUB.** Placeholder so the price-table router (`reference/model-prices.md`) has a complete link list for all 14 runtimes. Runtime adapter authors fill this in with provenance citations in a later cycle.
5
+
6
+ **Provenance:** `<TODO: confirm at https://antigravity.google/docs>` — pending.
7
+
8
+ ## Pricing (USD per 1M tokens)
9
+
10
+ | Model | Tier | input_per_1m | output_per_1m | cached_input_per_1m |
11
+ |-------|------|--------------|---------------|----------------------|
12
+ | _TBD_ | opus | <TODO> | <TODO> | <TODO> |
13
+ | _TBD_ | sonnet | <TODO> | <TODO> | <TODO> |
14
+ | _TBD_ | haiku | <TODO> | <TODO> | <TODO> |
15
+
16
+ The budget-enforcer treats unparseable rows as missing and falls back to `reference/prices/claude.md` per the D-08 fallback chain.
17
+
18
+ ## Update protocol
19
+
20
+ 1. Confirm authoritative numbers at the runtime author's pricing docs and update; remove `<TODO>` tags.
21
+ 2. Add provenance citation matching the `reference/runtime-models.md` row for `id: "antigravity"`.
@@ -0,0 +1,21 @@
1
+ # Augment Code — Price Table (stub)
2
+
3
+ **Runtime:** `augment` (Augment Code)
4
+ **Phase 26 D-08 sub-table — STUB.** Placeholder so the price-table router (`reference/model-prices.md`) has a complete link list for all 14 runtimes. Runtime adapter authors fill this in with provenance citations in a later cycle.
5
+
6
+ **Provenance:** `<TODO: confirm at https://docs.augmentcode.com>` — pending.
7
+
8
+ ## Pricing (USD per 1M tokens)
9
+
10
+ | Model | Tier | input_per_1m | output_per_1m | cached_input_per_1m |
11
+ |-------|------|--------------|---------------|----------------------|
12
+ | _TBD_ | opus | <TODO> | <TODO> | <TODO> |
13
+ | _TBD_ | sonnet | <TODO> | <TODO> | <TODO> |
14
+ | _TBD_ | haiku | <TODO> | <TODO> | <TODO> |
15
+
16
+ The budget-enforcer treats unparseable rows as missing and falls back to `reference/prices/claude.md` per the D-08 fallback chain.
17
+
18
+ ## Update protocol
19
+
20
+ 1. Confirm authoritative numbers at the runtime author's pricing docs and update; remove `<TODO>` tags.
21
+ 2. Add provenance citation matching the `reference/runtime-models.md` row for `id: "augment"`.
@@ -0,0 +1,42 @@
1
+ # Anthropic — Claude Code Price Table
2
+
3
+ **Runtime:** `claude` (Claude Code)
4
+ **Phase 26 D-08 sub-table.** Authoritative pricing for the Anthropic models referenced in `reference/runtime-models.md` under `id: "claude"`. Read by `scripts/lib/budget-enforcer.cjs` (and indirectly by `hooks/budget-enforcer.ts`) to compute `est_cost_usd` per spawn.
5
+
6
+ **Provenance:** https://docs.anthropic.com/en/docs/about-claude/pricing — retrieved 2026-04-29, cycle `2026-04-29-v1.26`.
7
+
8
+ ## Pricing (USD per 1M tokens)
9
+
10
+ | Model | Tier | input_per_1m | output_per_1m | cached_input_per_1m |
11
+ |-------|------|--------------|---------------|----------------------|
12
+ | claude-haiku-4-5 | haiku | 1.00 | 5.00 | 0.10 |
13
+ | claude-sonnet-4-7 | sonnet | 3.00 | 15.00 | 0.30 |
14
+ | claude-sonnet-4-6 | sonnet | 3.00 | 15.00 | 0.30 |
15
+ | claude-opus-4-7 | opus | 15.00 | 75.00 | 1.50 |
16
+
17
+ ## size_budget → conservative token ranges
18
+
19
+ Agent frontmatter carries `size_budget: S|M|L|XL`. The router uses these conservative token ranges to compute a pre-spawn `est_cost_usd` without a live model call:
20
+
21
+ | size_budget | input_tokens (conservative max) | output_tokens (conservative max) |
22
+ |-------------|----------------------------------|-----------------------------------|
23
+ | S | 4000 | 1000 |
24
+ | M | 10000 | 2500 |
25
+ | L | 25000 | 6000 |
26
+ | XL | 60000 | 15000 |
27
+
28
+ ## Estimator formula
29
+
30
+ ```
31
+ est_cost_usd =
32
+ (input_tokens / 1_000_000) * input_per_1m
33
+ + (output_tokens / 1_000_000) * output_per_1m
34
+ ```
35
+
36
+ When `cache_hit: true`, the formula re-runs with `cached_input_per_1m` in place of `input_per_1m` for the input portion.
37
+
38
+ ## Update protocol
39
+
40
+ 1. Pricing change: update the table above; commit as `chore(reference/prices): update Anthropic pricing YYYY-MM-DD`.
41
+ 2. New model name added to `reference/runtime-models.md` under `id: "claude"`: add a row here with the same model string in the `Model` column. Tier comes from the canonical `tier_to_model` mapping.
42
+ 3. size_budget revision: requires a Phase 11 reflector proposal under `[FRONTMATTER]` scope; do not hand-edit agent ranges.
@@ -0,0 +1,23 @@
1
+ # Cline — Price Table (stub)
2
+
3
+ **Runtime:** `cline` (Cline)
4
+ **Phase 26 D-08 sub-table — STUB.** Placeholder so the price-table router (`reference/model-prices.md`) has a complete link list for all 14 runtimes. Runtime adapter authors fill this in with provenance citations in a later cycle.
5
+
6
+ **Provenance:** `<TODO: confirm at https://docs.cline.bot>` — pending.
7
+
8
+ **Note:** Cline is a BYO-API-key runtime — actual pricing is determined by whichever provider key the user supplies. The `tier_to_model` row in `reference/runtime-models.md` chooses a default model per tier; this table prices that default. Users of other providers should override locally.
9
+
10
+ ## Pricing (USD per 1M tokens)
11
+
12
+ | Model | Tier | input_per_1m | output_per_1m | cached_input_per_1m |
13
+ |-------|------|--------------|---------------|----------------------|
14
+ | _TBD_ | opus | <TODO> | <TODO> | <TODO> |
15
+ | _TBD_ | sonnet | <TODO> | <TODO> | <TODO> |
16
+ | _TBD_ | haiku | <TODO> | <TODO> | <TODO> |
17
+
18
+ The budget-enforcer treats unparseable rows as missing and falls back to `reference/prices/claude.md` per the D-08 fallback chain.
19
+
20
+ ## Update protocol
21
+
22
+ 1. Confirm authoritative numbers at the runtime author's pricing docs and update; remove `<TODO>` tags.
23
+ 2. Add provenance citation matching the `reference/runtime-models.md` row for `id: "cline"`.
@@ -0,0 +1,21 @@
1
+ # CodeBuddy — Price Table (stub)
2
+
3
+ **Runtime:** `codebuddy` (Tencent CodeBuddy)
4
+ **Phase 26 D-08 sub-table — STUB.** Placeholder so the price-table router (`reference/model-prices.md`) has a complete link list for all 14 runtimes. Runtime adapter authors fill this in with provenance citations in a later cycle.
5
+
6
+ **Provenance:** `<TODO: confirm at https://copilot.tencent.com>` — pending.
7
+
8
+ ## Pricing (USD per 1M tokens)
9
+
10
+ | Model | Tier | input_per_1m | output_per_1m | cached_input_per_1m |
11
+ |-------|------|--------------|---------------|----------------------|
12
+ | _TBD_ | opus | <TODO> | <TODO> | <TODO> |
13
+ | _TBD_ | sonnet | <TODO> | <TODO> | <TODO> |
14
+ | _TBD_ | haiku | <TODO> | <TODO> | <TODO> |
15
+
16
+ The budget-enforcer treats unparseable rows as missing and falls back to `reference/prices/claude.md` per the D-08 fallback chain.
17
+
18
+ ## Update protocol
19
+
20
+ 1. Confirm authoritative numbers at the runtime author's pricing docs and update; remove `<TODO>` tags.
21
+ 2. Add provenance citation matching the `reference/runtime-models.md` row for `id: "codebuddy"`.
@@ -0,0 +1,25 @@
1
+ # OpenAI — Codex CLI Price Table
2
+
3
+ **Runtime:** `codex` (OpenAI Codex CLI)
4
+ **Phase 26 D-08 sub-table.** Pricing for the OpenAI Codex tier referenced in `reference/runtime-models.md` under `id: "codex"`.
5
+
6
+ **Provenance:** `<TODO: confirm at https://openai.com/api/pricing/>` — retrieved 2026-04-29 (placeholder — v1.26.0 ships with seed numbers; runtime adapter authors confirm and PR before v1.27).
7
+
8
+ **Status:** placeholder values are taken from public OpenAI tier-positioning at the time of v1.26.0 ship. The cost-aggregator will surface drift if measured spend per spawn diverges from these figures by more than 20% after 10+ cycles.
9
+
10
+ ## Pricing (USD per 1M tokens)
11
+
12
+ | Model | Tier | input_per_1m | output_per_1m | cached_input_per_1m |
13
+ |-------|------|--------------|---------------|----------------------|
14
+ | gpt-5 | opus | 1.25 | 10.00 | 0.13 |
15
+ | gpt-5-mini | sonnet | 0.25 | 2.00 | 0.03 |
16
+ | gpt-5-nano | haiku | 0.05 | 0.40 | 0.01 |
17
+
18
+ ## Estimator formula
19
+
20
+ Same shape as `reference/prices/claude.md`; see that file for the formula and `size_budget` ranges. Token ranges are runtime-neutral.
21
+
22
+ ## Update protocol
23
+
24
+ 1. Confirm authoritative numbers at https://openai.com/api/pricing/ and update the table; remove the `<TODO>` provenance tag.
25
+ 2. New model added to `reference/runtime-models.md` under `id: "codex"`: add a row here with the matching model string and tier.
@@ -0,0 +1,21 @@
1
+ # GitHub Copilot — Price Table (stub)
2
+
3
+ **Runtime:** `copilot` (GitHub Copilot CLI)
4
+ **Phase 26 D-08 sub-table — STUB.** Placeholder so the price-table router (`reference/model-prices.md`) has a complete link list for all 14 runtimes. Runtime adapter authors fill this in with provenance citations in a later cycle.
5
+
6
+ **Provenance:** `<TODO: confirm at https://docs.github.com/en/copilot/about-github-copilot/plans-for-github-copilot>` — pending.
7
+
8
+ ## Pricing (USD per 1M tokens)
9
+
10
+ | Model | Tier | input_per_1m | output_per_1m | cached_input_per_1m |
11
+ |-------|------|--------------|---------------|----------------------|
12
+ | _TBD_ | opus | <TODO> | <TODO> | <TODO> |
13
+ | _TBD_ | sonnet | <TODO> | <TODO> | <TODO> |
14
+ | _TBD_ | haiku | <TODO> | <TODO> | <TODO> |
15
+
16
+ The budget-enforcer treats unparseable rows as missing and falls back to `reference/prices/claude.md` per the D-08 fallback chain.
17
+
18
+ ## Update protocol
19
+
20
+ 1. Confirm authoritative numbers at the runtime author's pricing docs and update; remove `<TODO>` tags.
21
+ 2. Add provenance citation matching the `reference/runtime-models.md` row for `id: "copilot"`.