@hegemonart/get-design-done 1.55.0 → 1.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +90 -0
  4. package/README.md +6 -0
  5. package/SKILL.md +2 -0
  6. package/agents/design-fixer.md +16 -0
  7. package/dist/claude-code/.claude/skills/override/SKILL.md +86 -0
  8. package/dist/claude-code/.claude/skills/state/SKILL.md +106 -0
  9. package/hooks/gdd-decision-injector.js +58 -0
  10. package/hooks/gdd-fact-force.js +434 -0
  11. package/hooks/gdd-risk-gate.js +406 -0
  12. package/hooks/hooks.json +18 -0
  13. package/package.json +1 -1
  14. package/reference/schemas/events.schema.json +61 -1
  15. package/reference/skill-graph.md +3 -1
  16. package/scripts/lib/manifest/skills.json +16 -0
  17. package/scripts/lib/risk/calibration.cjs +385 -0
  18. package/scripts/lib/risk/compute-risk.cjs +229 -0
  19. package/scripts/lib/risk/consumers.cjs +211 -0
  20. package/scripts/lib/risk/override.cjs +87 -0
  21. package/scripts/lib/risk/route.cjs +59 -0
  22. package/scripts/lib/risk/tables.cjs +221 -0
  23. package/scripts/lib/state/migrate-to-sqlite.cjs +664 -0
  24. package/scripts/lib/state/query-surface.cjs +391 -0
  25. package/scripts/lib/state/render-markdown.cjs +717 -0
  26. package/scripts/lib/state/state-backend.cjs +345 -0
  27. package/scripts/lib/state/state-store.cjs +735 -0
  28. package/sdk/cli/index.js +193 -96
  29. package/sdk/dashboard/data/source.cjs +44 -5
  30. package/sdk/mcp/gdd-state/server.js +127 -30
  31. package/sdk/mcp/gdd-state/tools/get.ts +8 -0
  32. package/sdk/state/index.ts +267 -13
  33. package/sdk/state/lockfile.ts +48 -0
  34. package/sdk/state/schema.sql +218 -0
  35. package/skills/override/SKILL.md +86 -0
  36. package/skills/state/SKILL.md +106 -0
