@hegemonart/get-design-done 1.59.7 → 1.59.9

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 (55) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +59 -0
  4. package/README.md +2 -2
  5. package/SKILL.md +1 -1
  6. package/agents/design-authority-watcher.md +24 -5
  7. package/bin/gdd-graph +4 -1
  8. package/hooks/_hook-emit.js +113 -29
  9. package/hooks/budget-enforcer.ts +104 -5
  10. package/hooks/gdd-mcp-circuit-breaker.js +72 -3
  11. package/hooks/gdd-sessionstart-recap.js +23 -14
  12. package/hooks/hooks.json +2 -2
  13. package/package.json +2 -2
  14. package/reference/bandit-integration.md +13 -2
  15. package/reference/prices/claude.md +11 -0
  16. package/reference/runtime-models.md +9 -9
  17. package/reference/schemas/generated.d.ts +4 -0
  18. package/reference/schemas/runtime-models.schema.json +5 -0
  19. package/scripts/bootstrap.cjs +40 -8
  20. package/scripts/install.cjs +23 -1
  21. package/scripts/lib/bandit-router.cjs +47 -5
  22. package/scripts/lib/budget-enforcer.cjs +34 -5
  23. package/scripts/lib/detect/cli.cjs +13 -3
  24. package/scripts/lib/install/converters/cursor.cjs +11 -19
  25. package/scripts/lib/install/installer.cjs +72 -21
  26. package/scripts/lib/install/merge.cjs +31 -3
  27. package/scripts/lib/install/parse-runtime-models.cjs +9 -1
  28. package/scripts/lib/install/runtime-artifact-layout.cjs +42 -8
  29. package/scripts/lib/manifest/harnesses.json +29 -1
  30. package/scripts/lib/manifest/skills.json +1 -1
  31. package/scripts/lib/model-id.cjs +141 -0
  32. package/scripts/lib/session-runner/index.ts +87 -16
  33. package/scripts/skill-templates/bandit-reset/SKILL.md +2 -0
  34. package/scripts/skill-templates/bandit-status/SKILL.md +4 -1
  35. package/scripts/skill-templates/darkmode/SKILL.md +1 -1
  36. package/scripts/skill-templates/graphify/SKILL.md +6 -6
  37. package/scripts/skill-templates/quick/SKILL.md +3 -1
  38. package/scripts/skill-templates/reflect/SKILL.md +1 -1
  39. package/scripts/skill-templates/router/SKILL.md +4 -2
  40. package/sdk/cli/index.js +132 -55
  41. package/sdk/dashboard/data/source.cjs +50 -4
  42. package/sdk/event-stream/writer.ts +112 -30
  43. package/sdk/mcp/gdd-mcp/server.js +49 -36
  44. package/sdk/mcp/gdd-mcp/tools/shared.ts +20 -2
  45. package/sdk/mcp/gdd-state/server.js +107 -41
  46. package/sdk/primitives/lockfile.cjs +26 -5
  47. package/sdk/state/index.ts +91 -17
  48. package/sdk/state/lockfile.ts +47 -8
  49. package/skills/bandit-reset/SKILL.md +2 -0
  50. package/skills/bandit-status/SKILL.md +4 -1
  51. package/skills/darkmode/SKILL.md +1 -1
  52. package/skills/graphify/SKILL.md +6 -6
  53. package/skills/quick/SKILL.md +3 -1
  54. package/skills/reflect/SKILL.md +1 -1
  55. package/skills/router/SKILL.md +4 -2
@@ -122,6 +122,21 @@ const adaptiveModeLib = _nodeRequire(
122
122
  getMode: (opts?: { baseDir?: string; budgetPath?: string; quiet?: boolean }) => 'static' | 'hedge' | 'full';
123
123
  };
124
124
 
125
+ // ── Phase 59-9 — model-id normalization + tiering (single source of truth) ───
126
+ //
127
+ // `scripts/lib/model-id.cjs` is the canonical id parser shared with the
128
+ // budget-enforcer. We route BOTH tier-labeling (`tierFromModel`) and pricing
129
+ // (`rateFor`) through it so a new model family is a DATA edit there / in the
130
+ // price tables, never scattered substring logic here. `tierForModelId` returns
131
+ // `null` for an unknown family — callers MUST treat that as "price
132
+ // conservatively + loudly", never as a tier or as free.
133
+ const modelId = _nodeRequire(
134
+ _resolve(_REPO_ROOT, 'scripts/lib/model-id.cjs'),
135
+ ) as {
136
+ normalizeModelId: (id: string | null | undefined) => { base: string; variant: string | null };
137
+ tierForModelId: (id: string | null | undefined) => 'opus' | 'sonnet' | 'haiku' | null;
138
+ };
139
+
125
140
  /** Rate-guard provider key for the Anthropic Agent SDK. */
126
141
  const RATE_GUARD_PROVIDER = 'anthropic';
127
142
 
@@ -144,16 +159,24 @@ const SESSION_RUNNER_DEFAULT_BIN = 'medium';
144
159
  *
145
160
  * Used at the 4 terminal-emit sites where the final tier isn't already
