@hegemonart/get-design-done 1.19.6 → 1.20.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 +4 -4
- package/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +60 -0
- package/README.md +12 -0
- package/agents/design-reflector.md +13 -0
- package/connections/connections.md +3 -0
- package/connections/figma.md +2 -0
- package/connections/gdd-state.md +186 -0
- package/hooks/budget-enforcer.ts +716 -0
- package/hooks/context-exhaustion.ts +251 -0
- package/hooks/gdd-read-injection-scanner.ts +172 -0
- package/hooks/hooks.json +3 -3
- package/package.json +19 -6
- package/reference/config-schema.md +2 -2
- package/reference/error-recovery.md +58 -0
- package/reference/registry.json +7 -0
- package/reference/schemas/budget.schema.json +42 -0
- package/reference/schemas/events.schema.json +55 -0
- package/reference/schemas/generated.d.ts +419 -0
- package/reference/schemas/iteration-budget.schema.json +36 -0
- package/reference/schemas/mcp-gdd-state-tools.schema.json +89 -0
- package/reference/schemas/rate-limits.schema.json +31 -0
- package/scripts/aggregate-agent-metrics.ts +282 -0
- package/scripts/codegen-schema-types.ts +149 -0
- package/scripts/lib/error-classifier.cjs +232 -0
- package/scripts/lib/error-classifier.d.cts +44 -0
- package/scripts/lib/event-stream/emitter.ts +88 -0
- package/scripts/lib/event-stream/index.ts +154 -0
- package/scripts/lib/event-stream/types.ts +127 -0
- package/scripts/lib/event-stream/writer.ts +154 -0
- package/scripts/lib/gdd-errors/classification.ts +124 -0
- package/scripts/lib/gdd-errors/index.ts +218 -0
- package/scripts/lib/gdd-state/gates.ts +216 -0
- package/scripts/lib/gdd-state/index.ts +167 -0
- package/scripts/lib/gdd-state/lockfile.ts +232 -0
- package/scripts/lib/gdd-state/mutator.ts +574 -0
- package/scripts/lib/gdd-state/parser.ts +523 -0
- package/scripts/lib/gdd-state/types.ts +179 -0
- package/scripts/lib/iteration-budget.cjs +205 -0
- package/scripts/lib/iteration-budget.d.cts +32 -0
- package/scripts/lib/jittered-backoff.cjs +112 -0
- package/scripts/lib/jittered-backoff.d.cts +38 -0
- package/scripts/lib/lockfile.cjs +177 -0
- package/scripts/lib/lockfile.d.cts +21 -0
- package/scripts/lib/prompt-sanitizer/index.ts +435 -0
- package/scripts/lib/prompt-sanitizer/patterns.ts +173 -0
- package/scripts/lib/rate-guard.cjs +365 -0
- package/scripts/lib/rate-guard.d.cts +38 -0
- package/scripts/mcp-servers/gdd-state/schemas/add_blocker.schema.json +67 -0
- package/scripts/mcp-servers/gdd-state/schemas/add_decision.schema.json +68 -0
- package/scripts/mcp-servers/gdd-state/schemas/add_must_have.schema.json +68 -0
- package/scripts/mcp-servers/gdd-state/schemas/checkpoint.schema.json +51 -0
- package/scripts/mcp-servers/gdd-state/schemas/frontmatter_update.schema.json +62 -0
- package/scripts/mcp-servers/gdd-state/schemas/get.schema.json +51 -0
- package/scripts/mcp-servers/gdd-state/schemas/probe_connections.schema.json +75 -0
- package/scripts/mcp-servers/gdd-state/schemas/resolve_blocker.schema.json +66 -0
- package/scripts/mcp-servers/gdd-state/schemas/set_status.schema.json +47 -0
- package/scripts/mcp-servers/gdd-state/schemas/transition_stage.schema.json +70 -0
- package/scripts/mcp-servers/gdd-state/schemas/update_progress.schema.json +58 -0
- package/scripts/mcp-servers/gdd-state/server.ts +288 -0
- package/scripts/mcp-servers/gdd-state/tools/add_blocker.ts +72 -0
- package/scripts/mcp-servers/gdd-state/tools/add_decision.ts +89 -0
- package/scripts/mcp-servers/gdd-state/tools/add_must_have.ts +113 -0
- package/scripts/mcp-servers/gdd-state/tools/checkpoint.ts +60 -0
- package/scripts/mcp-servers/gdd-state/tools/frontmatter_update.ts +91 -0
- package/scripts/mcp-servers/gdd-state/tools/get.ts +51 -0
- package/scripts/mcp-servers/gdd-state/tools/index.ts +51 -0
- package/scripts/mcp-servers/gdd-state/tools/probe_connections.ts +73 -0
- package/scripts/mcp-servers/gdd-state/tools/resolve_blocker.ts +84 -0
- package/scripts/mcp-servers/gdd-state/tools/set_status.ts +54 -0
- package/scripts/mcp-servers/gdd-state/tools/shared.ts +194 -0
- package/scripts/mcp-servers/gdd-state/tools/transition_stage.ts +80 -0
- package/scripts/mcp-servers/gdd-state/tools/update_progress.ts +81 -0
- package/scripts/validate-frontmatter.ts +114 -0
- package/scripts/validate-schemas.ts +401 -0
- package/skills/brief/SKILL.md +15 -6
- package/skills/design/SKILL.md +31 -13
- package/skills/explore/SKILL.md +41 -17
- package/skills/health/SKILL.md +15 -4
- package/skills/optimize/SKILL.md +3 -3
- package/skills/pause/SKILL.md +16 -10
- package/skills/plan/SKILL.md +33 -17
- package/skills/progress/SKILL.md +15 -11
- package/skills/resume/SKILL.md +19 -10
- package/skills/settings/SKILL.md +11 -3
- package/skills/todo/SKILL.md +12 -3
- package/skills/verify/SKILL.md +65 -29
- package/hooks/budget-enforcer.js +0 -329
- package/hooks/context-exhaustion.js +0 -127
- package/hooks/gdd-read-injection-scanner.js +0 -39
- package/scripts/aggregate-agent-metrics.js +0 -173
- package/scripts/validate-frontmatter.cjs +0 -68
- package/scripts/validate-schemas.cjs +0 -242
package/skills/settings/SKILL.md
CHANGED
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
name: gdd-settings
|
|
3
3
|
description: "Manage .design/config.json settings. Subcommands: profile, parallelism, cleanup, show."
|
|
4
4
|
argument-hint: "<profile <name>|parallelism <key> <value>|cleanup|show>"
|
|
5
|
-
tools: Read, Write, AskUserQuestion, Bash
|
|
5
|
+
tools: Read, Write, AskUserQuestion, Bash, mcp__gdd_state__get, mcp__gdd_state__frontmatter_update
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# gdd-settings
|
|
9
9
|
|
|
10
|
-
Manages `.design/config.json` — the per-project config for model profile and parallelism. See `reference/config-schema.md` for the full schema.
|
|
10
|
+
Manages `.design/config.json` — the per-project config for model profile and parallelism. See `reference/config-schema.md` for the full schema. This skill also supports patching non-stage STATE.md frontmatter keys (`cycle`, `wave`, custom keys) via `mcp__gdd_state__frontmatter_update`. See **STATE.md frontmatter** below.
|
|
11
11
|
|
|
12
12
|
## Subcommands
|
|
13
13
|
|
|
14
14
|
### `show`
|
|
15
15
|
|
|
16
|
-
Print the current `.design/config.json` contents, nicely formatted. If the file is missing, print the defaults with a note that no config exists yet.
|
|
16
|
+
Print the current `.design/config.json` contents, nicely formatted. If the file is missing, print the defaults with a note that no config exists yet. Also call `mcp__gdd_state__get` to print the current STATE.md frontmatter keys (cycle, wave, model_profile) alongside config.json for a unified view.
|
|
17
17
|
|
|
18
18
|
### `profile <name>`
|
|
19
19
|
|
|
@@ -50,6 +50,14 @@ Always:
|
|
|
50
50
|
2. Merge the single field being changed — never overwrite unrelated fields.
|
|
51
51
|
3. Write back as pretty JSON (2-space indent, trailing newline).
|
|
52
52
|
|
|
53
|
+
## STATE.md frontmatter
|
|
54
|
+
|
|
55
|
+
For any STATE.md frontmatter patch (cycle, wave, or project-custom keys), call `mcp__gdd_state__frontmatter_update({ patch: { <key>: <value> } })`. Do not `Edit` or `Write` STATE.md directly.
|
|
56
|
+
|
|
57
|
+
**Stage-patch guard:** this skill cannot patch `stage`. If the user attempts to set `stage` here, reject with: "Use /gdd:brief, /gdd:explore, etc. for stage transitions. The settings skill is for non-stage frontmatter only." The MCP tool itself rejects `stage` patches with a VALIDATION error (surfaced by `mcp__gdd_state__frontmatter_update`), which this prose surfaces up-front so the user gets a clear message before the tool round-trip.
|
|
58
|
+
|
|
59
|
+
This surface is STATE.md-only. `.design/config.json` mutations continue to use `Read` + `Write` directly (out of scope for the 11-tool MCP catalog).
|
|
60
|
+
|
|
53
61
|
## Default Config
|
|
54
62
|
|
|
55
63
|
If `.design/config.json` does not exist, create it with:
|
package/skills/todo/SKILL.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
name: gdd-todo
|
|
3
3
|
description: "Design backlog — add/list/pick design tasks. Writes to .design/TODO.md."
|
|
4
4
|
argument-hint: "<add|list|pick> [text]"
|
|
5
|
-
tools: Read, Write, AskUserQuestion
|
|
5
|
+
tools: Read, Write, AskUserQuestion, mcp__gdd_state__get, mcp__gdd_state__add_decision, mcp__gdd_state__add_must_have
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# /gdd:todo
|
|
9
9
|
|
|
10
|
-
**Role:** Design todo list. Three subcommands: `add`, `list`, `pick`. Backing store: `.design/TODO.md`.
|
|
10
|
+
**Role:** Design todo list. Three subcommands: `add`, `list`, `pick`. Backing store: `.design/TODO.md`. For items that are pipeline-level decisions or must-haves (not free-form backlog), route through the `gdd-state` MCP tools instead — see **Pipeline-linked items** below.
|
|
11
11
|
|
|
12
12
|
## File format
|
|
13
13
|
|
|
@@ -37,7 +37,8 @@ If text omitted, use `AskUserQuestion`: "What todo item? (include priority P0-P3
|
|
|
37
37
|
Create TODO.md from the template above if missing.
|
|
38
38
|
|
|
39
39
|
### list
|
|
40
|
-
|
|
40
|
+
1. Call `mcp__gdd_state__get` → pipeline-level decisions + must-haves (shown as context at the top).
|
|
41
|
+
2. Read `.design/TODO.md` (file outside the MCP catalog). Print all `- [ ]` and `- [-]` items grouped by priority section, with index numbers.
|
|
41
42
|
|
|
42
43
|
### pick
|
|
43
44
|
Read `.design/TODO.md`. Collect unchecked items. Use `AskUserQuestion` to let the user pick one. Rewrite the chosen line as:
|
|
@@ -46,9 +47,17 @@ Read `.design/TODO.md`. Collect unchecked items. Use `AskUserQuestion` to let th
|
|
|
46
47
|
```
|
|
47
48
|
Print "Picked: <item>".
|
|
48
49
|
|
|
50
|
+
## Pipeline-linked items
|
|
51
|
+
|
|
52
|
+
When the user promotes a todo to a pipeline decision or must-have, route through MCP instead of TODO.md:
|
|
53
|
+
|
|
54
|
+
- Decision → `mcp__gdd_state__add_decision` with `{ id: "D-XX", text: "...", status: "locked"|"tentative" }`.
|
|
55
|
+
- Must-have → `mcp__gdd_state__add_must_have` with `{ id: "M-XX", text: "...", status: "pending" }`.
|
|
56
|
+
|
|
49
57
|
## Constraints
|
|
50
58
|
|
|
51
59
|
- Do not modify files outside `.design/`.
|
|
52
60
|
- Preserve existing sections and ordering on write.
|
|
61
|
+
- Do not mutate STATE.md directly — use the MCP tools above.
|
|
53
62
|
|
|
54
63
|
## TODO COMPLETE
|
package/skills/verify/SKILL.md
CHANGED
|
@@ -3,6 +3,7 @@ name: verify
|
|
|
3
3
|
description: "Stage 5 of 5 — spawns design-auditor, design-verifier, and design-integration-checker in sequence; interprets pass/gap result; handles gap-response loop with inline fix (Phase 5 will add AGENT-12 remediation agent). Thin orchestrator."
|
|
4
4
|
argument-hint: "[--auto]"
|
|
5
5
|
user-invocable: true
|
|
6
|
+
tools: mcp__gdd_state__get, mcp__gdd_state__transition_stage, mcp__gdd_state__add_must_have, mcp__gdd_state__add_blocker, mcp__gdd_state__resolve_blocker, mcp__gdd_state__update_progress, mcp__gdd_state__set_status, mcp__gdd_state__checkpoint, mcp__gdd_state__probe_connections
|
|
6
7
|
---
|
|
7
8
|
|
|
8
9
|
# Get Design Done — Verify
|
|
@@ -13,11 +14,30 @@ user-invocable: true
|
|
|
13
14
|
|
|
14
15
|
## State Integration
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
### Stage entry
|
|
18
|
+
|
|
19
|
+
1. `mcp__gdd_state__transition_stage` with `to: "verify"`.
|
|
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
|
+
3. Resume detection (read `state.position.status` from the snapshot):
|
|
22
|
+
- If `status==in_progress` and `.design/DESIGN-VERIFICATION.md` exists: RESUME — skip re-spawning agents, go to Step 2 (gap-response loop).
|
|
23
|
+
- Otherwise: call `mcp__gdd_state__update_progress` with `task_progress: "0/3"`, `status: "in_progress"` to open the stage, then proceed to Step 1.
|
|
24
|
+
4. If STATE.md is missing entirely (edge case — verify is never the entry point): block with "No STATE.md found — run /get-design-done:discover first." Do NOT attempt to create a skeleton from verify; upstream stages own bootstrap.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Flipping a must-have status
|
|
29
|
+
|
|
30
|
+
When verification concludes that M-XX is satisfied (or failed), record the result by issuing:
|
|
31
|
+
|
|
32
|
+
`mcp__gdd_state__add_must_have` with the SAME `id` as the existing entry and the updated `status`:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{ "id": "M-03", "text": "Dark mode toggle persists to localStorage", "status": "pass" }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The gdd-state mutator treats an `add_must_have` with an existing id as an **update-in-place**, not a duplicate append. The entry's position in the `<must_haves>` block is preserved. This is intentional design — verify doesn't need a dedicated `update_must_have_status` tool because `add_must_have` handles both cases correctly.
|
|
39
|
+
|
|
40
|
+
Pass the original `text` verbatim when you're only flipping the status; supplying a changed `text` overwrites the prose in-place as well (useful when the M-XX description was imprecise and the verifier can restate it). Omit `text` by passing the value from the earlier `mcp__gdd_state__get` snapshot.
|
|
21
41
|
|
|
22
42
|
---
|
|
23
43
|
|
|
@@ -37,7 +57,7 @@ Step P2 — Live tool call:
|
|
|
37
57
|
→ Error containing "permission"/blocked → preview: permission_denied
|
|
38
58
|
→ Any other error → preview: unreachable
|
|
39
59
|
|
|
40
|
-
|
|
60
|
+
Record the preview probe result via `mcp__gdd_state__probe_connections` (batched with the storybook and chromatic probes below — one call per stage, see "Batched connections write" at the end of this section).
|
|
41
61
|
```
|
|
42
62
|
|
|
43
63
|
When `preview: available`, the design-verifier agent runs Phase 4B — Screenshot Evidence to resolve `? VISUAL` heuristic flags with real screenshot evidence. See `agents/design-verifier.md` Phase 4B for the screenshot evidence loop.
|
|
@@ -60,13 +80,13 @@ Step B2 — Dev server detection:
|
|
|
60
80
|
→ Returns JSON → storybook: available (compat endpoint)
|
|
61
81
|
→ Fails → storybook: unavailable
|
|
62
82
|
|
|
63
|
-
|
|
83
|
+
Record the storybook probe result for the batched `mcp__gdd_state__probe_connections` call (see below).
|
|
64
84
|
|
|
65
85
|
---
|
|
66
86
|
|
|
67
87
|
### Storybook A11y Loop (when storybook: available)
|
|
68
88
|
|
|
69
|
-
If `storybook
|
|
89
|
+
If `state.connections.storybook === "available"` (from the earlier `mcp__gdd_state__get` snapshot):
|
|
70
90
|
1. Run: Bash: npx storybook test --ci 2>&1 | tee .design/storybook-a11y-report.txt
|
|
71
91
|
2. Read .design/storybook-a11y-report.txt — pass to design-verifier as additional a11y evidence
|
|
72
92
|
3. design-verifier reads this file in its a11y gap analysis section and annotates DESIGN-VERIFICATION.md with per-story violations
|
|
@@ -91,7 +111,21 @@ Step C2 — Token check:
|
|
|
91
111
|
→ false → chromatic: unavailable
|
|
92
112
|
|
|
93
113
|
Also check: if storybook: not_configured → chromatic effectively unavailable (emit note, do not run).
|
|
94
|
-
|
|
114
|
+
Record the chromatic probe result for the batched `mcp__gdd_state__probe_connections` call below.
|
|
115
|
+
|
|
116
|
+
### Batched connections write
|
|
117
|
+
|
|
118
|
+
After all three probes (preview, storybook, chromatic) have a verdict, call `mcp__gdd_state__probe_connections` ONCE with `probe_results` = an array of `{ name, status }` entries — one per probed connection. Example:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
[
|
|
122
|
+
{ "name": "preview", "status": "available" },
|
|
123
|
+
{ "name": "storybook", "status": "unavailable" },
|
|
124
|
+
{ "name": "chromatic", "status": "not_configured" }
|
|
125
|
+
]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Unspecified connections keep their existing value. Do NOT issue multiple `probe_connections` calls — the tool is designed for a single batch write per stage.
|
|
95
129
|
|
|
96
130
|
### Chromatic Visual Delta (when chromatic: available)
|
|
97
131
|
|
|
@@ -135,7 +169,7 @@ Also pass post-handoff context to design-auditor: auditor skips DESIGN-PLAN.md r
|
|
|
135
169
|
- Read `.design/config.json` `parallelism` (or defaults from `reference/config-schema.md`).
|
|
136
170
|
- Apply rules from `reference/parallelism-rules.md`.
|
|
137
171
|
- `design-verifier` depends on `design-auditor` output (rule 1) → serial between those two. `design-integration-checker` is independent of the auditor's *file* output but runs after verifier in the current sequence; if config opts in, `design-auditor` and `design-integration-checker` can parallelize (disjoint writes). Default: serial.
|
|
138
|
-
-
|
|
172
|
+
- Record `<parallelism_decision>` via `mcp__gdd_state__set_status` (e.g., `status: "verify_parallelism_decided: <serial|parallel>"`) before spawning. Do not write STATE.md directly.
|
|
139
173
|
|
|
140
174
|
## Step 1 — Spawn Auditor + Verifier + Integration Checker
|
|
141
175
|
|
|
@@ -169,7 +203,7 @@ Emit `## AUDIT COMPLETE` when done.
|
|
|
169
203
|
""")
|
|
170
204
|
```
|
|
171
205
|
|
|
172
|
-
Wait for `## AUDIT COMPLETE` in the agent response. Once detected,
|
|
206
|
+
Wait for `## AUDIT COMPLETE` in the agent response. Once detected, call `mcp__gdd_state__update_progress` with `task_progress: "1/3"` and a short `status` summary (e.g., `status: "audit_done"`).
|
|
173
207
|
|
|
174
208
|
### 1b-gate. Lazy gate — should design-verifier run?
|
|
175
209
|
|
|
@@ -193,7 +227,7 @@ Spawn the cheap Haiku gate before the expensive verifier:
|
|
|
193
227
|
|
|
194
228
|
Wait for `## GATE COMPLETE`. Parse the JSON:
|
|
195
229
|
|
|
196
|
-
- `spawn: false` → append pending telemetry row `{ts, agent: "design-verifier", tier: "skipped", tokens_in: 0, tokens_out: 0, cache_hit: false, est_cost_usd: 0, lazy_skipped: true, gate_rationale: "<from gate>", cycle, phase}` (PreToolUse hook from 10.1-01 flushes on next tool use; orchestrator MAY stub-append directly to `.design/telemetry/costs.jsonl` until 10.1-05 lands). Skip 1b.
|
|
230
|
+
- `spawn: false` → append pending telemetry row `{ts, agent: "design-verifier", tier: "skipped", tokens_in: 0, tokens_out: 0, cache_hit: false, est_cost_usd: 0, lazy_skipped: true, gate_rationale: "<from gate>", cycle, phase}` (PreToolUse hook from 10.1-01 flushes on next tool use; orchestrator MAY stub-append directly to `.design/telemetry/costs.jsonl` until 10.1-05 lands). Skip 1b. Call `mcp__gdd_state__update_progress` with `task_progress: "2/3"` and `status: "verifier_gate_skipped"`. Emit `design-verifier skipped — gate rationale: <rationale>`.
|
|
197
231
|
- `spawn: true` → proceed to 1b as currently written.
|
|
198
232
|
|
|
199
233
|
### 1b. Run design-verifier (reads auditor output as additional input)
|
|
@@ -231,7 +265,7 @@ by structured gap list, then `## VERIFICATION COMPLETE`. If no gaps, just emit `
|
|
|
231
265
|
""")
|
|
232
266
|
```
|
|
233
267
|
|
|
234
|
-
Wait for `## VERIFICATION COMPLETE` in the agent response. Once detected,
|
|
268
|
+
Wait for `## VERIFICATION COMPLETE` in the agent response. Once detected, call `mcp__gdd_state__update_progress` with `task_progress: "2/3"` and a short `status` summary (e.g., `status: "verifier_done"`).
|
|
235
269
|
|
|
236
270
|
### 1c-gate. Lazy gate — should design-integration-checker run?
|
|
237
271
|
|
|
@@ -255,7 +289,7 @@ Same pattern as 1b-gate:
|
|
|
255
289
|
|
|
256
290
|
Wait for `## GATE COMPLETE`. Parse JSON:
|
|
257
291
|
|
|
258
|
-
- `spawn: false` → append `lazy_skipped: true` telemetry row (same shape), skip 1c,
|
|
292
|
+
- `spawn: false` → append `lazy_skipped: true` telemetry row (same shape), skip 1c, call `mcp__gdd_state__update_progress` with `task_progress: "3/3"` and `status: "integration_checker_gate_skipped"`, emit `design-integration-checker skipped — gate rationale: <rationale>`.
|
|
259
293
|
- `spawn: true` → proceed to 1c as currently written.
|
|
260
294
|
|
|
261
295
|
### 1c. Run design-integration-checker (post-verification decision wiring check)
|
|
@@ -282,7 +316,7 @@ Emit `## INTEGRATION CHECK COMPLETE` when done.
|
|
|
282
316
|
""")
|
|
283
317
|
```
|
|
284
318
|
|
|
285
|
-
Wait for `## INTEGRATION CHECK COMPLETE` in the agent response. Once detected,
|
|
319
|
+
Wait for `## INTEGRATION CHECK COMPLETE` in the agent response. Once detected, call `mcp__gdd_state__update_progress` with `task_progress: "3/3"` and a short `status` summary (e.g., `status: "integration_check_done"`).
|
|
286
320
|
|
|
287
321
|
**Note:** Integration-checker findings (Orphaned and Missing decisions) are treated as additional gaps and fed into the gap-response loop in Step 2 alongside verifier gaps.
|
|
288
322
|
|
|
@@ -300,14 +334,14 @@ Merge verifier gaps (G-NN entries) and integration-checker gaps (Orphaned/Missin
|
|
|
300
334
|
|
|
301
335
|
### If NO gaps from either source (PASS):
|
|
302
336
|
|
|
303
|
-
-
|
|
304
|
-
- Go to **
|
|
337
|
+
- For each M-XX from the earlier `mcp__gdd_state__get` snapshot (`state.must_haves`): call `mcp__gdd_state__add_must_have` with the same `id`, the same `text`, and `status: "pass"`. The mutator updates in-place (see "Flipping a must-have status" above).
|
|
338
|
+
- Go to **Stage exit** with status=completed.
|
|
305
339
|
|
|
306
340
|
### If GAPS FOUND (from either source):
|
|
307
341
|
|
|
308
342
|
- Parse all gaps (verifier + integration-checker combined).
|
|
309
343
|
- Count gaps by severity (BLOCKER, MAJOR, MINOR, COSMETIC).
|
|
310
|
-
- If `auto_mode=true`: preserve DESIGN-VERIFICATION.md,
|
|
344
|
+
- If `auto_mode=true`: preserve DESIGN-VERIFICATION.md, call `mcp__gdd_state__set_status` with `status: "blocked"`, then call `mcp__gdd_state__add_blocker` with `stage: "verify"` and `text: "N blockers found — see .design/DESIGN-VERIFICATION.md and integration-checker output"` (the mutator stamps the ISO date automatically). Exit with message:
|
|
311
345
|
```
|
|
312
346
|
Verification failed — N gaps found (X blockers, Y majors, Z minors, W cosmetics).
|
|
313
347
|
Report: .design/DESIGN-VERIFICATION.md
|
|
@@ -340,9 +374,9 @@ Choose:
|
|
|
340
374
|
### If user chose [2] Save and exit:
|
|
341
375
|
|
|
342
376
|
- Preserve DESIGN-VERIFICATION.md.
|
|
343
|
-
-
|
|
344
|
-
-
|
|
345
|
-
-
|
|
377
|
+
- Call `mcp__gdd_state__set_status` with `status: "blocked"`.
|
|
378
|
+
- Call `mcp__gdd_state__add_blocker` with `stage: "verify"` and `text: "N gaps outstanding — see .design/DESIGN-VERIFICATION.md"` (ISO date stamped by the mutator).
|
|
379
|
+
- Call `mcp__gdd_state__checkpoint` to record the save-and-exit checkpoint.
|
|
346
380
|
- Exit:
|
|
347
381
|
```
|
|
348
382
|
Gaps saved. Resume with: /get-design-done:verify
|
|
@@ -351,9 +385,9 @@ Choose:
|
|
|
351
385
|
|
|
352
386
|
### If user chose [3] Accept as-is:
|
|
353
387
|
|
|
354
|
-
-
|
|
355
|
-
-
|
|
356
|
-
- Go to **
|
|
388
|
+
- For each unmet must-have (from the earlier snapshot, comparing against verifier gaps): call `mcp__gdd_state__add_must_have` with the same `id`, the same `text`, and `status: "fail"` (update-in-place idiom). Then proceed to exit.
|
|
389
|
+
- Call `mcp__gdd_state__add_blocker` with `stage: "verify"` and `text: "accepted with N unresolved gaps"`.
|
|
390
|
+
- Go to **Stage exit** with status=completed.
|
|
357
391
|
|
|
358
392
|
### If user chose [1] Fix now:
|
|
359
393
|
|
|
@@ -381,7 +415,7 @@ Context:
|
|
|
381
415
|
auto_mode: <true|false>
|
|
382
416
|
|
|
383
417
|
Emit ## FIX COMPLETE when all in-scope gaps have been attempted (partial success is still ## FIX COMPLETE).
|
|
384
|
-
|
|
418
|
+
Record any gap that could not be fixed via mcp__gdd_state__add_blocker with stage: "verify".
|
|
385
419
|
""")
|
|
386
420
|
|
|
387
421
|
Wait for `## FIX COMPLETE` in the agent response before continuing.
|
|
@@ -417,11 +451,13 @@ Write updated .design/DESIGN-VERIFICATION.md. Emit ## GAPS FOUND (if any), then
|
|
|
417
451
|
|
|
418
452
|
---
|
|
419
453
|
|
|
420
|
-
##
|
|
454
|
+
## Stage exit
|
|
421
455
|
|
|
422
|
-
1.
|
|
423
|
-
2.
|
|
424
|
-
|
|
456
|
+
1. Call `mcp__gdd_state__update_progress` with `task_progress: "<verified>/<total>"` (the total is `state.must_haves.length` from the entry snapshot; verified is the count set to `pass`) and `status: "verify_complete"`.
|
|
457
|
+
2. Call `mcp__gdd_state__set_status` with one of:
|
|
458
|
+
- `status: "pipeline_complete"` — all must-haves passed and no outstanding gaps.
|
|
459
|
+
- `status: "verify_failed_requires_loop"` — gaps remain (save-and-exit, accept-as-is with fails, or auto-mode blocker).
|
|
460
|
+
3. Call `mcp__gdd_state__checkpoint` — stamps `frontmatter.last_checkpoint` and appends a `verify_completed_at` timestamp entry. No direct STATE.md writes; the checkpoint tool owns the final persist.
|
|
425
461
|
|
|
426
462
|
---
|
|
427
463
|
|
package/hooks/budget-enforcer.js
DELETED
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* budget-enforcer.js — PreToolUse hook (matcher: Agent)
|
|
4
|
-
*
|
|
5
|
-
* Intercepts every Agent tool spawn. Consults:
|
|
6
|
-
* (a) router decision (from tool_input.context.router_decision if supplied)
|
|
7
|
-
* (b) .design/cache-manifest.json for short-circuit cached answers (D-05)
|
|
8
|
-
* (c) .design/budget.json for tier_overrides + caps (D-01, D-04, D-10)
|
|
9
|
-
*
|
|
10
|
-
* Enforcement (D-02, D-03, D-11):
|
|
11
|
-
* - enforcement_mode: "enforce" + 100% cap → block with actionable error
|
|
12
|
-
* - enforcement_mode: "enforce" + 80% soft-threshold + auto_downgrade_on_cap → rewrite tier to haiku
|
|
13
|
-
* - enforcement_mode: "warn" → log warning, allow spawn
|
|
14
|
-
* - enforcement_mode: "log" → advisory only
|
|
15
|
-
*
|
|
16
|
-
* Logs every decision to .design/telemetry/costs.jsonl (OPT-09 schema).
|
|
17
|
-
* Every telemetry write fires a detached child aggregator (scripts/aggregate-agent-metrics.js)
|
|
18
|
-
* that rebuilds .design/agent-metrics.json incrementally.
|
|
19
|
-
*
|
|
20
|
-
* Hook type: PreToolUse
|
|
21
|
-
* Input: JSON on stdin { tool_name, tool_input }
|
|
22
|
-
* Output: JSON on stdout { continue, suppressOutput, message, modified_tool_input? }
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
'use strict';
|
|
26
|
-
|
|
27
|
-
const fs = require('fs');
|
|
28
|
-
const path = require('path');
|
|
29
|
-
const readline = require('readline');
|
|
30
|
-
const { spawn } = require('child_process');
|
|
31
|
-
|
|
32
|
-
const BUDGET_PATH = path.join(process.cwd(), '.design', 'budget.json');
|
|
33
|
-
const MANIFEST_PATH = path.join(process.cwd(), '.design', 'cache-manifest.json');
|
|
34
|
-
const TELEMETRY_PATH = path.join(process.cwd(), '.design', 'telemetry', 'costs.jsonl');
|
|
35
|
-
const PHASE_TOTALS_PATH = path.join(process.cwd(), '.design', 'telemetry', 'phase-totals.json');
|
|
36
|
-
const STATE_PATH = path.join(process.cwd(), '.design', 'STATE.md');
|
|
37
|
-
|
|
38
|
-
// ---- budget.json loader with defaults per D-12 ----
|
|
39
|
-
function loadBudget() {
|
|
40
|
-
const defaults = {
|
|
41
|
-
per_task_cap_usd: 2.00,
|
|
42
|
-
per_phase_cap_usd: 20.00,
|
|
43
|
-
tier_overrides: {},
|
|
44
|
-
auto_downgrade_on_cap: true,
|
|
45
|
-
cache_ttl_seconds: 3600,
|
|
46
|
-
enforcement_mode: 'enforce'
|
|
47
|
-
};
|
|
48
|
-
if (!fs.existsSync(BUDGET_PATH)) return defaults;
|
|
49
|
-
try { return { ...defaults, ...JSON.parse(fs.readFileSync(BUDGET_PATH, 'utf8')) }; }
|
|
50
|
-
catch { return defaults; }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ---- cumulative phase spend (WR-02) ----
|
|
54
|
-
// Reads from the lightweight phase-totals.json written by aggregate-agent-metrics.js
|
|
55
|
-
// instead of replaying the full costs.jsonl on every hook invocation.
|
|
56
|
-
// Falls back to 0 when the file doesn't exist yet (early in a session).
|
|
57
|
-
function currentPhaseSpend(phase) {
|
|
58
|
-
if (fs.existsSync(PHASE_TOTALS_PATH)) {
|
|
59
|
-
try {
|
|
60
|
-
const data = JSON.parse(fs.readFileSync(PHASE_TOTALS_PATH, 'utf8'));
|
|
61
|
-
return Number(data.totals?.[phase] || 0);
|
|
62
|
-
} catch { /* fall through */ }
|
|
63
|
-
}
|
|
64
|
-
// Fallback: replay JSONL when phase-totals.json not yet written (first spawn of session).
|
|
65
|
-
if (!fs.existsSync(TELEMETRY_PATH)) return 0;
|
|
66
|
-
const lines = fs.readFileSync(TELEMETRY_PATH, 'utf8').split(/\r?\n/).filter(Boolean);
|
|
67
|
-
let sum = 0;
|
|
68
|
-
for (const line of lines) {
|
|
69
|
-
try {
|
|
70
|
-
const row = JSON.parse(line);
|
|
71
|
-
if (row.phase === phase) sum += Number(row.est_cost_usd || 0);
|
|
72
|
-
} catch { /* tolerant */ }
|
|
73
|
-
}
|
|
74
|
-
return sum;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ---- cycle + phase reader (STATE.md frontmatter) ----
|
|
78
|
-
function readCycleAndPhase() {
|
|
79
|
-
const defaults = { cycle: 'unknown', phase: 'unknown' };
|
|
80
|
-
if (!fs.existsSync(STATE_PATH)) return defaults;
|
|
81
|
-
try {
|
|
82
|
-
const content = fs.readFileSync(STATE_PATH, 'utf8');
|
|
83
|
-
// Match the first frontmatter block: between opening '---' and next '---'
|
|
84
|
-
const fm = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
85
|
-
const body = fm ? fm[1] : content;
|
|
86
|
-
const cycleMatch = body.match(/^cycle:\s*"?([^"\n]+)"?/m);
|
|
87
|
-
const phaseMatch = body.match(/^phase:\s*"?([^"\n]+)"?/m);
|
|
88
|
-
return {
|
|
89
|
-
cycle: cycleMatch ? cycleMatch[1].trim() : 'unknown',
|
|
90
|
-
phase: phaseMatch ? phaseMatch[1].trim() : 'unknown',
|
|
91
|
-
};
|
|
92
|
-
} catch {
|
|
93
|
-
return defaults;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Deprecated alias for plan 01 callers (and any other hook/script that imports this).
|
|
98
|
-
// Returns only the phase string; prefer readCycleAndPhase() for new code.
|
|
99
|
-
function currentPhase() {
|
|
100
|
-
return readCycleAndPhase().phase;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ---- cache short-circuit (D-05) ----
|
|
104
|
-
function cacheLookup(agent, inputHash) {
|
|
105
|
-
if (!fs.existsSync(MANIFEST_PATH)) return null;
|
|
106
|
-
try {
|
|
107
|
-
const manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
|
|
108
|
-
const entry = manifest.entries?.[`${agent}:${inputHash}`];
|
|
109
|
-
if (!entry) return null;
|
|
110
|
-
const age = Date.now() / 1000 - entry.ts_unix;
|
|
111
|
-
if (age > (manifest.ttl_seconds || 3600)) return null;
|
|
112
|
-
return entry.result; // cached blob
|
|
113
|
-
} catch { return null; }
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ---- tier resolution (D-04) ----
|
|
117
|
-
function resolveTier(agent, agentDefaultTier, overrides) {
|
|
118
|
-
return overrides?.[agent] || agentDefaultTier || 'sonnet';
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ---- detached aggregator invocation ----
|
|
122
|
-
// Fire-and-forget: do not block the hook. The aggregator reads costs.jsonl tail
|
|
123
|
-
// and rewrites .design/agent-metrics.json atomically.
|
|
124
|
-
function spawnAggregator() {
|
|
125
|
-
try {
|
|
126
|
-
const aggregatorPath = path.join(process.cwd(), 'scripts', 'aggregate-agent-metrics.js');
|
|
127
|
-
if (!fs.existsSync(aggregatorPath)) return; // script not installed — fail open
|
|
128
|
-
const child = spawn('node', [aggregatorPath], {
|
|
129
|
-
cwd: process.cwd(),
|
|
130
|
-
detached: true,
|
|
131
|
-
stdio: 'ignore',
|
|
132
|
-
env: { PATH: process.env.PATH }, // IN-02: minimal env; aggregator needs no secrets
|
|
133
|
-
});
|
|
134
|
-
child.unref();
|
|
135
|
-
} catch {
|
|
136
|
-
// Aggregator failures are non-fatal to the hook.
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// ---- locked-schema row builder (OPT-09) ----
|
|
141
|
-
function buildTelemetryRow(partial) {
|
|
142
|
-
const { cycle, phase } = partial._cyclePhase || readCycleAndPhase();
|
|
143
|
-
// The nine mandatory fields per OPT-09, always in this order.
|
|
144
|
-
const row = {
|
|
145
|
-
ts: partial.ts || new Date().toISOString(),
|
|
146
|
-
agent: String(partial.agent || 'unknown'),
|
|
147
|
-
tier: String(partial.tier || 'unknown'),
|
|
148
|
-
tokens_in: Number(partial.tokens_in || 0),
|
|
149
|
-
tokens_out: Number(partial.tokens_out || 0),
|
|
150
|
-
cache_hit: Boolean(partial.cache_hit),
|
|
151
|
-
est_cost_usd: Number(partial.est_cost_usd || 0),
|
|
152
|
-
cycle: partial.cycle || cycle,
|
|
153
|
-
phase: partial.phase || phase,
|
|
154
|
-
};
|
|
155
|
-
// Optional diagnostic fields (Phase 11 reflector ignores unknown fields gracefully).
|
|
156
|
-
if (partial.tier_downgraded !== undefined) row.tier_downgraded = Boolean(partial.tier_downgraded);
|
|
157
|
-
if (partial.enforcement_mode !== undefined) row.enforcement_mode = String(partial.enforcement_mode);
|
|
158
|
-
if (partial.lazy_skipped !== undefined) row.lazy_skipped = Boolean(partial.lazy_skipped);
|
|
159
|
-
if (partial.block_reason !== undefined) row.block_reason = String(partial.block_reason);
|
|
160
|
-
return row;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ---- telemetry writer: append one JSON row to costs.jsonl ----
|
|
164
|
-
function writeTelemetry(partial) {
|
|
165
|
-
const dir = path.dirname(TELEMETRY_PATH);
|
|
166
|
-
try {
|
|
167
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
168
|
-
const row = buildTelemetryRow(partial);
|
|
169
|
-
fs.appendFileSync(TELEMETRY_PATH, JSON.stringify(row) + '\n', 'utf8');
|
|
170
|
-
// Fire-and-forget aggregator — rebuilds .design/agent-metrics.json incrementally.
|
|
171
|
-
spawnAggregator();
|
|
172
|
-
} catch {
|
|
173
|
-
// Fail open: telemetry must never block the hook.
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Backward-compat alias (plan 01 called it appendTelemetry; keep it working).
|
|
178
|
-
const appendTelemetry = writeTelemetry;
|
|
179
|
-
|
|
180
|
-
// ---- main ----
|
|
181
|
-
async function main() {
|
|
182
|
-
const rl = readline.createInterface({ input: process.stdin });
|
|
183
|
-
let inputData = '';
|
|
184
|
-
for await (const line of rl) inputData += line + '\n';
|
|
185
|
-
|
|
186
|
-
let parsed;
|
|
187
|
-
try { parsed = JSON.parse(inputData); } catch { process.exit(0); }
|
|
188
|
-
|
|
189
|
-
if (parsed.tool_name !== 'Agent') process.exit(0); // only guard Agent spawns
|
|
190
|
-
|
|
191
|
-
const toolInput = parsed.tool_input || {};
|
|
192
|
-
const agent = toolInput.subagent_type || toolInput.agent || 'unknown';
|
|
193
|
-
const inputHash = toolInput._input_hash || null; // supplied by orchestrator if cache-manager pre-computed it
|
|
194
|
-
|
|
195
|
-
// Resolve cycle + phase once so every branch can stamp consistent values.
|
|
196
|
-
const { cycle, phase } = readCycleAndPhase();
|
|
197
|
-
const cyclePhase = { cycle, phase };
|
|
198
|
-
|
|
199
|
-
// Branch A: lazy-gate signal from plan 10.1-04 agents (design-verifier-gate, etc.).
|
|
200
|
-
// Gate agents set tool_input.lazy_skipped === true when the heuristic declines
|
|
201
|
-
// to spawn the full checker. We log a zero-cost row and pass through.
|
|
202
|
-
if (toolInput.lazy_skipped === true) {
|
|
203
|
-
writeTelemetry({
|
|
204
|
-
agent,
|
|
205
|
-
tier: 'gate',
|
|
206
|
-
tokens_in: 0,
|
|
207
|
-
tokens_out: 0,
|
|
208
|
-
cache_hit: false,
|
|
209
|
-
est_cost_usd: 0,
|
|
210
|
-
lazy_skipped: true,
|
|
211
|
-
_cyclePhase: cyclePhase,
|
|
212
|
-
});
|
|
213
|
-
const response = { continue: true, suppressOutput: true };
|
|
214
|
-
process.stdout.write(JSON.stringify(response));
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const budget = loadBudget();
|
|
219
|
-
|
|
220
|
-
// Branch B: cache short-circuit (D-05)
|
|
221
|
-
if (inputHash) {
|
|
222
|
-
const cached = cacheLookup(agent, inputHash);
|
|
223
|
-
if (cached !== null) {
|
|
224
|
-
writeTelemetry({
|
|
225
|
-
agent,
|
|
226
|
-
tier: 'cache',
|
|
227
|
-
tokens_in: 0,
|
|
228
|
-
tokens_out: 0,
|
|
229
|
-
cache_hit: true,
|
|
230
|
-
est_cost_usd: 0,
|
|
231
|
-
_cyclePhase: cyclePhase,
|
|
232
|
-
});
|
|
233
|
-
const response = {
|
|
234
|
-
continue: false, // block the real spawn; orchestrator reads suppressOutput.message for cached blob
|
|
235
|
-
suppressOutput: false,
|
|
236
|
-
message: `gdd-budget-enforcer: SkippedCached — returning cached result for ${agent}:${inputHash}`,
|
|
237
|
-
cached_result: cached,
|
|
238
|
-
};
|
|
239
|
-
process.stdout.write(JSON.stringify(response));
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Layer B: cap checks (D-02)
|
|
245
|
-
const estCost = Number(toolInput._est_cost_usd || 0);
|
|
246
|
-
const phaseSpend = currentPhaseSpend(phase);
|
|
247
|
-
|
|
248
|
-
if (budget.enforcement_mode === 'enforce') {
|
|
249
|
-
// Branch C: 100% per_task cap (hard block)
|
|
250
|
-
if (estCost >= budget.per_task_cap_usd) {
|
|
251
|
-
writeTelemetry({
|
|
252
|
-
agent,
|
|
253
|
-
tier: toolInput._tier_override || toolInput._default_tier || 'sonnet',
|
|
254
|
-
tokens_in: Number(toolInput._tokens_in_est || 0),
|
|
255
|
-
tokens_out: Number(toolInput._tokens_out_est || 0),
|
|
256
|
-
cache_hit: false,
|
|
257
|
-
est_cost_usd: estCost,
|
|
258
|
-
enforcement_mode: budget.enforcement_mode,
|
|
259
|
-
block_reason: 'per_task_cap',
|
|
260
|
-
_cyclePhase: cyclePhase,
|
|
261
|
-
});
|
|
262
|
-
const response = {
|
|
263
|
-
continue: false,
|
|
264
|
-
suppressOutput: false,
|
|
265
|
-
message: `Budget cap reached for per-task. Estimated: $${estCost.toFixed(4)}, cap: $${budget.per_task_cap_usd.toFixed(2)}. Raise cap in .design/budget.json or retry after next task.`,
|
|
266
|
-
};
|
|
267
|
-
process.stdout.write(JSON.stringify(response));
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
// Branch D: 100% per_phase cap (hard block)
|
|
271
|
-
if (phaseSpend + estCost >= budget.per_phase_cap_usd) {
|
|
272
|
-
writeTelemetry({
|
|
273
|
-
agent,
|
|
274
|
-
tier: toolInput._tier_override || toolInput._default_tier || 'sonnet',
|
|
275
|
-
tokens_in: Number(toolInput._tokens_in_est || 0),
|
|
276
|
-
tokens_out: Number(toolInput._tokens_out_est || 0),
|
|
277
|
-
cache_hit: false,
|
|
278
|
-
est_cost_usd: estCost,
|
|
279
|
-
enforcement_mode: budget.enforcement_mode,
|
|
280
|
-
block_reason: 'per_phase_cap',
|
|
281
|
-
_cyclePhase: cyclePhase,
|
|
282
|
-
});
|
|
283
|
-
const response = {
|
|
284
|
-
continue: false,
|
|
285
|
-
suppressOutput: false,
|
|
286
|
-
message: `Budget cap reached for per-phase (${phase}). Cumulative: $${(phaseSpend + estCost).toFixed(4)}, cap: $${budget.per_phase_cap_usd.toFixed(2)}. Raise cap in .design/budget.json or retry after next phase.`,
|
|
287
|
-
};
|
|
288
|
-
process.stdout.write(JSON.stringify(response));
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
// 80% soft-threshold downgrade (D-03): task-scoped, per reference/model-tiers.md
|
|
292
|
-
if (budget.auto_downgrade_on_cap && estCost >= (0.80 * budget.per_task_cap_usd)) {
|
|
293
|
-
toolInput._tier_override = 'haiku';
|
|
294
|
-
toolInput._tier_downgraded = true;
|
|
295
|
-
}
|
|
296
|
-
} else if (budget.enforcement_mode === 'warn') {
|
|
297
|
-
if (estCost >= budget.per_task_cap_usd) {
|
|
298
|
-
process.stderr.write(`gdd-budget-enforcer WARN: per-task cap will be exceeded ($${estCost.toFixed(4)} >= $${budget.per_task_cap_usd})\n`);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
// enforcement_mode === 'log': no blocking, just telemetry
|
|
302
|
-
|
|
303
|
-
// D-04: tier_overrides rewrite
|
|
304
|
-
if (budget.tier_overrides[agent]) {
|
|
305
|
-
toolInput._tier_override = budget.tier_overrides[agent];
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Branch E: standard spawn-allowed (includes tier-downgraded path D-03)
|
|
309
|
-
writeTelemetry({
|
|
310
|
-
agent,
|
|
311
|
-
tier: toolInput._tier_override || toolInput._default_tier || 'sonnet',
|
|
312
|
-
tokens_in: Number(toolInput._tokens_in_est || 0),
|
|
313
|
-
tokens_out: Number(toolInput._tokens_out_est || 0),
|
|
314
|
-
cache_hit: false,
|
|
315
|
-
est_cost_usd: estCost,
|
|
316
|
-
tier_downgraded: !!toolInput._tier_downgraded,
|
|
317
|
-
enforcement_mode: budget.enforcement_mode,
|
|
318
|
-
_cyclePhase: cyclePhase,
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
const response = {
|
|
322
|
-
continue: true,
|
|
323
|
-
suppressOutput: true,
|
|
324
|
-
modified_tool_input: toolInput
|
|
325
|
-
};
|
|
326
|
-
process.stdout.write(JSON.stringify(response));
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
main().catch(err => { console.error('budget-enforcer hook error:', err); process.exit(0); });
|