@hegemonart/get-design-done 1.59.6 → 1.59.8

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 +55 -0
  4. package/README.md +4 -13
  5. package/SKILL.md +1 -1
  6. package/agents/design-authority-watcher.md +24 -5
  7. package/bin/gdd-graph +4 -1
  8. package/docs/i18n/README.de.md +210 -527
  9. package/docs/i18n/README.fr.md +201 -518
  10. package/docs/i18n/README.it.md +209 -526
  11. package/docs/i18n/README.ja.md +207 -524
  12. package/docs/i18n/README.ko.md +208 -525
  13. package/docs/i18n/README.zh-CN.md +213 -551
  14. package/hooks/_hook-emit.js +113 -29
  15. package/hooks/budget-enforcer.ts +44 -5
  16. package/hooks/gdd-mcp-circuit-breaker.js +72 -3
  17. package/hooks/gdd-sessionstart-recap.js +23 -14
  18. package/hooks/hooks.json +2 -2
  19. package/package.json +2 -2
  20. package/reference/bandit-integration.md +13 -2
  21. package/scripts/bootstrap.cjs +40 -8
  22. package/scripts/install.cjs +23 -1
  23. package/scripts/lib/bandit-router.cjs +47 -5
  24. package/scripts/lib/detect/cli.cjs +13 -3
  25. package/scripts/lib/install/converters/cursor.cjs +11 -19
  26. package/scripts/lib/install/doctor-codex-plugin.cjs +1 -1
  27. package/scripts/lib/install/doctor-cursor-marketplace.cjs +2 -2
  28. package/scripts/lib/install/installer.cjs +72 -21
  29. package/scripts/lib/install/merge.cjs +31 -3
  30. package/scripts/lib/install/runtime-artifact-layout.cjs +42 -8
  31. package/scripts/lib/manifest/harnesses.json +29 -1
  32. package/scripts/lib/manifest/skills.json +1 -1
  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 +114 -47
  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
@@ -99,7 +99,25 @@ export async function acquire(
99
99
 
100
100
  const parsed: LockPayload | null = parseLock(existing);
101
101
  if (parsed !== null && isStale(parsed, staleMs)) {
102
- // Clear stale lock and retry.
102
+ // Audit D3 (TOCTOU): two waiters could each observe the same stale
103
+ // lock and both unlink+recreate, or one could unlink a DIFFERENT,
104
+ // freshly-acquired lock that replaced the stale one in the read→unlink
105
+ // window. Guard by confirming the on-disk bytes STILL match the exact
106
+ // stale payload we observed immediately before unlinking; if they
107
+ // changed (a new holder wrote a fresh lock), abandon the clear and
108
+ // loop — the next iteration re-reads and re-evaluates the new holder.
109
+ const confirm: string | null = readLockSafe(lockPath);
110
+ if (confirm === null) {
111
+ // Already gone — someone cleared it first. Retry immediately to
112
+ // race for the wx-create.
113
+ continue;
114
+ }
115
+ if (confirm !== existing) {
116
+ // A different writer replaced the lock between our read and now.
117
+ // Do NOT unlink — that would steal a (potentially fresh) lock.
118
+ await sleep(pollMs);
119
+ continue;
120
+ }
103
121
  try {
104
122
  unlinkSync(lockPath);
105
123
  } catch (delErr) {
@@ -108,6 +126,9 @@ export async function acquire(
108
126
  // Someone else cleared it first; fall through to retry.
109
127
  }
110
128
  }
129
+ // The wx-create on the next iteration is itself atomic (O_CREAT|O_EXCL),
130
+ // so even if two waiters both reach the unlink, only ONE wins the
131
+ // recreate; the loser sees EEXIST and re-evaluates.
111
132
  continue;
112
133
  }
113
134
 
@@ -181,13 +202,31 @@ function parseLock(raw: string): LockPayload | null {
181
202
  }
182
203
 
183
204
  function isStale(payload: LockPayload, staleMs: number): boolean {
184
- // 1) PID check if the process is dead, the lock is stale.
185
- if (!isPidAlive(payload.pid, payload.host)) return true;
186
- // 2) Age check acquired_at older than staleMs is stale even if the
187
- // PID is reused by something else.
188
- const acquiredAt = Date.parse(payload.acquired_at);
189
- if (!Number.isFinite(acquiredAt)) return true; // garbage timestamp
190
- return Date.now() - acquiredAt > staleMs;
205
+ // Audit D3: PID-liveness is AUTHORITATIVE. A lock whose holder PID is still
206
+ // alive on this host is NEVER stale, regardless of age — a legitimate
207
+ // long-running mutation (e.g. a >60s transaction) must not have its lock
208
+ // stolen out from under it. The age-based fallback only applies when we
209
+ // CANNOT confirm liveness: a dead PID, a missing/invalid pid field, a
210
+ // cross-host holder, or an unsignalable PID.
211
+ //
212
+ // Note: `isPidAlive` already returns true for the conservative
213
+ // can't-introspect cases (different host, EPERM). For those, the holder is
214
+ // treated as alive and the lock is held until released — we do NOT fall
215
+ // through to age-staleness, because doing so reintroduces the steal. Stale
216
+ // reclamation for genuinely-abandoned cross-host/unsignalable locks is left
217
+ // to manual cleanup, which is strictly safer than racing a live writer.
218
+ const pidRecorded =
219
+ typeof payload.pid === 'number' && Number.isInteger(payload.pid) && payload.pid > 0;
220
+ if (!pidRecorded) {
221
+ // No usable pid → cannot prove liveness. Fall back to age-staleness.
222
+ const acquiredAt = Date.parse(payload.acquired_at);
223
+ if (!Number.isFinite(acquiredAt)) return true; // garbage timestamp
224
+ return Date.now() - acquiredAt > staleMs;
225
+ }
226
+ // A recorded, live PID is decisive: NOT stale at any age.
227
+ if (isPidAlive(payload.pid, payload.host)) return false;
228
+ // PID is recorded but confirmed dead (ESRCH on this host) → stale.
229
+ return true;
191
230
  }
192
231
 
193
232
  /**
@@ -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 `/gdd: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 /gdd: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: `/gdd: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 `/gdd: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 `/gdd:*` 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` / `/gdd:handoff`, and `/gdd: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