146
161
  * carried on `opts` — we fall back to inspecting `usage.model` (folded
147
- * during the run loop from SDK chunks). Unknown / empty model names
148
- * default to 'sonnet' (matches the DEFAULT_MODEL_RATE choice and is
149
- * the safest middle tier for posterior arms).
162
+ * during the run loop from SDK chunks). Delegates to the shared
163
+ * `model-id.cjs` resolver (variant suffix like `[1m]` is stripped, known
164
+ * ids classified identically to before).
165
+ *
166
+ * The shared resolver returns `null` for an UNKNOWN family. For tier
167
+ * LABELING (telemetry / posterior arms) we map null → 'sonnet' as the
168
+ * safest middle tier so the bandit arms stay well-defined. This is a
169
+ * TELEMETRY default only — it does NOT influence PRICING. Pricing of an
170
+ * unknown family uses the conservative OPUS ceiling, resolved separately in
171
+ * `rateFor` (see DEFAULT_MODEL_RATE / tier fallback there). Keep the two
172
+ * concerns distinct: a wrong tier label mis-attributes a posterior arm; a
173
+ * wrong price under-bills a frontier model.
150
174
  */
151
175
  function tierFromModel(modelName: string | null | undefined): 'opus' | 'sonnet' | 'haiku' {
152
- if (typeof modelName !== 'string' || modelName.length === 0) return 'sonnet';
153
- const lower = modelName.toLowerCase();
154
- if (lower.includes('opus')) return 'opus';
155
- if (lower.includes('haiku')) return 'haiku';
156
- return 'sonnet';
176
+ const tier = modelId.tierForModelId(modelName);
177
+ // null = unknown family → conservative TELEMETRY default (pricing handled
178
+ // separately + conservatively in rateFor).
179
+ return tier ?? 'sonnet';
157
180
  }
158
181
 
159
182
  /**
@@ -539,29 +562,72 @@ function _logPeerCallComplete(args: {
539
562
  const RETRY_BACKOFF = { baseMs: 1000, maxMs: 30_000 } as const;
540
563
 
541
564
  /**
542
- * Per-million-token USD rates. Unknown models default to the Sonnet
543
- * rate (safer overestimate — we'd rather cap early than under-bill).
565
+ * Per-million-token USD rates.
566
+ *
567
+ * Canonical price source is `reference/prices/claude.md`; this table mirrors
568
+ * it for the sync headless path — keep in lockstep.
569
+ *
570
+ * Unknown FAMILIES default to the OPUS ceiling (see DEFAULT_MODEL_RATE) — a
571
+ * conservative overestimate. We'd rather cap early than silently under-bill a
572
+ * frontier model. Known families fall back to their per-tier representative
573
+ * rate (PER_TIER_RATE) so a dated/variant sku still prices correctly.
544
574
  */
545
575
  const MODEL_RATES: Readonly<Record<string, { input: number; output: number }>> = Object.freeze({
576
+ 'claude-opus-4-8': { input: 15, output: 75 },
546
577
  'claude-opus-4-7': { input: 15, output: 75 },
547
578
  'claude-sonnet-4-5': { input: 3, output: 15 },
548
579
  'claude-haiku-4-5': { input: 0.8, output: 4 },
549
580
  });
550
- const DEFAULT_MODEL_RATE = Object.freeze({ input: 3, output: 15 });
551
581
 