@@ -58,6 +58,10 @@ function tryRequire(relPath) {
58
58
  const designContextQuery = tryRequire('scripts/lib/design-context-query.cjs');
59
59
  const eventChain = tryRequire('scripts/lib/event-chain.cjs');
60
60
  const healthMirror = tryRequire('scripts/lib/health-mirror/index.cjs');
61
+ // Phase 57 (Round 3-E): state-store provides backendName() so the dashboard
62
+ // model can surface whether it read from SQLite or markdown. Soft-loaded so
63
+ // a missing module degrades without crashing. Never throws.
64
+ const stateStore = tryRequire('scripts/lib/state/state-store.cjs');
61
65
 
62
66
  // ---------------------------------------------------------------------------
63
67
  // .ts shared libs — dynamic import(pathToFileURL), memoized.
@@ -211,23 +215,54 @@ function scrapeEventsFile(eventsPath) {
211
215
 
212
216
  /**
213
217
  * Load STATE.md-derived fields: status, phase(stage), cycle, decisions[],
214
- * blockers[]. Tries the typed sdk/state read() first, then the file scrape.
218
+ * blockers[], backend. Tries the typed sdk/state read() first (which is
219
+ * already migration-active-aware for Phase 57: when BACKEND==='sqlite' and
220
+ * a sibling state.sqlite exists, the dual-write path ensures STATE.md is
221
+ * byte-equal with SQLite so read() returns the canonical view), then the
222
+ * file-scrape fallback. Never throws.
223
+ *
224
+ * The `backend` field reflects the active state-store backend:
225
+ * 'sqlite' — better-sqlite3 + FTS5 available and migration active
226
+ * 'markdown' — markdown floor (the universal default and CI surface)
215
227
  *
216
228
  * @param {string} root
217
229
  * @param {string[]} degraded
218
230
  * @returns {Promise<{status:string|null, phase:string|null, cycle:string|null,
219
- * decisions:Array, blockers:Array}>}
231
+ * decisions:Array, blockers:Array, backend:'sqlite'|'markdown'}>}
220
232
  */
221
233
  async function loadState(root, degraded) {
222
234
  const statePath = path.join(root, '.design', 'STATE.md');
223
- const empty = { status: null, phase: null, cycle: null, decisions: [], blockers: [] };
235
+ const empty = {
236
+ status: null, phase: null, cycle: null,
237
+ decisions: [], blockers: [],
238
+ backend: /** @type {'sqlite'|'markdown'} */ ('markdown'),
239
+ };
240
+
241
+ // Determine the active backend for this specific state path (Phase 57 R3-E).
242
+ // The migration-active gate is:
243
+ // BACKEND==='sqlite' AND existsSync(<statePath-sibling>/state.sqlite)
244
+ //
245
+ // We use state-store.backendName() to check the global probe result, then
246
+ // confirm by checking whether a sibling state.sqlite exists next to STATE.md.
247
+ // This mirrors the migrationActive() logic in sdk/state/index.ts exactly.
248
+ const globalBackend =
249
+ (stateStore && typeof stateStore.backendName === 'function')
250
+ ? /** @type {'sqlite'|'markdown'} */ (stateStore.backendName())
251
+ : 'markdown';
252
+ const sqliteSibling = path.join(root, '.design', 'state.sqlite');
253
+ const activeBackend = /** @type {'sqlite'|'markdown'} */ (
254
+ globalBackend === 'sqlite' && fs.existsSync(sqliteSibling) ? 'sqlite' : 'markdown'
255
+ );
224
256
 
225
257
  if (!fs.existsSync(statePath)) {
226
258
  degraded.push('state: .design/STATE.md not found');
227
- return empty;
259
+ return { ...empty, backend: activeBackend };
228
260
  }
229
261
 
230
262
  // 1) Typed lib read() — the in-process shared surface (R1).
263
+ // Phase 57: sdk/state read() is already migration-active-aware; when
264
+ // BACKEND==='sqlite' and state.sqlite sibling exists, STATE.md is kept
265
+ // byte-equal by the dual-write path, so no separate SQLite read needed.
231
266
  try {
232
267
  const stateMod = await importState();
233
268
  if (stateMod && typeof stateMod.read === 'function') {
@@ -239,6 +274,7 @@ async function loadState(root, degraded) {
239
274
  cycle: (parsed.frontmatter && parsed.frontmatter.cycle) || null,
240
275
  decisions: Array.isArray(parsed.decisions) ? parsed.decisions : [],
241
276
  blockers: Array.isArray(parsed.blockers) ? parsed.blockers : [],
277
+ backend: activeBackend,
242
278
  };
243
279
  }
244
280
  degraded.push('state: sdk/state import unavailable — using file scrape');
@@ -246,7 +282,7 @@ async function loadState(root, degraded) {
246
282
  degraded.push(`state: typed read failed (${errMsg(err)}) — using file scrape`);
247
283
  }
248
284
 
249
- // 2) File-scrape fallback.
285
+ // 2) File-scrape fallback (ultimate fallback; never throws).
250
286
  const scraped = scrapeStateFile(statePath);
251
287
  if (scraped) {
252
288
  return {
@@ -255,6 +291,7 @@ async function loadState(root, degraded) {
255
291
  cycle: scraped.cycle,
256
292
  decisions: scraped.decisions,
257
293
  blockers: scraped.blockers,
294
+ backend: 'markdown',
258
295
  };
259
296
  }
260
297
  degraded.push('state: scrape fallback failed');
@@ -508,6 +545,7 @@ function errMsg(err) {
508
545
  * sessions: Array,
509
546
  * degraded: string[],
510
547
  * root: string,
548
+ * backend: 'sqlite'|'markdown',
511
549
  * }>}
512
550
  */
513
551
  async function loadDashboardModel(opts = {}) {
@@ -564,6 +602,7 @@ async function loadDashboardModel(opts = {}) {
564
602
  sessions,
565
603
  degraded,
566
604
  root,
605
+ backend: stateRes.backend,
567
606
  };
568
607
  }
569
608
 
@@ -36,7 +36,7 @@ __export(server_exports, {
36
36
  });
37
37
  module.exports = __toCommonJS(server_exports);
38
38
  var import_node_fs5 = require("node:fs");
39
- var import_node_path3 = require("node:path");
39
+ var import_node_path4 = require("node:path");
40
40
  var import_server = require("@modelcontextprotocol/sdk/server/index.js");
41
41
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
42
42
  var import_types8 = require("@modelcontextprotocol/sdk/types.js");
@@ -196,6 +196,7 @@ __export(get_exports, {
196
196
 
197
197
  // sdk/state/index.ts
198
198
  var import_node_fs2 = require("node:fs");
199
+ var import_node_path = require("node:path");
199
200
 
200
201
  // sdk/state/lockfile.ts
201
202
  var import_node_fs = require("node:fs");
@@ -349,7 +350,10 @@ function getErrnoCode(err) {
349
350
  return void 0;
350
351
  }
351
352
  function sleep(ms) {
352
- return new Promise((resolve3) => setTimeout(resolve3, ms));
353
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
354
+ }
355
+ async function acquireSqliteLock(sqlitePath, opts = {}) {
356
+ return acquire(sqlitePath, opts);
353
357
  }
354
358
 
355
359
  // sdk/state/parser.ts
@@ -1727,11 +1731,64 @@ function gateFor(from, to) {
1727
1731
  }
1728
1732
 
1729
1733
  // sdk/state/index.ts
1734
+ function _findPackageRoot(startDir) {
1735
+ let dir = (0, import_node_path.resolve)(startDir);
1736
+ let firstWithPkg = null;
1737
+ for (let i = 0; i < 12; i++) {
1738
+ const pkgPath = (0, import_node_path.join)(dir, "package.json");
1739
+ if ((0, import_node_fs2.existsSync)(pkgPath)) {
1740
+ try {
1741
+ const pkg = require(pkgPath);
1742
+ if (firstWithPkg === null) firstWithPkg = dir;
1743
+ if (pkg.name === "@hegemonart/get-design-done") return dir;
1744
+ } catch {
1745
+ if (firstWithPkg === null) firstWithPkg = dir;
1746
+ }
1747
+ }
1748
+ const parent = (0, import_node_path.dirname)(dir);
1749
+ if (parent === dir) break;
1750
+ dir = parent;
1751
+ }
1752
+ return firstWithPkg;
1753
+ }
1754
+ var _backendCache = null;
1755
+ function _loadBackend() {
1756
+ if (_backendCache !== null) return _backendCache === false ? null : _backendCache;
1757
+ try {
1758
+ const pkgRoot = _findPackageRoot(__dirname);
1759
+ if (pkgRoot === null) {
1760
+ _backendCache = false;
1761
+ return null;
1762
+ }
1763
+ const backendPath = (0, import_node_path.join)(pkgRoot, "scripts", "lib", "state", "state-backend.cjs");
1764
+ if (!(0, import_node_fs2.existsSync)(backendPath)) {
1765
+ _backendCache = false;
1766
+ return null;
1767
+ }
1768
+ _backendCache = require(backendPath);
1769
+ return _backendCache;
1770
+ } catch {
1771
+ _backendCache = false;
1772
+ return null;
1773
+ }
1774
+ }
1775
+ function migrationActive(statePath) {
1776
+ const backend = _loadBackend();
1777
+ if (backend === null || backend.BACKEND !== "sqlite") return false;
1778
+ const sqliteSibling = (0, import_node_path.join)((0, import_node_path.dirname)(statePath), "state.sqlite");
1779
+ return (0, import_node_fs2.existsSync)(sqliteSibling);
1780
+ }
1730
1781
  async function read(path2) {
1731
1782
  const raw = (0, import_node_fs2.readFileSync)(path2, "utf8");
1732
1783
  return parse(raw).state;
1733
1784
  }
1734
1785
  async function mutate(path2, fn) {
1786
+ if (migrationActive(path2)) {
1787
+ return _mutateSqliteActive(path2, fn);
1788
+ }
1789
+ return _mutateMarkdown(path2, fn);
1790
+ }
1791
+ async function _mutateMarkdown(path2, fn) {
1735
1792
  const release = await acquire(path2);
1736
1793
  const tmpPath = `${path2}.tmp`;
1737
1794
  try {
@@ -1768,6 +1825,46 @@ async function mutate(path2, fn) {
1768
1825
  await release();
1769
1826
  }
1770
1827
  }
1828
+ async function _mutateSqliteActive(path2, fn) {
1829
+ const sqlitePath = (0, import_node_path.join)((0, import_node_path.dirname)(path2), "state.sqlite");
1830
+ const releaseSqliteLock = await acquireSqliteLock(sqlitePath);
1831
+ const release = await acquire(path2);
1832
+ const tmpPath = `${path2}.tmp`;
1833
+ try {
1834
+ const raw = (0, import_node_fs2.readFileSync)(path2, "utf8");
1835
+ const { state, raw_bodies, raw_frontmatter, block_gaps, line_ending } = parse(raw);
1836
+ const clone = structuredClone(state);
1837
+ const next = fn(clone);
1838
+ const out = serialize(next, {
1839
+ raw_frontmatter,
1840
+ raw_bodies,
1841
+ block_gaps,
1842
+ line_ending
1843
+ });
1844
+ (0, import_node_fs2.writeFileSync)(tmpPath, out, "utf8");
1845
+ try {
1846
+ (0, import_node_fs2.renameSync)(tmpPath, path2);
1847
+ } catch (err) {
1848
+ const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
1849
+ if (code === "EPERM" || code === "EBUSY") {
1850
+ await new Promise((r) => setTimeout(r, 50));
1851
+ (0, import_node_fs2.renameSync)(tmpPath, path2);
1852
+ } else {
1853
+ throw err;
1854
+ }
1855
+ }
1856
+ return next;
1857
+ } catch (err) {
1858
+ try {
1859
+ if ((0, import_node_fs2.existsSync)(tmpPath)) (0, import_node_fs2.unlinkSync)(tmpPath);
1860
+ } catch {
1861
+ }
1862
+ throw err;
1863
+ } finally {
1864
+ await release();
1865
+ await releaseSqliteLock();
1866
+ }
1867
+ }
1771
1868
  async function transition(path2, toStage) {
1772
1869
  const beforeMutate = await read(path2);
1773
1870
  const from = beforeMutate.position.stage;
@@ -1798,7 +1895,7 @@ async function transition(path2, toStage) {
1798
1895
  }
1799
1896
 
1800
1897
  // sdk/mcp/gdd-state/tools/shared.ts
1801
- var import_node_path2 = __toESM(require("node:path"));
1898
+ var import_node_path3 = __toESM(require("node:path"));
1802
1899
  var import_node_fs4 = require("node:fs");
1803
1900
  var import_node_module2 = require("node:module");
1804
1901
 
@@ -1848,13 +1945,13 @@ var EventBus = class extends import_node_events.EventEmitter {
1848
1945
 
1849
1946
  // sdk/event-stream/writer.ts
1850
1947
  var import_node_fs3 = require("node:fs");
1851
- var import_node_path = require("node:path");
1948
+ var import_node_path2 = require("node:path");
1852
1949
  var import_node_module = require("node:module");
1853
1950
  function _findRepoRoot() {
1854
1951
  let dir = process.cwd();
1855
1952
  for (let i = 0; i < 8; i++) {
1856
- if ((0, import_node_fs3.existsSync)((0, import_node_path.join)(dir, "package.json"))) return dir;
1857
- const parent = (0, import_node_path.dirname)(dir);
1953
+ if ((0, import_node_fs3.existsSync)((0, import_node_path2.join)(dir, "package.json"))) return dir;
1954
+ const parent = (0, import_node_path2.dirname)(dir);
1858
1955
  if (parent === dir) break;
1859
1956
  dir = parent;
1860
1957
  }
@@ -1863,16 +1960,16 @@ function _findRepoRoot() {
1863
1960
  var _redact;
1864
1961
  try {
1865
1962
  const _root = _findRepoRoot();
1866
- const _candidate = (0, import_node_path.resolve)(_root, "scripts/lib/redact.cjs");
1963
+ const _candidate = (0, import_node_path2.resolve)(_root, "scripts/lib/redact.cjs");
1867
1964
  if ((0, import_node_fs3.existsSync)(_candidate)) {
1868
- const _redactRequire = (0, import_node_module.createRequire)((0, import_node_path.join)(_root, "package.json"));
1965
+ const _redactRequire = (0, import_node_module.createRequire)((0, import_node_path2.join)(_root, "package.json"));
1869
1966
  const _mod = _redactRequire(_candidate);
1870
1967
  _redact = _mod.redact;
1871
1968
  } else {
1872
- const _altRoot = (0, import_node_path.resolve)(_root, "..", "..");
1873
- const _altCandidate = (0, import_node_path.resolve)(_altRoot, "scripts/lib/redact.cjs");
1969
+ const _altRoot = (0, import_node_path2.resolve)(_root, "..", "..");
1970
+ const _altCandidate = (0, import_node_path2.resolve)(_altRoot, "scripts/lib/redact.cjs");
1874
1971
  if ((0, import_node_fs3.existsSync)(_altCandidate)) {
1875
- const _altRequire = (0, import_node_module.createRequire)((0, import_node_path.join)(_altRoot, "package.json"));
1972
+ const _altRequire = (0, import_node_module.createRequire)((0, import_node_path2.join)(_altRoot, "package.json"));
1876
1973
  const _altMod = _altRequire(_altCandidate);
1877
1974
  _redact = _altMod.redact;
1878
1975
  } else {
@@ -1898,7 +1995,7 @@ var EventWriter = class {
1898
1995
  directoryEnsured = false;
1899
1996
  constructor(opts = {}) {
1900
1997
  const rawPath = opts.path ?? DEFAULT_EVENTS_PATH;
1901
- this.path = (0, import_node_path.isAbsolute)(rawPath) ? rawPath : (0, import_node_path.resolve)(process.cwd(), rawPath);
1998
+ this.path = (0, import_node_path2.isAbsolute)(rawPath) ? rawPath : (0, import_node_path2.resolve)(process.cwd(), rawPath);
1902
1999
  this.maxLineBytes = opts.maxLineBytes ?? DEFAULT_MAX_LINE_BYTES;
1903
2000
  }
1904
2001
  /**
@@ -1959,7 +2056,7 @@ var EventWriter = class {
1959
2056
  */
1960
2057
  ensureDirectory() {
1961
2058
  if (this.directoryEnsured) return;
1962
- (0, import_node_fs3.mkdirSync)((0, import_node_path.dirname)(this.path), { recursive: true });
2059
+ (0, import_node_fs3.mkdirSync)((0, import_node_path2.dirname)(this.path), { recursive: true });
1963
2060
  this.directoryEnsured = true;
1964
2061
  }
1965
2062
  };
@@ -2017,8 +2114,8 @@ function getSessionId() {
2017
2114
  function _findRepoRoot2() {
2018
2115
  let dir = process.cwd();
2019
2116
  for (let i = 0; i < 8; i++) {
2020
- if ((0, import_node_fs4.existsSync)(import_node_path2.default.join(dir, "package.json"))) return dir;
2021
- const parent = import_node_path2.default.dirname(dir);
2117
+ if ((0, import_node_fs4.existsSync)(import_node_path3.default.join(dir, "package.json"))) return dir;
2118
+ const parent = import_node_path3.default.dirname(dir);
2022
2119
  if (parent === dir) break;
2023
2120
  dir = parent;
2024
2121
  }
@@ -2027,9 +2124,9 @@ function _findRepoRoot2() {
2027
2124
  var _worktree = (() => {
2028
2125
  try {
2029
2126
  const root = _findRepoRoot2();
2030
- const candidate = import_node_path2.default.resolve(root, "scripts/lib/worktree-resolve.cjs");
2127
+ const candidate = import_node_path3.default.resolve(root, "scripts/lib/worktree-resolve.cjs");
2031
2128
  if (!(0, import_node_fs4.existsSync)(candidate)) return null;
2032
- const req = (0, import_node_module2.createRequire)(import_node_path2.default.join(root, "package.json"));
2129
+ const req = (0, import_node_module2.createRequire)(import_node_path3.default.join(root, "package.json"));
2033
2130
  return req(candidate);
2034
2131
  } catch {
2035
2132
  return null;
@@ -2042,20 +2139,20 @@ function resolveStatePath() {
2042
2139
  try {
2043
2140
  const designRoot = _worktree.resolveDesignRoot();
2044
2141
  if (_worktree.isWorktree()) {
2045
- _worktree.noticeOnce(import_node_path2.default.dirname(designRoot));
2142
+ _worktree.noticeOnce(import_node_path3.default.dirname(designRoot));
2046
2143
  }
2047
- return import_node_path2.default.join(designRoot, "STATE.md");
2144
+ return import_node_path3.default.join(designRoot, "STATE.md");
2048
2145
  } catch {
2049
2146
  }
2050
2147
  }
2051
2148
  return ".design/STATE.md";
2052
2149
  }
2053
- if (import_node_path2.default.isAbsolute(override)) {
2054
- return import_node_path2.default.resolve(override);
2150
+ if (import_node_path3.default.isAbsolute(override)) {
2151
+ return import_node_path3.default.resolve(override);
2055
2152
  }
2056
- const root = import_node_path2.default.resolve(process.cwd());
2057
- const resolved = import_node_path2.default.resolve(root, override);
2058
- const withSep = root.endsWith(import_node_path2.default.sep) ? root : root + import_node_path2.default.sep;
2153
+ const root = import_node_path3.default.resolve(process.cwd());
2154
+ const resolved = import_node_path3.default.resolve(root, override);
2155
+ const withSep = root.endsWith(import_node_path3.default.sep) ? root : root + import_node_path3.default.sep;
2059
2156
  if (resolved !== root && !resolved.startsWith(withSep)) {
2060
2157
  throwValidation(
2061
2158
  "STATE_PATH_ESCAPE",
@@ -2763,16 +2860,16 @@ var TOOL_COUNT = TOOL_MODULES.length;
2763
2860
  var SERVER_NAME = "gdd-state";
2764
2861
  var SERVER_VERSION = "1.20.0";
2765
2862
  function here() {
2766
- const expectedRel = (0, import_node_path3.join)("sdk", "mcp", "gdd-state");
2863
+ const expectedRel = (0, import_node_path4.join)("sdk", "mcp", "gdd-state");
2767
2864
  const entry = process.argv[1];
2768
2865
  if (typeof entry === "string" && entry.length > 0) {
2769
- const entryDir = (0, import_node_path3.dirname)((0, import_node_path3.resolve)(entry));
2770
- if ((0, import_node_fs5.existsSync)((0, import_node_path3.join)(entryDir, "tools", "index.ts"))) {
2866
+ const entryDir = (0, import_node_path4.dirname)((0, import_node_path4.resolve)(entry));
2867
+ if ((0, import_node_fs5.existsSync)((0, import_node_path4.join)(entryDir, "tools", "index.ts"))) {
2771
2868
  return entryDir;
2772
2869
  }
2773
2870
  }
2774
- const candidate = (0, import_node_path3.resolve)(process.cwd(), expectedRel);
2775
- if ((0, import_node_fs5.existsSync)((0, import_node_path3.join)(candidate, "tools", "index.ts"))) {
2871
+ const candidate = (0, import_node_path4.resolve)(process.cwd(), expectedRel);
2872
+ if ((0, import_node_fs5.existsSync)((0, import_node_path4.join)(candidate, "tools", "index.ts"))) {
2776
2873
  return candidate;
2777
2874
  }
2778
2875
  return candidate;
@@ -2780,7 +2877,7 @@ function here() {
2780
2877
  function loadTools() {
2781
2878
  const baseDir = here();
2782
2879
  return TOOL_MODULES.map((m) => {
2783
- const absPath = (0, import_node_path3.join)(baseDir, "tools", m.schemaPath);
2880
+ const absPath = (0, import_node_path4.join)(baseDir, "tools", m.schemaPath);
2784
2881
  const raw = (0, import_node_fs5.readFileSync)(absPath, "utf8");
2785
2882
  const parsed = JSON.parse(raw);
2786
2883
  const rawInput = parsed.properties?.input;
@@ -5,6 +5,14 @@
5
5
  // event. Optionally projects a subset of fields when `input.fields` is
6
6
  // provided; unknown field names are silently ignored so callers can pass
7
7
  // a broad list without pre-flight knowledge of ParsedState shape.
8
+ //
9
+ // Phase 57 (Round 3-E): this tool already benefits from the SQLite read
10
+ // path transparently. `sdk/state/index.ts` `read()` delegates through the
11
+ // migration-active gate: when BACKEND==='sqlite' and a sibling state.sqlite
12
+ // exists the dual-write path keeps STATE.md byte-equal with SQLite state,
13
+ // so reading the on-disk STATE.md returns the canonical view regardless of
14
+ // which backend is active. No behavioral change is required here and the
15
+ // tool input/output SCHEMA IS UNCHANGED (no mcp-tools-manifest hash drift).
8
16
 
9
17
  import { read } from '../../../state/index.ts';
10
18
  import type { ParsedState } from '../../../state/types.ts';