@hegemonart/get-design-done 1.56.0 → 1.57.1

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.
@@ -5,14 +5,14 @@
5
5
  },
6
6
  "metadata": {
7
7
  "description": "Get Design Done — 5-stage agent-orchestrated design pipeline with 9 connections, handoff-first workflow, bidirectional Figma write-back, 22+ specialized agents, queryable knowledge layer (intel store, dependency analysis, learnings extraction), and a self-improvement loop (reflector, frontmatter + budget feedback, global-skills layer). v1.20.0 ships the SDK foundation: gdd-state MCP server (11 typed tools), lockfile-safe STATE.md mutations, event stream, and resilience primitives (jittered-backoff, rate-guard, error-classifier, iteration-budget) for rate-limit + 429 + context-overflow recovery. Full CI/CD pipeline (Node 22/24 × Linux/macOS/Windows) and release automation (auto-tag + GitHub Release + release-time smoke test).",
8
- "version": "1.56.0"
8
+ "version": "1.57.1"
9
9
  },
10
10
  "plugins": [
11
11
  {
12
12
  "name": "get-design-done",
13
13
  "source": "./",
14
14
  "description": "Agent-orchestrated 5-stage design pipeline: Brief → Explore → Plan → Design → Verify. 22+ specialized agents, 9 connections (Figma, Refero, Preview, Storybook, Chromatic, Figma Writer, Graphify, Pinterest, Claude Design), Claude Design handoff, bidirectional Figma write-back, and a queryable intel store (.design/intel/) for dependency and learnings queries. Standalone commands: style, darkmode, compare, figma-write, graphify, handoff, analyze-dependencies, skill-manifest, extract-learnings. Embeds NNG heuristics, WCAG thresholds, typographic systems, motion framework, and anti-pattern catalog. Ships with a full CI/CD pipeline (Node 22/24 × Linux/macOS/Windows) and release automation. Optimization layer (v1.0.4.1, retroactive): gdd-router + gdd-cache-manager skills, PreToolUse budget-enforcer hook, tier-aware agent frontmatter, lazy checker gates, streaming synthesizer, /gdd:warm-cache + /gdd:optimize commands, and cost telemetry at .design/telemetry/costs.jsonl — targeting 50-70% per-task token-cost reduction with no quality-floor regression. v1.20.0 SDK foundation: gdd-state MCP server (11 typed tools), lockfile-safe STATE.md mutations, event stream at .design/telemetry/events.jsonl, resilience primitives (jittered-backoff, rate-guard, error-classifier, iteration-budget) with rate-limit + 429 + context-overflow recovery, and TypeScript toolchain.",
15
- "version": "1.56.0",
15
+ "version": "1.57.1",
16
16
  "author": {
17
17
  "name": "hegemonart"
18
18
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "get-design-done",
3
3
  "short_name": "gdd",
4
- "version": "1.56.0",
4
+ "version": "1.57.1",
5
5
  "description": "Agent-orchestrated 5-stage design pipeline: Brief → Explore → Plan → Design → Verify. 59 specialized agents, 88 skills, 41 connection integrations (Figma, Refero, Preview, Storybook, Chromatic, Graphify, Slack, Linear, Jira, Notion, and more), handoff-first workflow via Claude Design bundles, bidirectional Figma write-back (annotations, Code Connect), queryable intel store (`.design/intel/`) for O(1) design surface lookups, and self-improvement loop (reflector agent, frontmatter + budget feedback, global-skills layer at `~/.claude/gdd/global-skills/`). Standalone commands: style, darkmode, compare, figma-write, graphify, handoff, analyze-dependencies, skill-manifest, extract-learnings, reflect, apply-reflections. Embeds NNG heuristics, WCAG thresholds, typographic systems, motion framework, and anti-pattern catalog. Ships with a full CI/CD pipeline (Node 22/24 × Linux/macOS/Windows, lint + schema + frontmatter + stale-ref + shellcheck + gitleaks + injection-scan + blocking size-budget) and release automation (auto-tag + GitHub Release + release-time smoke test). Optimization layer (v1.0.4.1, retroactive): gdd-router + gdd-cache-manager skills, PreToolUse budget-enforcer hook, tier-aware agent frontmatter, lazy checker gates, streaming synthesizer, /gdd:warm-cache + /gdd:optimize commands, and cost telemetry at .design/telemetry/costs.jsonl — targeting 50-70% per-task token-cost reduction with no quality-floor regression. v1.20.0 SDK foundation: gdd-state MCP server (11 typed tools), lockfile-safe STATE.md mutations, event stream at .design/telemetry/events.jsonl, resilience primitives (jittered-backoff, rate-guard, error-classifier, iteration-budget) with rate-limit + 429 + context-overflow recovery, and TypeScript toolchain. v1.27.7 ships gdd-mcp (Phase 27.7): 12 read-only MCP tools for sub-3s priming. v1.28.0 (Phase 28): Foundational References Tier 2 — 5 new reference files (color-theory, composition, proportion-systems, i18n, contrast-advanced), 2 verifier i18n probes + 1 explore i18n-readiness probe, 12 additive cross-link insertions across 10 existing references, 2 orthogonal audit-scoring lens-tags (composition_alignment + i18n_readiness).",
6
6
  "author": {
7
7
  "name": "hegemonart",
package/CHANGELOG.md CHANGED
@@ -4,6 +4,79 @@ All notable changes to get-design-done are documented here. Versions follow [sem
4
4
 
5
5
  ---
6
6
 
7
+ ## [1.57.1] - 2026-06-03
8
+
9
+ ### Fixed
10
+
11
+ Post-wave debug analysis (a 4-agent sweep after Phase 57) found and fixed a set of latent bugs that surface only when
12
+ `better-sqlite3` is installed (the CI surface, which has no module, was unaffected - so these never failed CI but did
13
+ degrade real users who have the module). No new dependency; the markdown floor is unchanged.
14
+
15
+ - **Recall returned nothing for every `.md` file when better-sqlite3 was present.** `scripts/lib/design-search.cjs`
16
+ passed unquoted query terms (e.g. `heuristics.md OR reference/heuristics.md`) to FTS5, whose trigram tokenizer rejects
17
+ `.` and `/` as a syntax error; the error was swallowed and recall came back empty. Each term is now double-quoted
18
+ (matching the instinct-store pattern), so FTS5 recall matches the JS-scan fallback.
19
+ - **The Phase 57 fact-force freshness guard silently discarded hand-edits.** `state-store.cjs` detected an out-of-band
20
+ STATE.md edit but its re-sync body was empty, so the edit was overwritten and lost. It now folds the hand-edit back
21
+ into SQLite before the next mutation.
22
+ - **Blocker rows duplicated on every re-migration** (no `ON CONFLICT`); migration now clears a cycle's blockers before
23
+ re-inserting. **FTS5 virtual tables were never populated** by the migration; they are now. **`/gdd:state recover`**
24
+ never awaited the async migration (always reported corruption); it is now async. **State getters threw** on an absent
25
+ `state.sqlite` (exposing the fact-force hook); they now return empty. **`migrationActive`** guards against a directory
26
+ named `state.sqlite`. **`WITH ... SELECT` CTEs** are allowed by the read-only query surface.
27
+ - **The `risk_assessment` event did not conform to its own schema** (`tool`/`score` instead of `tool_name`/`risk_score`,
28
+ no `event_id`, extra fields); `hooks/gdd-risk-gate.js` now emits the schema-correct shape, and an Ajv validation test
29
+ guards it. The **dashboard risk column** read the wrong fields and case-mismatched the action vocabulary, so it was
30
+ permanently blank; it is now wired and case-correct.
31
+ - **`budget-enforcer` PreToolUse blocks** used `message` instead of `stopReason`, so the block reason was invisible to
32
+ the user; they now use `stopReason`. The **read-injection scanner** loads its pattern file fail-open (a missing file
33
+ no longer crashes the hook). Three package-root walk-ups now match the scoped package name.
34
+
35
+ ## [1.57.0] - 2026-06-03
36
+
37
+ ### Phase 57 - SQLite State Backbone (Cross-Session Query Layer)
38
+
39
+ GDD project state lives in per-file markdown (STATE.md blocks, recall, instincts). Cross-cycle queries ("every decision
40
+ tagged accessibility across the last five cycles") require fan-out greps and are effectively infeasible. Phase 57 adds an
41
+ opt-in `.design/state.sqlite` as an indexed query layer over that state, while markdown stays the human-editable source of
42
+ truth. It is **built with zero new dependency**: SQLite is reached through the existing opportunistic
43
+ `probeOptional('better-sqlite3')` runtime probe (the same pattern Phase 51 uses for the instinct store), with a guaranteed
44
+ markdown / JS-scan fallback whenever the module is absent. Migration is opt-in (`--migrate-state`) in v1.57.0, dual-writes
45
+ for one minor version, and is fully reversible (`/gdd:state demigrate`). When better-sqlite3 is not installed, GDD behaves
46
+ exactly as before. Planned and executed via the GSD pipeline (3 + 1 + 2 parallel executors with one reconciliation pass).
47
+
48
+ ### Breaking changes
49
+
50
+ - **Opt-in SQLite state backbone.** Running `node scripts/lib/state/migrate-to-sqlite.cjs --migrate-state` builds
51
+ `.design/state.sqlite` (14 tables plus FTS5) from your STATE.md. After migration, state mutations dual-write: SQLite is
52
+ the authoritative store and STATE.md is rendered from it byte-for-byte (the existing `gdd-state` `read`/`mutate`/`transition`
53
+ API is unchanged). Without migration, or when better-sqlite3 is absent, nothing changes - markdown stays authoritative.
54
+ Markdown is always human-editable; a hand-edit is detected on the next write and folded back into SQLite.
55
+ - **New `/gdd:state` skill** with three subcommands: `query "<sql>"` (engine-level read-only SELECT over the
56
+ decisions/blockers/plans tables), `recover` (rebuild a corrupt `state.sqlite` from markdown), and `demigrate` (drop
57
+ `state.sqlite` and fall back to the markdown-only source of truth).
58
+
59
+ ### Added
60
+
61
+ - **`scripts/lib/state/`** - `state-backend.cjs` (`probeOptional('better-sqlite3')` + FTS5 probe -> `{Database, BACKEND}`;
62
+ WAL / busy_timeout / foreign_keys pragmas; engine-level readonly opens), `state-store.cjs` (dual-backend dispatch; the
63
+ SQLite-authoritative -> render-markdown -> write-STATE.md transaction; hand-edit freshness guard), `migrate-to-sqlite.cjs`
64
+ (idempotent UPSERT migration, `--migrate-state` opt-in, `--dry-run`), `render-markdown.cjs` (byte-equal STATE.md
65
+ reconstruction from SQLite via the proven serializer), `query-surface.cjs` (readonly SQL + first-token denylist, recover,
66
+ demigrate, cap-10 `.bak` rotation).
67
+ - **`sdk/state/schema.sql`** - 14 tables (state_position, decisions, blockers, must_haves, plans, findings, design_debt,
68
+ recall_records, instincts, sessions, worktree_state, conflict_incidents, plus `_meta`/`_block_meta`) with FTS5 virtual
69
+ tables on decisions/findings/recall/instincts.
70
+ - **Consumers read SQLite when migrated** - `gdd_state__get` and the dashboard data plane read SQLite-direct (markdown
71
+ scrape fallback; the MCP tool schema is unchanged), and the Phase 56 fact-force gate gains an FTS5 tier-0 lookup with the
72
+ grep fallback intact.
73
+
74
+ ### Changed
75
+
76
+ - **`sdk/state/index.ts`** routes `read`/`mutate`/`transition` through the SQLite dual-write path only when migration is
77
+ active for the resolved state file (a sibling `state.sqlite` exists); otherwise the markdown path runs byte-identically.
78
+ The new SQLite sibling lock (`state.sqlite.lock`) is always acquired before `STATE.md.lock`.
79
+
7
80
  ## [1.56.0] - 2026-06-03
8
81
 
9
82
  ### Phase 56 - Risk-Scoring + Fact-Forcing Gate (Quantified Action Confidence)
package/README.md CHANGED
@@ -273,6 +273,8 @@ All 14 runtimes receive their native artifact layout (`skills/`, `command/`, `ag
273
273
 
274
274
  **Risk-scoring and fact-forcing gate (v1.56.0).** Writer actions now carry a quantified risk score instead of a binary allow/deny. A pure, deterministic scorer (`scripts/lib/risk/compute-risk.cjs`, frozen tables, no I/O) grades each Write / Edit / MultiEdit / Bash by tool, file sensitivity, and input shape, then two PreToolUse hooks act on it: `gdd-risk-gate.js` emits a `risk_assessment` event and blocks only the genuinely dangerous actions (destructive bash, high-sensitivity-file rewrites at or above 0.85), while `gdd-fact-force.js` holds the first write to a file until its DesignContext consumers and recorded decisions have actually been read this session. The fact-force hold is soft and softens to a warning when the Phase 52 graph is absent, so greenfield projects are never over-blocked. `/gdd:override` clears a block or a fact-force hold with an approver and reason (audit-trailed as a `D-XX` override decision), and `design-fixer` routes findings by confidence times risk. **Built dep-free** (a maintainer decision: a pure scorer plus static tables, no ML). **No new runtime dependency.**
275
275
 
276
+ **SQLite state backbone (v1.57.0).** Project state (decisions, blockers, plans, findings, recall, instincts) lives in per-file markdown, which makes cross-cycle queries ("every accessibility decision across the last five cycles") infeasible. Phase 57 adds an opt-in `.design/state.sqlite` as an indexed query layer while markdown stays the human-editable source of truth. It is **built with zero new dependency**: SQLite is reached through the existing opportunistic `probeOptional('better-sqlite3')` runtime probe (the Phase 51 pattern), with a guaranteed markdown / JS-scan fallback whenever the module is absent. Run `migrate-to-sqlite.cjs --migrate-state` to build the database (14 tables plus FTS5); afterwards state mutations dual-write (SQLite authoritative, STATE.md rendered from it byte-for-byte) while the `gdd-state` read/mutate/transition API is unchanged. `/gdd:state query "<sql>"` runs read-only SELECTs (engine-level readonly), `/gdd:state recover` rebuilds a corrupt database from markdown, and `/gdd:state demigrate` reverts to markdown-only. When better-sqlite3 is not installed, GDD behaves exactly as before. **No new runtime dependency.**
277
+
276
278
  Verify with:
277
279
 
278
280
  ```
package/SKILL.md CHANGED
@@ -117,6 +117,7 @@ Each stage produces artifacts in `.design/` inside the current project.
117
117
  | `context [nodes --type X \| edges --type Z \| path <a> <b> \| consumers-of <id> \| unreachable \| cycles \| coverage]` | `get-design-done:gdd-context` | Phase 52 - read-only query front end for the typed DesignContext graph at `.design/context-graph.json`; lists/filters nodes and edges, traces a path between two nodes, finds a node's consumers, and reports unreachable nodes, dependency cycles, and coverage. Never writes |
118
118
  | `migrate-context [--dry-run]` | `get-design-done:gdd-migrate-context` | Phase 52 - migrate a pre-Phase-52 project from flat `.design/map/*.md` mapper notes to the typed DesignContext graph; runs the extract-*.mjs passes, merges fragments, validates with `validate-design-context.cjs`, and flags low-confidence transforms for review. Preview-first; `--dry-run` previews without writing |
119
119
  | `override <finding-id \| factforce <path>> [--approver <who>] [--reason <text>]` | `get-design-done:gdd-override` | Phase 56 - escalation surface for a risk-gate block or a first-write fact-force hold; with an approver and reason, writes a `D-XX` override-tagged decision (audit trail) for a blocked finding, or clears the fact-force `checked[path]` lock for a path you have legitimately reviewed. Mirrors unlock-decision; never overrides silently |
120
+ | `state <query "<sql>" \| recover \| demigrate>` | `get-design-done:gdd-state` | Phase 57 - operate the opt-in SQLite state backbone: `query` runs a read-only SELECT over the decisions/blockers/plans tables (engine-level readonly), `recover` rebuilds a corrupt `state.sqlite` from the markdown STATE.md, `demigrate` removes `state.sqlite` to fall back to the markdown-only source of truth. Markdown stays the human-editable SoT; SQLite is opportunistic and reversible |
120
121
 
121
122
  ## Handoff Routing
122
123
 
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: gdd-state
3
+ description: "Query, recover, or roll back the Phase 57 SQLite state backbone. Use when you need to inspect the decisions/blockers/plans tables with a raw SELECT, rebuild a corrupt state.sqlite from the markdown STATE.md, or revert to the markdown-only source of truth by removing state.sqlite. Activates for requests involving querying the SQLite state database, recovering from SQLite corruption, or reverting the migration (demigrate)."
4
+ argument-hint: "<query \"<sql>\" | recover | demigrate>"
5
+ user-invocable: true
6
+ tools: Read, Bash, Grep, Glob
7
+ ---
8
+
9
+ # /gdd:state
10
+
11
+ The Phase 57 SQLite state backbone is opt-in (`--migrate-state`) and fully reversible.
12
+ Markdown `.design/STATE.md` is always the human-editable SoT; SQLite is a faster query
13
+ layer derived from it. This skill exposes three subcommands for operating on that layer.
14
+
15
+ ## Subcommands
16
+
17
+ | Subcommand | What it does |
18
+ |---|---|
19
+ | `/gdd:state query "<sql>"` | Run a read-only SELECT against `.design/state.sqlite`. |
20
+ | `/gdd:state recover` | Rotate the current sqlite to a `.bak` and rebuild from markdown. |
21
+ | `/gdd:state demigrate` | Remove `.design/state.sqlite`; markdown becomes the SoT again. |
22
+
23
+ ---
24
+
25
+ ## query
26
+
27
+ Execute a read-only SELECT against the state database.
28
+
29
+ The engine opens the file with `readonly:true` so all writes are rejected at the
30
+ engine level. A first-token denylist (Set membership, no regex) additionally blocks
31
+ DROP, DELETE, UPDATE, INSERT, ALTER, ATTACH, CREATE, PRAGMA, VACUUM, ANALYZE,
32
+ REINDEX, and REPLACE before the connection is opened.
33
+
34
+ ```bash
35
+ node -e '
36
+ const qs = require("./scripts/lib/state/query-surface.cjs");
37
+ const result = qs.query(process.argv[1], { projectRoot: process.cwd() });
38
+ console.log(JSON.stringify(result, null, 2));
39
+ ' "<sql>"
40
+ ```
41
+
42
+ Outputs `{ rows: [...], backend: "sqlite" }` on success, or
43
+ `{ degraded: true, message: "..." }` when SQLite is not active.
44
+
45
+ Degrades gracefully (no throw) when `BACKEND==='markdown'` or when
46
+ `.design/state.sqlite` has not been created yet (run `--migrate-state` first).
47
+
48
+ ---
49
+
50
+ ## recover
51
+
52
+ Rotate the current (possibly corrupt) `.design/state.sqlite` to `.bak.0`, then
53
+ rebuild a fresh database from the markdown `.design/STATE.md` using
54
+ `migrate-to-sqlite.cjs` in force mode. Runs `PRAGMA integrity_check` on the
55
+ result and reports the outcome.
56
+
57
+ ```bash
58
+ node -e '
59
+ const qs = require("./scripts/lib/state/query-surface.cjs");
60
+ const result = qs.recover({ projectRoot: process.cwd() });
61
+ console.log(JSON.stringify(result, null, 2));
62
+ '
63
+ ```
64
+
65
+ Outputs `{ recovered: true, integrity: true, message: "..." }` on success.
66
+
67
+ Backup rotation keeps at most 10 files (`.bak.0` through `.bak.9`). The oldest
68
+ backup is overwritten when the cap is reached.
69
+
70
+ ---
71
+
72
+ ## demigrate
73
+
74
+ Remove `.design/state.sqlite` so the markdown `STATE.md` becomes the SoT again.
75
+ Idempotent: if the file does not exist, returns a clear no-op message without error.
76
+ A backup is taken in `.bak.0` before removal.
77
+
78
+ ```bash
79
+ node -e '
80
+ const qs = require("./scripts/lib/state/query-surface.cjs");
81
+ const result = qs.demigrate({ projectRoot: process.cwd() });
82
+ console.log(JSON.stringify(result, null, 2));
83
+ '
84
+ ```
85
+
86
+ Outputs `{ demigrated: true, message: "..." }` when the file was removed, or
87
+ `{ demigrated: false, message: "..." }` when it was already absent (no-op).
88
+
89
+ To re-enable SQLite after a demigrate, run `--migrate-state` again.
90
+
91
+ ---
92
+
93
+ ## Key design decisions
94
+
95
+ - **Markdown is always the SoT.** SQLite is opt-in via `--migrate-state` and
96
+ reversible via `demigrate`. The markdown file is never silently overwritten.
97
+ - **Read-only queries only.** The `query` subcommand enforces SELECT-only via both
98
+ the engine (`readonly:true`) and a defense-in-depth denylist. No writes
99
+ are possible through this skill.
100
+ - **Backup rotation cap.** `rotateBak` shifts `.bak.0..8` up by one index and caps
101
+ at `.bak.9` (10 files total). The oldest backup is overwritten automatically.
102
+ - **Graceful degradation.** All subcommands return a clear `{ degraded, message }`
103
+ object (no throw) when `better-sqlite3` is not installed or when
104
+ `GDD_STATE_BACKEND=markdown` is set.
105
+
106
+ ## STATE COMPLETE
@@ -332,6 +332,7 @@ interface ToolOutput {
332
332
  continue: boolean;
333
333
  suppressOutput?: boolean;
334
334
  message?: string;
335
+ stopReason?: string;
335
336
  modified_tool_input?: ToolInput;
336
337
  cached_result?: unknown;
337
338
  }
@@ -1046,7 +1047,7 @@ export async function main(): Promise<void> {
1046
1047
  const response: ToolOutput = {
1047
1048
  continue: false,
1048
1049
  suppressOutput: false,
1049
- message: `gdd-budget-enforcer: rate-limited on anthropic, retry in ${waitSeconds}s (resetAt=${rateState.resetAt})`,
1050
+ stopReason: `gdd-budget-enforcer: rate-limited on anthropic, retry in ${waitSeconds}s (resetAt=${rateState.resetAt})`,
1050
1051
  };
1051
1052
  process.stdout.write(JSON.stringify(response));
1052
1053
  return;
@@ -1121,7 +1122,7 @@ export async function main(): Promise<void> {
1121
1122
  const response: ToolOutput = {
1122
1123
  continue: false,
1123
1124
  suppressOutput: false,
1124
- message: `Project budget cap reached: $${projClass.spend.toFixed(2)} of $${budget.project_cap_usd.toFixed(2)} (${projClass.pct.toFixed(0)}%). Raise project_cap_usd in .design/budget.json, or set project_cap_enforcement_mode to "warn" to keep going. (Graceful halt — the current stage's earlier spawns already completed; this blocks the next one.)`,
1125
+ stopReason: `Project budget cap reached: $${projClass.spend.toFixed(2)} of $${budget.project_cap_usd.toFixed(2)} (${projClass.pct.toFixed(0)}%). Raise project_cap_usd in .design/budget.json, or set project_cap_enforcement_mode to "warn" to keep going. (Graceful halt — the current stage's earlier spawns already completed; this blocks the next one.)`,
1125
1126
  };
1126
1127
  process.stdout.write(JSON.stringify(response));
1127
1128
  return;
@@ -1162,7 +1163,7 @@ export async function main(): Promise<void> {
1162
1163
  const response: ToolOutput = {
1163
1164
  continue: false,
1164
1165
  suppressOutput: false,
1165
- message: `Budget cap reached for ${capLabel}. Estimated: $${estCost.toFixed(4)}, cap: $${perSpawnCap.toFixed(2)}. Raise cap in .design/budget.json or retry after next task.`,
1166
+ stopReason: `Budget cap reached for ${capLabel}. Estimated: $${estCost.toFixed(4)}, cap: $${perSpawnCap.toFixed(2)}. Raise cap in .design/budget.json or retry after next task.`,
1166
1167
  };
1167
1168
  process.stdout.write(JSON.stringify(response));
1168
1169
  return;
@@ -1187,7 +1188,7 @@ export async function main(): Promise<void> {
1187
1188
  const response: ToolOutput = {
1188
1189
  continue: false,
1189
1190
  suppressOutput: false,
1190
- 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.`,
1191
+ stopReason: `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.`,
1191
1192
  };
1192
1193
  process.stdout.write(JSON.stringify(response));
1193
1194
  return;
@@ -148,13 +148,102 @@ function decisionSources(cwd) {
148
148
  }
149
149
 
150
150
  /**
151
- * Does any decision/blocker line mention this file? Best-effort substring grep
152
- * over the canonical docs for the file's basename or relPath (the same terms
153
- * the decision-injector greps on). Returns { found:boolean, where:string|null }.
151
+ * Lazy-require state-store.cjs (Phase 57 dual-backend layer).
152
+ * Returns null if not yet available (degrade to grep).
153
+ */
154
+ function _requireStateStore() {
155
+ try {
156
+ const candidates = [
157
+ path.join(__dirname, '..', 'scripts', 'lib', 'state', 'state-store.cjs'),
158
+ ];
159
+ const root = findPackageRoot(__dirname);
160
+ if (root) candidates.push(path.join(root, 'scripts', 'lib', 'state', 'state-store.cjs'));
161
+ for (const c of candidates) {
162
+ try {
163
+ const m = require(c);
164
+ if (m && typeof m.queryDecisions === 'function') return m;
165
+ } catch { /* try next */ }
166
+ }
167
+ } catch { /* never throw */ }
168
+ return null;
169
+ }
170
+
171
+ /**
172
+ * Lazy-require state-backend.cjs to check if migration is active.
173
+ * Migration is active when BACKEND==='sqlite' AND the sibling .design/state.sqlite exists.
174
+ */
175
+ function _isMigrationActive(cwd) {
176
+ try {
177
+ const candidates = [
178
+ path.join(__dirname, '..', 'scripts', 'lib', 'state', 'state-backend.cjs'),
179
+ ];
180
+ const root = findPackageRoot(__dirname);
181
+ if (root) candidates.push(path.join(root, 'scripts', 'lib', 'state', 'state-backend.cjs'));
182
+ let backend = null;
183
+ for (const c of candidates) {
184
+ try {
185
+ const m = require(c);
186
+ if (m && typeof m.BACKEND === 'string' && typeof m.sqlitePath === 'function') {
187
+ backend = m;
188
+ break;
189
+ }
190
+ } catch { /* try next */ }
191
+ }
192
+ if (!backend || backend.BACKEND !== 'sqlite') return false;
193
+ // Verify that the sibling .design/state.sqlite actually exists (migration-active gate).
194
+ const dbPath = backend.sqlitePath(cwd);
195
+ return fs.existsSync(dbPath);
196
+ } catch {
197
+ return false;
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Does any decision/blocker line mention this file?
203
+ *
204
+ * When migration is active (BACKEND==='sqlite' AND .design/state.sqlite exists):
205
+ * - Tier-0: query state-store.cjs queryDecisions(term) for each search term.
206
+ * Falls back to grep if the store query throws.
207
+ * When migration is NOT active (default, un-migrated):
208
+ * - Substring grep over STATE.md/CYCLES.md/LEARNINGS.md (UNCHANGED).
209
+ *
210
+ * Returns { found:boolean, where:string|null }.
211
+ * The return shape and the soften-if-absent behavior are UNCHANGED.
154
212
  */
155
213
  function decisionMentions(cwd, relPath) {
156
214
  const basename = path.basename(relPath);
157
215
  const terms = Array.from(new Set([basename, relPath].filter(Boolean)));
216
+
217
+ // Tier-0: FTS5 path (migration-active only).
218
+ if (_isMigrationActive(cwd)) {
219
+ const store = _requireStateStore();
220
+ if (store) {
221
+ try {
222
+ for (const t of terms) {
223
+ if (!t) continue;
224
+ const rows = store.queryDecisions(t, { projectRoot: cwd, limit: 1 });
225
+ if (Array.isArray(rows) && rows.length > 0) {
226
+ return { found: true, where: 'state.sqlite' };
227
+ }
228
+ }
229
+ // FTS5 returned no matches; check blockers via getBlockers substring.
230
+ const blockers = store.getBlockers ? store.getBlockers({ projectRoot: cwd }) : [];
231
+ if (Array.isArray(blockers) && blockers.length > 0) {
232
+ for (const b of blockers) {
233
+ const body = (b.body_md || b.raw_line || '');
234
+ for (const t of terms) {
235
+ if (t && body.includes(t)) return { found: true, where: 'state.sqlite' };
236
+ }
237
+ }
238
+ }
239
+ return { found: false, where: null };
240
+ } catch {
241
+ // FTS5 query failed: fall through to grep.
242
+ }
243
+ }
244
+ }
245
+
246
+ // Tier-1 (always-on fallback): substring grep over canonical docs.
158
247
  for (const src of decisionSources(cwd)) {
159
248
  let content;
160
249
  try { content = fs.readFileSync(src, 'utf8'); } catch { continue; }
@@ -13,9 +13,32 @@
13
13
 
14
14
  const fs = require('fs');
15
15
  const path = require('path');
16
- const { matches } = require(path.join(__dirname, '..', 'scripts', 'lib', 'glob-match.cjs'));
17
16
 
18
- const REPO_ROOT = path.resolve(__dirname, '..');
17
+ /**
18
+ * Walk up from startDir to find the package root by looking for a
19
+ * package.json with name '@hegemonart/get-design-done'. Returns null
20
+ * when the root cannot be found (e.g. in unusual installed layouts).
21
+ * Mirrors the pattern used by gdd-fact-force.js / gdd-risk-gate.js
22
+ * (Phase 56+) to be robust against esbuild/installed layouts that
23
+ * may relocate or rewrite __dirname.
24
+ */
25
+ function findPackageRoot(startDir) {
26
+ let dir = startDir;
27
+ for (let i = 0; i < 12; i++) {
28
+ try {
29
+ const pkg = require(path.join(dir, 'package.json'));
30
+ if (pkg && pkg.name === '@hegemonart/get-design-done') return dir;
31
+ } catch { /* not this level */ }
32
+ const parent = path.dirname(dir);
33
+ if (parent === dir) break;
34
+ dir = parent;
35
+ }
36
+ return null;
37
+ }
38
+
39
+ const REPO_ROOT = findPackageRoot(__dirname) || path.resolve(__dirname, '..');
40
+
41
+ const { matches } = require(path.join(REPO_ROOT, 'scripts', 'lib', 'glob-match.cjs'));
19
42
 
20
43
  function loadProtectedPaths(cwd) {
21
44
  const defaultFile = path.join(REPO_ROOT, 'reference', 'protected-paths.default.json');
@@ -79,7 +79,15 @@ function loadPatterns(): readonly RegExp[] {
79
79
  );
80
80
  }
81
81
 
82
- const INJECTION_PATTERNS: readonly RegExp[] = loadPatterns();
82
+ // Wrapped in try/catch so a missing injection-patterns.cjs does not throw
83
+ // at import time (before main()'s catch guard is active). On failure the
84
+ // hook falls back to an empty pattern list and emits a passthrough result.
85
+ let INJECTION_PATTERNS: readonly RegExp[];
86
+ try {
87
+ INJECTION_PATTERNS = loadPatterns();
88
+ } catch {
89
+ INJECTION_PATTERNS = [];
90
+ }
83
91
 
84
92
  // ── Types ───────────────────────────────────────────────────────────────────
85
93
 
@@ -2,6 +2,9 @@
2
2
  'use strict';
3
3
  /**
4
4
  * hooks/gdd-risk-gate.js — PreToolUse:Write|Edit|MultiEdit|Bash risk gate (Phase 56, RISK-02).
5
+ * Payload shape locked to RiskAssessmentPayload (events.schema.json): event_id, tool_name,
6
+ * risk_score, suggested_action, reasons (required). Optional: agent, decision_context.
7
+ * additionalProperties:false — do NOT add breakdown/paths/score/tool to the payload.
5
8
  *
6
9
  * Quantifies the confidence/risk of a writer action with the PURE scorer
7
10
  * `scripts/lib/risk/compute-risk.cjs` (executor A), emits a `risk_assessment`
@@ -39,6 +42,7 @@
39
42
 
40
43
  const fs = require('fs');
41
44
  const path = require('path');
45
+ const { randomUUID } = require('node:crypto');
42
46
 
43
47
  // ── Package-root walk-up: locate scripts/lib/risk/compute-risk.cjs ──────────
44
48
  // Start at this file's dir and climb until we find the risk module (or a
@@ -325,7 +329,14 @@ async function main() {
325
329
  );
326
330
  } catch { /* swallow */ }
327
331
  emitHookFired('allow', { reason: 'scorer-error' });
328
- emitRiskAssessment({ tool, agent: agent || undefined, error: 'scorer-error', suggested_action: 'allow' }, sessionId);
332
+ emitRiskAssessment({
333
+ event_id: randomUUID(),
334
+ tool_name: tool,
335
+ risk_score: 0,
336
+ suggested_action: 'allow',
337
+ reasons: [],
338
+ agent: agent || undefined,
339
+ }, sessionId);
329
340
  process.stdout.write(JSON.stringify(ALLOW));
330
341
  return;
331
342
  }
@@ -337,13 +348,12 @@ async function main() {
337
348
  // distribution. Best-effort.
338
349
  emitRiskAssessment(
339
350
  {
340
- tool,
341
- agent: agent || undefined,
342
- score: assessment.score,
351
+ event_id: randomUUID(),
352
+ tool_name: tool,
353
+ risk_score: assessment.score,
343
354
  suggested_action: action,
344
- reasons: assessment.reasons,
345
- breakdown: assessment.breakdown,
346
- paths: assessment.breakdown && assessment.breakdown.paths,
355
+ reasons: Array.isArray(assessment.reasons) ? assessment.reasons : [],
356
+ agent: agent || undefined,
347
357
  },
348
358
  sessionId,
349
359
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hegemonart/get-design-done",
3
- "version": "1.56.0",
3
+ "version": "1.57.1",
4
4
  "description": "A design-quality pipeline for AI coding agents: brief, plan, implement, and verify UI work against your design system.",
5
5
  "author": "Hegemon",
6
6
  "homepage": "https://github.com/hegemonart/get-design-done",
@@ -9,7 +9,7 @@ is a `composes_with` edge (the source calls the target as sub-orchestration); a
9
9
  a `next_skills` edge (a pipeline hint for what runs next). Stage grouping is best-effort and
10
10
  inferred from the skill name; skills with no stage keyword fall under Utility.
11
11
 
12
- Skills: 93. Composition edges: 0 composes_with, 6 next_skills.
12
+ Skills: 94. Composition edges: 0 composes_with, 6 next_skills.
13
13
 
14
14
  ```mermaid
15
15
  flowchart TD
@@ -108,6 +108,7 @@ flowchart TD
108
108
  n_settings["settings"]
109
109
  n_ship["ship"]
110
110
  n_skill_manifest["skill-manifest"]
111
+ n_state["state"]
111
112
  n_stats["stats"]
112
113
  n_style["style"]
113
114
  n_synthesize["synthesize"]
@@ -124,14 +124,32 @@ function reindex(projectRoot) {
124
124
  db.close();
125
125
  }
126
126
 
127
+ /**
128
+ * Re-wrap each whitespace/OR-delimited term in FTS5 double-quote escaping so
129
+ * that tokens containing `.`, `/`, `-`, or other punctuation that the trigram
130
+ * tokenizer treats as illegal bare-term characters are accepted as quoted
131
+ * phrase queries. This matches the pattern used in instinct-store.cjs.
132
+ *
133
+ * Examples:
134
+ * "heuristics.md OR reference/heuristics.md"
135
+ * -> '"heuristics.md" OR "reference/heuristics.md"'
136
+ * "color tokens"
137
+ * -> '"color" OR "tokens"'
138
+ */
139
+ function _quoteFts5Query(rawQuery) {
140
+ const terms = rawQuery.split(/\s+OR\s+|\s+/).filter(Boolean);
141
+ return terms.map(t => '"' + t.replace(/"/g, '""') + '"').join(' OR ');
142
+ }
143
+
127
144
  function _searchFts5(query, projectRoot, limit) {
128
145
  const dbPath = _dbPath(projectRoot);
129
146
  if (!fs.existsSync(dbPath)) reindex(projectRoot);
130
147
  const db = new Database(dbPath, { readonly: true });
131
148
  try {
149
+ const matchExpr = _quoteFts5Query(query);
132
150
  const rows = db.prepare(
133
151
  `SELECT file, line, text FROM docs WHERE docs MATCH ? ORDER BY rank LIMIT ?`
134
- ).all(query, limit);
152
+ ).all(matchExpr, limit);
135
153
  return rows.map(r => ({ file: r.file, line: r.line, text: r.text }));
136
154
  } finally {
137
155
  db.close();
@@ -203,4 +221,4 @@ function search(query, projectRoot, opts = {}) {
203
221
  return _searchNode(query, projectRoot, limit);
204
222
  }
205
223
 
206
- module.exports = { search, reindex, backendName };
224
+ module.exports = { search, reindex, backendName, _quoteFts5Query };
@@ -542,6 +542,14 @@
542
542
  "tools": "Read, Grep, Glob, Bash, Write, Task",
543
543
  "disable_model_invocation": true
544
544
  },
545
+ {
546
+ "name": "state",
547
+ "description": "Query, recover, or roll back the Phase 57 SQLite state backbone. Use when you need to inspect the decisions/blockers/plans tables with a raw SELECT, rebuild a corrupt state.sqlite from the markdown STATE.md, or revert to the markdown-only source of truth by removing state.sqlite. Activates for requests involving querying the SQLite state database, recovering from SQLite corruption, or reverting the migration (demigrate).",
548
+ "argument_hint": "<query \"<sql>\" | recover | demigrate>",
549
+ "user_invocable": true,
550
+ "tools": "Read, Bash, Grep, Glob",
551
+ "registered_in_phase": "57"
552
+ },
545
553
  {
546
554
  "name": "stats",
547
555
  "description": "Cycle stats - decisions made, tasks completed, commits, timeline, git metrics.",