552
- /** Resolve a per-M-token rate for a model name, matching prefix when possible. */
582
+ /** Per-tier representative rates (match reference/prices/claude.md). Used as
583
+ * the fallback when an exact/prefix MODEL_RATES match is absent but the
584
+ * family tier is known. */
585
+ const PER_TIER_RATE: Readonly<Record<'opus' | 'sonnet' | 'haiku', { input: number; output: number }>> =
586
+ Object.freeze({
587
+ opus: { input: 15, output: 75 },
588
+ sonnet: { input: 3, output: 15 },
589
+ haiku: { input: 1, output: 5 },
590
+ });
591
+
592
+ /**
593
+ * DEFAULT_MODEL_RATE — conservative ceiling for a GENUINELY UNKNOWN family
594
+ * (tier resolves to null). Set to the OPUS rate, matching this file's own
595
+ * "safer overestimate" intent. The previous sonnet default UNDER-billed any
596
+ * frontier model whose id we did not yet recognize.
597
+ */
598
+ const DEFAULT_MODEL_RATE = Object.freeze({ input: 15, output: 75 });
599
+
600
+ /**
601
+ * Resolve a per-M-token rate for a model name.
602
+ *
603
+ * Resolution order (conservative + robust):
604
+ * 1. normalize the id (strip `[1m]`/`[200k]` variant suffix) → work on base;
605
+ * 2. exact match in MODEL_RATES;
606
+ * 3. prefix match (e.g. "claude-opus-4-7-20250101" → "claude-opus-4-7");
607
+ * 4. per-tier fallback via `tierForModelId(base)` (opus/sonnet/haiku → that
608
+ * tier's representative rate) — keeps dated/variant skus of a known
609
+ * family priced correctly;
610
+ * 5. ONLY if the tier is null (genuinely unknown family) → DEFAULT_MODEL_RATE
611
+ * (opus ceiling — price LOUDLY + CONSERVATIVELY, never $0 or sonnet).
612
+ */
553
613
  function rateFor(modelName: string | null): { input: number; output: number } {
554
614
  if (modelName === null || modelName === '') return DEFAULT_MODEL_RATE;
555
- // Direct match first.
556
- const direct = MODEL_RATES[modelName];
615
+ const { base } = modelId.normalizeModelId(modelName);
616
+ if (base === '') return DEFAULT_MODEL_RATE;
617
+ // (2) Direct match first.
618
+ const direct = MODEL_RATES[base];
557
619
  if (direct !== undefined) return direct;
558
- // Prefix match (e.g. "claude-opus-4-7-20250101" → "claude-opus-4-7").
620
+ // (3) Prefix match.
559
621
  for (const key of Object.keys(MODEL_RATES)) {
560
- if (modelName.startsWith(key)) {
622
+ if (base.startsWith(key)) {
561
623
  const hit = MODEL_RATES[key];
562
624
  if (hit !== undefined) return hit;
563
625
  }
564
626
  }
627
+ // (4) Per-tier fallback for a known family.
628
+ const tier = modelId.tierForModelId(base);
629
+ if (tier !== null) return PER_TIER_RATE[tier];
630
+ // (5) Unknown family → conservative opus ceiling.
565
631
  return DEFAULT_MODEL_RATE;
566
632
  }
567
633
 
@@ -1281,3 +1347,8 @@ function buildResult(args: BuildResultArgs): SessionResult {
1281
1347
  // invariant: session-runner consumers can rely on these constants being
1282
1348
  // stable across minor releases.
1283
1349
  export { MODEL_RATES, DEFAULT_MODEL_RATE, RATE_GUARD_PROVIDER };
1350
+
1351
+ // Pricing internals exported for regression tests (Phase 59-9 model-cost-truth):
1352
+ // verify unknown families resolve to the conservative opus ceiling while known
1353
+ // families price correctly via the per-tier fallback.
1354
+ export { rateFor, usdCost, tierFromModel, PER_TIER_RATE };
@@ -31,6 +31,8 @@ No posterior file found at `.design/telemetry/posterior.json` — nothing to res
31
31
  The next bandit pull with `adaptive_mode: full` will bootstrap a fresh posterior from informed priors. See `reference/bandit-integration.md`.
32
32
  ```
33
33
 
34
+ > Note: the posterior only learns (updates from outcomes) on the SDK / headless `session-runner` path. In interactive Claude Code with `adaptive_mode: full`, the bandit samples from the configured priors but does not currently update them in-session. A reset therefore re-bootstraps the priors the SDK path will subsequently learn from. See `reference/bandit-integration.md` ("Where adaptive routing actually learns").
35
+
34
36
  If present, count the arms (`arms.length`, treating a missing/non-array `arms` as `0`) so the confirmation and receipt can report what will be cleared. A corrupted/unparseable file is still resettable - report `arms: unknown (file unparseable)` and continue.
35
37
 
36
38
  ### 2. Require explicit confirmation
@@ -33,10 +33,13 @@ Possible reasons:
33
33
  - `adaptive_mode` is `static` or `hedge` (bandit silent — see `.design/budget.json`).
34
34
  - No spawns have fired since Phase 27.5 wiring landed.
35
35
  - Posterior was cleared via `{{command_prefix}}bandit-reset`.
36
+ - You are running in interactive Claude Code: the posterior is updated (learns) only on the SDK / headless `session-runner` path. In interactive `adaptive_mode: full` the bandit samples from configured priors but does not learn from in-session outcomes.
36
37
 
37
- See `reference/bandit-integration.md` for setup guidance.
38
+ See `reference/bandit-integration.md` ("Where adaptive routing actually learns") for setup guidance.
38
39
  ```
39
40
 
41
+ > Note: the posterior only moves (learns) on the SDK / headless `session-runner` path. In interactive Claude Code with `adaptive_mode: full`, the bandit samples from the configured priors but does not currently update them in-session. See `reference/bandit-integration.md`.
42
+
40
43
  Skip to Section 4 (Record). Parse failure (truncated/corrupted) → emit `Posterior file exists but is unparseable. Run {{command_prefix}}bandit-reset to start fresh, or restore from a backup.`
41
44
 
42
45
  ### 2. Parse the posterior
@@ -29,7 +29,7 @@ Output artifact prefix `DARKMODE-AUDIT` is distinct from the pipeline namespace
29
29
 
30
30
  ## Pre-Flight
31
31
 
32
- Confirm source root exists. Try in order: `src/` (preferred), `app/` (Next.js App Router), `lib/` (libraries), `pages/` (Next.js Pages Router). Set `SRC_ROOT` to the first that exists. If none exist, abort: `"No source directory detected. Run /get-design-done scan first."`
32
+ Confirm source root exists. Try in order: `src/` (preferred), `app/` (Next.js App Router), `lib/` (libraries), `pages/` (Next.js Pages Router). Set `SRC_ROOT` to the first that exists. If none exist, abort: `"No source directory detected. Run /get-design-done explore first."`
33
33
 
34
34
  Confirm `.design/` exists (create if absent: `mkdir -p .design/`).
35
35
 
@@ -5,7 +5,7 @@ description: "Manage the Graphify knowledge graph for the current project. Build
5
5
 
6
6
  # gdd-graphify
7
7
 
8
- Thin command wrapper around the GSD graphify tools integration.
8
+ Thin command wrapper around the get-design-done (GDD) graphify tools integration.
9
9
 
10
10
  ## Usage
11
11
 
@@ -30,10 +30,10 @@ Thin command wrapper around the GSD graphify tools integration.
30
30
  ```
31
31
  STOP.
32
32
  4. Execute the requested subcommand via the native CLI:
33
- - build: `node bin/gdd-graph build`
34
- - query: `node bin/gdd-graph query "<term>" --budget 2000`
35
- - status: `node bin/gdd-graph status`
36
- - diff: `node bin/gdd-graph diff`
33
+ - build: `node "${CLAUDE_PLUGIN_ROOT}/bin/gdd-graph" build`
34
+ - query: `node "${CLAUDE_PLUGIN_ROOT}/bin/gdd-graph" query "<term>" --budget 2000`
35
+ - status: `node "${CLAUDE_PLUGIN_ROOT}/bin/gdd-graph" status`
36
+ - diff: `node "${CLAUDE_PLUGIN_ROOT}/bin/gdd-graph" diff`
37
37
  5. After `build` completes, update `.design/STATE.md` `<connections>`: `graphify: available`
38
38
 
39
39
  ## Required Reading
@@ -43,7 +43,7 @@ Thin command wrapper around the GSD graphify tools integration.
43
43
 
44
44
  ## Notes
45
45
 
46
- - Graphify is optional. The native CLI ships in this repo at `bin/gdd-graph` (no external install - Node only).
46
+ - Graphify is optional. The native CLI ships with the plugin at `${CLAUDE_PLUGIN_ROOT}/bin/gdd-graph` (no external install - Node only).
47
47
  - Graph is stored at `.design/graph/graph.json` (Ajv-validated against `scripts/lib/graph/schema.json`).
48
48
  - Graph covers source code (`src/`, `components/`). It does NOT index `.design/` artifacts by default.
49
49
  - Use `query` with node IDs from the graph schema: `component:<name>`, `token:color/<name>`, `decision:D-<nn>`, etc.
@@ -26,10 +26,12 @@ Fast pipeline run. Skips optional-quality agents for speed while keeping the cor
26
26
  - Optional stage name (defaults to full pipeline from the current STATE.md position).
27
27
  - `--skip <agent-name>` (repeatable) adds to the skip list.
28
28
  2. Read `.design/STATE.md` to determine entry stage if none was passed.
29
- 3. For each stage to execute, spawn the stage skill with a `quick_mode: true` flag and the effective skip list in the spawn context. Stage skills read this flag and route around the listed agents.
29
+ 3. For each stage to execute, invoke the stage skill but spawn it with the optional agents in the effective skip list **omitted from the spawn graph** - this skill is the orchestrator, so it simply does not call those agents (the stage skills do not read a `quick_mode` flag; the skipping happens here, by not spawning them). The kept agents run exactly as in the full pipeline.
30
30
  4. After each stage, print: "Stage <name> done. Skipped: <list>."
31
31
  5. Final summary prints which agents were skipped across the full run.
32
32
 
33
+ Mechanism note: `{{command_prefix}}quick` is a lighter-touch *invocation* of the normal stages, not a special stage mode. It reduces ceremony by leaving the listed optional-quality agents out of the spawn graph it orchestrates. There is no flag the stage skills parse - if invoked directly (not via this skill) the stages run their full agent set.
34
+
33
35
  ## Use When
34
36
 
35
37
  - You trust the problem scope (no need for fresh research).
@@ -37,7 +37,7 @@ Run `design-reflector` on demand against the current (or specified) cycle. Produ
37
37
  See @skills/reflect/procedures/capability-gap-scan.md for the full procedure.
38
38
  The `design-reflector` agent runs the scan automatically as part of its reflection pass; this step lets users dry-run it independently with:
39
39
  ```
40
- node scripts/lib/reflector/capability-gap-scan.cjs --dry-run
40
+ node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/reflector/capability-gap-scan.cjs" --dry-run
41
41
  ```
42
42
  The scan emits `capability_gap` events (`source: "reflector_pattern"`) for recurring patterns lacking a dedicated executable owner; Plan 29-03 aggregates these for `{{command_prefix}}apply-reflections`.
43
43
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: gdd-router
3
- description: "Routes a /gdd command to fast|quick|full path + S|M|L|XL complexity_class and returns {path, complexity_class, model_tier_overrides, resolved_models, estimated_cost_usd, cache_hits}. Deterministic - no model call. Invoked once at command entry before any Agent spawn. Read by hooks/budget-enforcer.ts."
3
+ description: "Routes a /gdd command to fast|quick|full path + S|M|L|XL complexity_class and returns {path, complexity_class, model_tier_overrides, resolved_models, estimated_cost_usd, cache_hits}. A SKILL.md prompt the model executes to emit a routing-decision JSON from rule tables (no separate agent spawn). Optional/advisory - invoked only by the skills that opt into routing; the budget-enforcer hook tolerates its absence. Read by hooks/budget-enforcer.ts."
4
4
  argument-hint: "<intent-string> [<target-artifacts-csv>]"
5
5
  tools: Read, Bash, Grep
6
6
  ---
@@ -69,7 +69,9 @@ Delegate to `skills/cache-manager/SKILL.md` (Plan 10.1-02). The router lists can
69
69
 
70
70
  ## Integration Point
71
71
 
72
- Every `{{command_prefix}}*` SKILL.md's first substantive step is: spawn the router via `Task` or inline invocation; receive the JSON blob; pass it to downstream agents as context so the budget-enforcer hook has the router decision available in tool_input metadata when the first Agent spawn fires.
72
+ The router is **optional and advisory**, not a universal first step. Only the handful of skills that explicitly opt into routing reference it (today: the root pipeline `SKILL.md` / `{{command_prefix}}handoff`, and `{{command_prefix}}style` documents that it deliberately does *not* invoke the router because it is a leaf invocation). The pipeline stage skills (explore / plan / design / verify) do **not** spawn the router. When a skill does invoke it, the flow is: invoke the router via `Task` or inline invocation; receive the JSON blob; pass it to downstream agents as context so the budget-enforcer hook has the router decision available in tool_input metadata when the first Agent spawn fires.
73
+
74
+ When no skill supplies a router decision, the budget-enforcer hook reads `tool_input.context.router_decision` as absent and falls back to its legacy back-compat path - the router's absence is tolerated by design, never an error.
73
75
 
74
76
  ## Failure Modes
75
77
 
package/sdk/cli/index.js CHANGED
@@ -82,44 +82,69 @@ var init_emitter = __esm({
82
82
 
83
83
  // sdk/event-stream/writer.ts
84
84
  function _findRepoRoot() {
85
- let dir = process.cwd();
86
- for (let i = 0; i < 8; i++) {
85
+ return _walkToPackageJson(process.cwd());
86
+ }
87
+ function _walkToPackageJson(startDir) {
88
+ let dir = startDir;
89
+ for (let i = 0; i < 12; i++) {
87
90
  if ((0, import_node_fs.existsSync)((0, import_node_path.join)(dir, "package.json"))) return dir;
88
91
  const parent = (0, import_node_path.dirname)(dir);
89
92
  if (parent === dir) break;
90
93
  dir = parent;
91
94
  }
92
- return process.cwd();
95
+ return startDir;
93
96
  }
94
- var import_node_fs, import_node_path, import_node_module, _redact, redact, DEFAULT_EVENTS_PATH, DEFAULT_MAX_LINE_BYTES, EventWriter;
97
+ function _warnRedactUnavailable() {
98
+ if (_redactWarned) return;
99
+ _redactWarned = true;
100
+ try {
101
+ process.stderr.write(
102
+ "[event-stream] WARNING: scripts/lib/redact.cjs could not be loaded \u2014 failing CLOSED: event payloads are dropped (envelope-only) to avoid writing unscrubbed secrets. Run the event writer from inside the plugin tree or set the redact lib on PATH to restore full payloads.\n"
103
+ );
104
+ } catch {
105
+ }
106
+ }
107
+ function _loadRedact() {
108
+ const candidates = [];
109
+ const entry = process.argv[1];
110
+ if (typeof entry === "string" && entry.length > 0) {
111
+ const entryAbs = (0, import_node_path.isAbsolute)(entry) ? entry : (0, import_node_path.resolve)(entry);
112
+ const entryRoot = _walkToPackageJson((0, import_node_path.dirname)(entryAbs));
113
+ candidates.push((0, import_node_path.resolve)(entryRoot, "scripts/lib/redact.cjs"));
114
+ }
115
+ const repoRoot = _findRepoRoot();
116
+ candidates.push((0, import_node_path.resolve)(repoRoot, "scripts/lib/redact.cjs"));
117
+ candidates.push((0, import_node_path.resolve)(repoRoot, "..", "..", "scripts/lib/redact.cjs"));
118
+ for (const candidate of candidates) {
119
+ try {
120
+ if (!(0, import_node_fs.existsSync)(candidate)) continue;
121
+ const req = (0, import_node_module.createRequire)(candidate);
122
+ const mod = req(candidate);
123
+ if (mod && typeof mod.redact === "function") return mod.redact;
124
+ } catch {
125
+ }
126
+ }
127
+ return null;
128
+ }
129
+ var import_node_fs, import_node_path, import_node_module, _redactWarned, _realRedact, redact, DEFAULT_EVENTS_PATH, DEFAULT_MAX_LINE_BYTES, EventWriter;
95
130
  var init_writer = __esm({
96
131
  "sdk/event-stream/writer.ts"() {
97
132
  "use strict";
98
133
  import_node_fs = require("node:fs");
99
134
  import_node_path = require("node:path");
100
135
  import_node_module = require("node:module");
101
- try {
102
- const _root = _findRepoRoot();
103
- const _candidate = (0, import_node_path.resolve)(_root, "scripts/lib/redact.cjs");
104
- if ((0, import_node_fs.existsSync)(_candidate)) {
105
- const _redactRequire = (0, import_node_module.createRequire)((0, import_node_path.join)(_root, "package.json"));
106
- const _mod = _redactRequire(_candidate);
107
- _redact = _mod.redact;
108
- } else {
109
- const _altRoot = (0, import_node_path.resolve)(_root, "..", "..");
110
- const _altCandidate = (0, import_node_path.resolve)(_altRoot, "scripts/lib/redact.cjs");
111
- if ((0, import_node_fs.existsSync)(_altCandidate)) {
112
- const _altRequire = (0, import_node_module.createRequire)((0, import_node_path.join)(_altRoot, "package.json"));
113
- const _altMod = _altRequire(_altCandidate);
114
- _redact = _altMod.redact;
115
- } else {
116
- _redact = (v) => v;
117
- }
136
+ _redactWarned = false;
137
+ _realRedact = _loadRedact();
138
+ redact = _realRedact !== null ? _realRedact : (v) => {
139
+ _warnRedactUnavailable();
140
+ if (v !== null && typeof v === "object") {
141
+ const ev = v;
142
+ const out = { ...ev };
143
+ out["payload"] = { _redaction_unavailable: true };
144
+ return out;
118
145
  }
119
- } catch {
120
- _redact = (v) => v;
121
- }
122
- redact = _redact;
146
+ return { _redaction_unavailable: true };
147
+ };
123
148
  DEFAULT_EVENTS_PATH = ".design/telemetry/events.jsonl";
124
149
  DEFAULT_MAX_LINE_BYTES = 64 * 1024;
125
150
  EventWriter = class {
@@ -2435,6 +2460,7 @@ function getLogger() {
2435
2460
  // sdk/state/index.ts
2436
2461
  var import_node_fs5 = require("node:fs");
2437
2462
  var import_node_path4 = require("node:path");
2463
+ var import_node_module2 = require("node:module");
2438
2464
 
2439
2465
  // sdk/state/lockfile.ts
2440
2466
  var import_node_fs4 = require("node:fs");
@@ -2501,6 +2527,14 @@ async function acquire(path, opts = {}) {
2501
2527
  }
2502
2528
  const parsed = parseLock(existing);
2503
2529
  if (parsed !== null && isStale(parsed, staleMs)) {
2530
+ const confirm = readLockSafe(lockPath);
2531
+ if (confirm === null) {
2532
+ continue;
2533
+ }
2534
+ if (confirm !== existing) {
2535
+ await sleep(pollMs);
2536
+ continue;
2537
+ }
2504
2538
  try {
2505
2539
  (0, import_node_fs4.unlinkSync)(lockPath);
2506
2540
  } catch (delErr) {
@@ -2559,10 +2593,14 @@ function parseLock(raw) {
2559
2593
  }
2560
2594
  }
2561
2595
  function isStale(payload, staleMs) {
2562
- if (!isPidAlive(payload.pid, payload.host)) return true;
2563
- const acquiredAt = Date.parse(payload.acquired_at);
2564
- if (!Number.isFinite(acquiredAt)) return true;
2565
- return Date.now() - acquiredAt > staleMs;
2596
+ const pidRecorded = typeof payload.pid === "number" && Number.isInteger(payload.pid) && payload.pid > 0;
2597
+ if (!pidRecorded) {
2598
+ const acquiredAt = Date.parse(payload.acquired_at);
2599
+ if (!Number.isFinite(acquiredAt)) return true;
2600
+ return Date.now() - acquiredAt > staleMs;
2601
+ }
2602
+ if (isPidAlive(payload.pid, payload.host)) return false;
2603
+ return true;
2566
2604
  }
2567
2605
  function isPidAlive(pid, host) {
2568
2606
  if (host !== (0, import_node_os2.hostname)()) {
@@ -3969,6 +4007,8 @@ function gateFor(from, to) {
3969
4007
  }
3970
4008
 
3971
4009
  // sdk/state/index.ts
4010
+ var _moduleDir = typeof __dirname !== "undefined" ? __dirname : (0, import_node_path4.dirname)(process.argv[1] || process.cwd());
4011
+ var _require = typeof require !== "undefined" ? require : (0, import_node_module2.createRequire)(process.argv[1] || process.cwd());
3972
4012
  function _findPackageRoot(startDir) {
3973
4013
  let dir = (0, import_node_path4.resolve)(startDir);
3974
4014
  let firstWithPkg = null;
@@ -3976,7 +4016,7 @@ function _findPackageRoot(startDir) {
3976
4016
  const pkgPath = (0, import_node_path4.join)(dir, "package.json");
3977
4017
  if ((0, import_node_fs5.existsSync)(pkgPath)) {
3978
4018
  try {
3979
- const pkg = require(pkgPath);
4019
+ const pkg = _require(pkgPath);
3980
4020
  if (firstWithPkg === null) firstWithPkg = dir;
3981
4021
  if (pkg.name === "@hegemonart/get-design-done") return dir;
3982
4022
  } catch {
@@ -3993,7 +4033,7 @@ var _backendCache = null;
3993
4033
  function _loadBackend() {
3994
4034
  if (_backendCache !== null) return _backendCache === false ? null : _backendCache;
3995
4035
  try {
3996
- const pkgRoot = _findPackageRoot(__dirname);
4036
+ const pkgRoot = _findPackageRoot(_moduleDir);
3997
4037
  if (pkgRoot === null) {
3998
4038
  _backendCache = false;
3999
4039
  return null;
@@ -4003,7 +4043,7 @@ function _loadBackend() {
4003
4043
  _backendCache = false;
4004
4044
  return null;
4005
4045
  }
4006
- _backendCache = require(backendPath);
4046
+ _backendCache = _require(backendPath);
4007
4047
  return _backendCache;
4008
4048
  } catch {
4009
4049
  _backendCache = false;
@@ -4128,14 +4168,41 @@ async function transition(path, toStage) {
4128
4168
  throw new TransitionGateFailed(toStage, gateResult.blockers);
4129
4169
  }
4130
4170
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
4131
- const nextState = await mutate(path, (s) => {
4132
- s.frontmatter.stage = toStage;
4133
- s.frontmatter.last_checkpoint = nowIso;
4134
- s.position.stage = toStage;
4135
- s.timestamps[`${toStage}_started_at`] = nowIso;
4136
- return s;
4137
- });
4138
- return { pass: true, blockers: gateResult.blockers, state: nextState };
4171
+ let lockedFailure = null;
4172
+ let lockedBlockers = gateResult.blockers;
4173
+ try {
4174
+ const nextState = await mutate(path, (s) => {
4175
+ const fromNow = s.position.stage;
4176
+ if (!isStage(fromNow)) {
4177
+ lockedFailure = new TransitionGateFailed(toStage, [
4178
+ `Invalid transition: from="${fromNow}" is not a recognized Stage (changed under lock)`
4179
+ ]);
4180
+ throw lockedFailure;
4181
+ }
4182
+ const gateNow = gateFor(fromNow, toStage);
4183
+ if (gateNow === null) {
4184
+ lockedFailure = new TransitionGateFailed(toStage, [
4185
+ `Invalid transition: ${fromNow} \u2192 ${toStage} (changed under lock)`
4186
+ ]);
4187
+ throw lockedFailure;
4188
+ }
4189
+ const resultNow = gateNow(s);
4190
+ if (!resultNow.pass) {
4191
+ lockedFailure = new TransitionGateFailed(toStage, resultNow.blockers);
4192
+ throw lockedFailure;
4193
+ }
4194
+ lockedBlockers = resultNow.blockers;
4195
+ s.frontmatter.stage = toStage;
4196
+ s.frontmatter.last_checkpoint = nowIso;
4197
+ s.position.stage = toStage;
4198
+ s.timestamps[`${toStage}_started_at`] = nowIso;
4199
+ return s;
4200
+ });
4201
+ return { pass: true, blockers: lockedBlockers, state: nextState };
4202
+ } catch (err) {
4203
+ if (lockedFailure !== null && err === lockedFailure) throw lockedFailure;
4204
+ throw err;
4205
+ }
4139
4206
  }
4140
4207
 
4141
4208
  // scripts/lib/pipeline-runner/state-machine.ts
@@ -4982,7 +5049,7 @@ function collapseBlankLines(text) {
4982
5049
  }
4983
5050
 
4984
5051
  // scripts/lib/session-runner/errors.ts
4985
- var import_node_module2 = require("node:module");
5052
+ var import_node_module3 = require("node:module");
4986
5053
  var import_node_fs8 = require("node:fs");
4987
5054
  var import_node_path7 = require("node:path");
4988
5055
  function findRepoRoot() {
@@ -4996,7 +5063,7 @@ function findRepoRoot() {
4996
5063
  return process.cwd();
4997
5064
  }
4998
5065
  var REPO_ROOT = findRepoRoot();
4999
- var nodeRequire = (0, import_node_module2.createRequire)((0, import_node_path7.join)(REPO_ROOT, "package.json"));
5066
+ var nodeRequire = (0, import_node_module3.createRequire)((0, import_node_path7.join)(REPO_ROOT, "package.json"));
5000
5067
  var transportClassifier = nodeRequire(
5001
5068
  (0, import_node_path7.resolve)(REPO_ROOT, "sdk/primitives/error-classifier.cjs")
5002
5069
  );
@@ -5348,7 +5415,7 @@ var TranscriptWriter = class {
5348
5415
  };
5349
5416
 
5350
5417
  // scripts/lib/session-runner/index.ts
5351
- var import_node_module3 = require("node:module");
5418
+ var import_node_module4 = require("node:module");
5352
5419
  var import_node_fs10 = require("node:fs");
5353
5420
  var import_node_path9 = require("node:path");
5354
5421
  function _findRepoRoot2() {
@@ -5362,7 +5429,7 @@ function _findRepoRoot2() {
5362
5429
  return process.cwd();
5363
5430
  }
5364
5431
  var _REPO_ROOT = _findRepoRoot2();
5365
- var _nodeRequire = (0, import_node_module3.createRequire)((0, import_node_path9.join)(_REPO_ROOT, "package.json"));
5432
+ var _nodeRequire = (0, import_node_module4.createRequire)((0, import_node_path9.join)(_REPO_ROOT, "package.json"));
5366
5433
  var jitteredBackoff = _nodeRequire(
5367
5434
  (0, import_node_path9.resolve)(_REPO_ROOT, "sdk/primitives/jittered-backoff.cjs")
5368
5435
  );
@@ -5375,15 +5442,15 @@ var banditIntegration = _nodeRequire(
5375
5442
  var adaptiveModeLib = _nodeRequire(
5376
5443
  (0, import_node_path9.resolve)(_REPO_ROOT, "scripts/lib/adaptive-mode.cjs")
5377
5444
  );
5445
+ var modelId = _nodeRequire(
5446
+ (0, import_node_path9.resolve)(_REPO_ROOT, "scripts/lib/model-id.cjs")
5447
+ );
5378
5448
  var RATE_GUARD_PROVIDER = "anthropic";
5379
5449
  var DEFAULT_MAX_RETRIES = 2;
5380
5450
  var SESSION_RUNNER_DEFAULT_BIN = "medium";
5381
5451
  function tierFromModel(modelName) {
5382
- if (typeof modelName !== "string" || modelName.length === 0) return "sonnet";
5383
- const lower = modelName.toLowerCase();
5384
- if (lower.includes("opus")) return "opus";
5385
- if (lower.includes("haiku")) return "haiku";
5386
- return "sonnet";
5452
+ const tier = modelId.tierForModelId(modelName);
5453
+ return tier ?? "sonnet";
5387
5454
  }
5388
5455
  function _recordBanditOutcome(input) {
5389
5456
  try {
@@ -5592,21 +5659,31 @@ function _logPeerCallComplete(args) {
5592
5659
  }
5593
5660
  var RETRY_BACKOFF = { baseMs: 1e3, maxMs: 3e4 };
5594
5661
  var MODEL_RATES = Object.freeze({
5662
+ "claude-opus-4-8": { input: 15, output: 75 },
5595
5663
  "claude-opus-4-7": { input: 15, output: 75 },
5596
5664
  "claude-sonnet-4-5": { input: 3, output: 15 },
5597
5665
  "claude-haiku-4-5": { input: 0.8, output: 4 }
5598
5666
  });
5599
- var DEFAULT_MODEL_RATE = Object.freeze({ input: 3, output: 15 });
5667
+ var PER_TIER_RATE = Object.freeze({
5668
+ opus: { input: 15, output: 75 },
5669
+ sonnet: { input: 3, output: 15 },
5670
+ haiku: { input: 1, output: 5 }
5671
+ });
5672
+ var DEFAULT_MODEL_RATE = Object.freeze({ input: 15, output: 75 });
5600
5673
  function rateFor(modelName) {
5601
5674
  if (modelName === null || modelName === "") return DEFAULT_MODEL_RATE;
5602
- const direct = MODEL_RATES[modelName];
5675
+ const { base } = modelId.normalizeModelId(modelName);
5676
+ if (base === "") return DEFAULT_MODEL_RATE;
5677
+ const direct = MODEL_RATES[base];
5603
5678
  if (direct !== void 0) return direct;
5604
5679
  for (const key of Object.keys(MODEL_RATES)) {
5605
- if (modelName.startsWith(key)) {
5680
+ if (base.startsWith(key)) {
5606
5681
  const hit = MODEL_RATES[key];
5607
5682
  if (hit !== void 0) return hit;
5608
5683
  }
5609
5684
  }
5685
+ const tier = modelId.tierForModelId(base);
5686
+ if (tier !== null) return PER_TIER_RATE[tier];
5610
5687
  return DEFAULT_MODEL_RATE;
5611
5688
  }
5612
5689
  function usdCost(inputTokens, outputTokens, modelName) {
@@ -9731,7 +9808,7 @@ ${BUILD_USAGE}`);
9731
9808
 
9732
9809
  // sdk/cli/commands/dashboard.ts
9733
9810
  var import_node_child_process2 = require("node:child_process");
9734
- var import_node_module4 = require("node:module");
9811
+ var import_node_module5 = require("node:module");
9735
9812
  var import_node_http = require("node:http");
9736
9813
  var import_node_fs23 = require("node:fs");
9737
9814
  var import_node_path22 = require("node:path");
@@ -9767,7 +9844,7 @@ function anchorDirs() {
9767
9844
  return out;
9768
9845
  }
9769
9846
  function climbToMarker(startDir) {
9770
- const req = (0, import_node_module4.createRequire)((0, import_node_path22.join)(startDir, "noop.js"));
9847
+ const req = (0, import_node_module5.createRequire)((0, import_node_path22.join)(startDir, "noop.js"));
9771
9848
  let dir = startDir;
9772
9849
  let firstWithPkg = null;
9773
9850
  for (let i = 0; i < 12; i++) {
@@ -9803,7 +9880,7 @@ function findPackageRoot() {
9803
9880
  }
9804
9881
  function requireFromRoot(relPath) {
9805
9882
  const root = findPackageRoot();
9806
- const req = (0, import_node_module4.createRequire)((0, import_node_path22.join)(root, "noop.js"));
9883
+ const req = (0, import_node_module5.createRequire)((0, import_node_path22.join)(root, "noop.js"));
9807
9884
  return req((0, import_node_path22.join)(root, relPath));
9808
9885
  }
9809
9886
  function resolveRoot(deps, flags) {