@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +55 -0
- package/README.md +4 -13
- package/SKILL.md +1 -1
- package/agents/design-authority-watcher.md +24 -5
- package/bin/gdd-graph +4 -1
- package/docs/i18n/README.de.md +210 -527
- package/docs/i18n/README.fr.md +201 -518
- package/docs/i18n/README.it.md +209 -526
- package/docs/i18n/README.ja.md +207 -524
- package/docs/i18n/README.ko.md +208 -525
- package/docs/i18n/README.zh-CN.md +213 -551
- package/hooks/_hook-emit.js +113 -29
- package/hooks/budget-enforcer.ts +44 -5
- package/hooks/gdd-mcp-circuit-breaker.js +72 -3
- package/hooks/gdd-sessionstart-recap.js +23 -14
- package/hooks/hooks.json +2 -2
- package/package.json +2 -2
- package/reference/bandit-integration.md +13 -2
- package/scripts/bootstrap.cjs +40 -8
- package/scripts/install.cjs +23 -1
- package/scripts/lib/bandit-router.cjs +47 -5
- package/scripts/lib/detect/cli.cjs +13 -3
- package/scripts/lib/install/converters/cursor.cjs +11 -19
- package/scripts/lib/install/doctor-codex-plugin.cjs +1 -1
- package/scripts/lib/install/doctor-cursor-marketplace.cjs +2 -2
- package/scripts/lib/install/installer.cjs +72 -21
- package/scripts/lib/install/merge.cjs +31 -3
- package/scripts/lib/install/runtime-artifact-layout.cjs +42 -8
- package/scripts/lib/manifest/harnesses.json +29 -1
- package/scripts/lib/manifest/skills.json +1 -1
- package/scripts/skill-templates/bandit-reset/SKILL.md +2 -0
- package/scripts/skill-templates/bandit-status/SKILL.md +4 -1
- package/scripts/skill-templates/darkmode/SKILL.md +1 -1
- package/scripts/skill-templates/graphify/SKILL.md +6 -6
- package/scripts/skill-templates/quick/SKILL.md +3 -1
- package/scripts/skill-templates/reflect/SKILL.md +1 -1
- package/scripts/skill-templates/router/SKILL.md +4 -2
- package/sdk/cli/index.js +114 -47
- package/sdk/dashboard/data/source.cjs +50 -4
- package/sdk/event-stream/writer.ts +112 -30
- package/sdk/mcp/gdd-mcp/server.js +49 -36
- package/sdk/mcp/gdd-mcp/tools/shared.ts +20 -2
- package/sdk/mcp/gdd-state/server.js +107 -41
- package/sdk/primitives/lockfile.cjs +26 -5
- package/sdk/state/index.ts +91 -17
- package/sdk/state/lockfile.ts +47 -8
- package/skills/bandit-reset/SKILL.md +2 -0
- package/skills/bandit-status/SKILL.md +4 -1
- package/skills/darkmode/SKILL.md +1 -1
- package/skills/graphify/SKILL.md +6 -6
- package/skills/quick/SKILL.md +3 -1
- package/skills/reflect/SKILL.md +1 -1
- package/skills/router/SKILL.md +4 -2
package/sdk/state/lockfile.ts
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
//
|
|
185
|
-
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
package/skills/darkmode/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
package/skills/graphify/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
package/skills/quick/SKILL.md
CHANGED
|
@@ -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,
|
|
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).
|
package/skills/reflect/SKILL.md
CHANGED
|
@@ -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
|
|
package/skills/router/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gdd-router
|
|
3
|
-
description: "Routes a /gdd command to fast|quick|full path + S|M|L|XL complexity_class and returns {path, complexity_class, model_tier_overrides, resolved_models, estimated_cost_usd, cache_hits}.
|
|
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
|
-
|
|
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
|
|