gsd-pi 2.74.0-dev.2b524c3 → 2.74.0-dev.b741afb

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 (159) hide show
  1. package/dist/cli.js +85 -0
  2. package/dist/headless-query.js +4 -1
  3. package/dist/help-text.js +23 -0
  4. package/dist/resources/extensions/gsd/auto/detect-stuck.js +11 -4
  5. package/dist/resources/extensions/gsd/auto/phases.js +45 -1
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +52 -56
  7. package/dist/resources/extensions/gsd/auto-prompts.js +12 -0
  8. package/dist/resources/extensions/gsd/auto.js +8 -2
  9. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +21 -8
  10. package/dist/resources/extensions/gsd/commands/catalog.js +26 -1
  11. package/dist/resources/extensions/gsd/commands/handlers/ops.js +20 -0
  12. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +68 -9
  13. package/dist/resources/extensions/gsd/commands-add-tests.js +111 -0
  14. package/dist/resources/extensions/gsd/commands-backlog.js +140 -0
  15. package/dist/resources/extensions/gsd/commands-do.js +79 -0
  16. package/dist/resources/extensions/gsd/commands-maintenance.js +6 -6
  17. package/dist/resources/extensions/gsd/commands-pr-branch.js +180 -0
  18. package/dist/resources/extensions/gsd/commands-session-report.js +82 -0
  19. package/dist/resources/extensions/gsd/commands-ship.js +187 -0
  20. package/dist/resources/extensions/gsd/db-writer.js +3 -5
  21. package/dist/resources/extensions/gsd/graph-context.js +66 -0
  22. package/dist/resources/extensions/gsd/gsd-db.js +321 -0
  23. package/dist/resources/extensions/gsd/index.js +15 -2
  24. package/dist/resources/extensions/gsd/md-importer.js +3 -4
  25. package/dist/resources/extensions/gsd/memory-store.js +19 -51
  26. package/dist/resources/extensions/gsd/milestone-validation-gates.js +13 -12
  27. package/dist/resources/extensions/gsd/native-git-bridge.js +7 -4
  28. package/dist/resources/extensions/gsd/prompts/add-tests.md +35 -0
  29. package/dist/resources/extensions/gsd/state.js +5 -1
  30. package/dist/resources/extensions/gsd/tools/complete-slice.js +15 -0
  31. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +3 -14
  32. package/dist/resources/extensions/gsd/triage-resolution.js +2 -5
  33. package/dist/resources/extensions/gsd/workflow-manifest.js +8 -69
  34. package/dist/resources/extensions/gsd/workflow-migration.js +21 -22
  35. package/dist/resources/extensions/gsd/workflow-projections.js +4 -1
  36. package/dist/resources/extensions/gsd/workflow-reconcile.js +14 -11
  37. package/dist/tsconfig.extensions.tsbuildinfo +1 -0
  38. package/dist/web/standalone/.next/BUILD_ID +1 -1
  39. package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
  40. package/dist/web/standalone/.next/build-manifest.json +2 -2
  41. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  42. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/index.html +1 -1
  59. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
  66. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  68. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  69. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  70. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  71. package/package.json +3 -2
  72. package/packages/daemon/package.json +2 -2
  73. package/packages/mcp-server/dist/index.d.ts +3 -0
  74. package/packages/mcp-server/dist/index.d.ts.map +1 -1
  75. package/packages/mcp-server/dist/index.js +3 -0
  76. package/packages/mcp-server/dist/index.js.map +1 -1
  77. package/packages/mcp-server/dist/readers/graph.d.ts +87 -0
  78. package/packages/mcp-server/dist/readers/graph.d.ts.map +1 -0
  79. package/packages/mcp-server/dist/readers/graph.js +548 -0
  80. package/packages/mcp-server/dist/readers/graph.js.map +1 -0
  81. package/packages/mcp-server/dist/readers/index.d.ts +2 -0
  82. package/packages/mcp-server/dist/readers/index.d.ts.map +1 -1
  83. package/packages/mcp-server/dist/readers/index.js +1 -0
  84. package/packages/mcp-server/dist/readers/index.js.map +1 -1
  85. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  86. package/packages/mcp-server/dist/server.js +65 -0
  87. package/packages/mcp-server/dist/server.js.map +1 -1
  88. package/packages/mcp-server/package.json +2 -2
  89. package/packages/mcp-server/src/index.ts +15 -0
  90. package/packages/mcp-server/src/readers/graph.test.ts +426 -0
  91. package/packages/mcp-server/src/readers/graph.ts +708 -0
  92. package/packages/mcp-server/src/readers/index.ts +12 -0
  93. package/packages/mcp-server/src/server.ts +83 -0
  94. package/packages/mcp-server/tsconfig.json +1 -0
  95. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -0
  96. package/packages/native/package.json +2 -2
  97. package/packages/native/tsconfig.tsbuildinfo +1 -0
  98. package/packages/pi-agent-core/package.json +1 -1
  99. package/packages/pi-agent-core/tsconfig.json +1 -0
  100. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -0
  101. package/packages/pi-ai/package.json +1 -1
  102. package/packages/pi-ai/tsconfig.json +1 -0
  103. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -0
  104. package/packages/pi-coding-agent/tsconfig.json +1 -0
  105. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -0
  106. package/packages/pi-tui/package.json +1 -1
  107. package/packages/pi-tui/tsconfig.json +1 -0
  108. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -0
  109. package/packages/rpc-client/package.json +1 -1
  110. package/packages/rpc-client/tsconfig.json +1 -0
  111. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -0
  112. package/src/resources/extensions/gsd/auto/detect-stuck.ts +12 -4
  113. package/src/resources/extensions/gsd/auto/loop-deps.ts +6 -0
  114. package/src/resources/extensions/gsd/auto/phases.ts +68 -1
  115. package/src/resources/extensions/gsd/auto-post-unit.ts +60 -57
  116. package/src/resources/extensions/gsd/auto-prompts.ts +13 -0
  117. package/src/resources/extensions/gsd/auto.ts +7 -0
  118. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +24 -8
  119. package/src/resources/extensions/gsd/commands/catalog.ts +26 -1
  120. package/src/resources/extensions/gsd/commands/handlers/ops.ts +20 -0
  121. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +74 -9
  122. package/src/resources/extensions/gsd/commands-add-tests.ts +137 -0
  123. package/src/resources/extensions/gsd/commands-backlog.ts +182 -0
  124. package/src/resources/extensions/gsd/commands-do.ts +109 -0
  125. package/src/resources/extensions/gsd/commands-maintenance.ts +6 -6
  126. package/src/resources/extensions/gsd/commands-pr-branch.ts +234 -0
  127. package/src/resources/extensions/gsd/commands-session-report.ts +101 -0
  128. package/src/resources/extensions/gsd/commands-ship.ts +219 -0
  129. package/src/resources/extensions/gsd/db-writer.ts +3 -5
  130. package/src/resources/extensions/gsd/graph-context.ts +85 -0
  131. package/src/resources/extensions/gsd/gsd-db.ts +467 -0
  132. package/src/resources/extensions/gsd/index.ts +18 -2
  133. package/src/resources/extensions/gsd/md-importer.ts +3 -5
  134. package/src/resources/extensions/gsd/memory-store.ts +31 -62
  135. package/src/resources/extensions/gsd/milestone-validation-gates.ts +13 -14
  136. package/src/resources/extensions/gsd/native-git-bridge.ts +11 -12
  137. package/src/resources/extensions/gsd/prompts/add-tests.md +35 -0
  138. package/src/resources/extensions/gsd/state.ts +9 -2
  139. package/src/resources/extensions/gsd/tests/commands-backlog.test.ts +158 -0
  140. package/src/resources/extensions/gsd/tests/commands-do.test.ts +127 -0
  141. package/src/resources/extensions/gsd/tests/commands-pr-branch.test.ts +68 -0
  142. package/src/resources/extensions/gsd/tests/commands-session-report.test.ts +82 -0
  143. package/src/resources/extensions/gsd/tests/commands-ship.test.ts +71 -0
  144. package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +14 -0
  145. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +154 -0
  146. package/src/resources/extensions/gsd/tests/graph-context.test.ts +337 -0
  147. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +68 -1
  148. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +140 -0
  149. package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +180 -0
  150. package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +223 -0
  151. package/src/resources/extensions/gsd/tools/complete-slice.ts +19 -0
  152. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +3 -11
  153. package/src/resources/extensions/gsd/triage-resolution.ts +2 -7
  154. package/src/resources/extensions/gsd/workflow-manifest.ts +9 -104
  155. package/src/resources/extensions/gsd/workflow-migration.ts +21 -29
  156. package/src/resources/extensions/gsd/workflow-projections.ts +8 -1
  157. package/src/resources/extensions/gsd/workflow-reconcile.ts +15 -15
  158. /package/dist/web/standalone/.next/static/{YzIEI9sxJy4t5xgClF08g → XnHY5eXUsTCFmNodWHetD}/_buildManifest.js +0 -0
  159. /package/dist/web/standalone/.next/static/{YzIEI9sxJy4t5xgClF08g → XnHY5eXUsTCFmNodWHetD}/_ssgManifest.js +0 -0
@@ -4,6 +4,21 @@
4
4
  //
5
5
  // Exposes a unified sync API for decisions and requirements storage.
6
6
  // Schema is initialized on first open with WAL mode for file-backed DBs.
7
+ //
8
+ // ─── Single-writer invariant ─────────────────────────────────────────────
9
+ // This file is the ONLY place in the codebase that issues write SQL
10
+ // (INSERT / UPDATE / DELETE / REPLACE / BEGIN-COMMIT transactions) against
11
+ // the engine database at `.gsd/gsd.db`. All other modules must call the
12
+ // typed wrappers exported here. The structural test
13
+ // `tests/single-writer-invariant.test.ts` fails CI if a new bypass appears.
14
+ //
15
+ // `_getAdapter()` is retained for read-only SELECTs in query modules
16
+ // (context-store, memory-store queries, doctor checks, projections).
17
+ // Do NOT use it for writes — add a wrapper here instead.
18
+ //
19
+ // The separate `.gsd/unit-claims.db` managed by `unit-ownership.ts` is an
20
+ // intentionally independent store for cross-worktree claim races and is
21
+ // excluded from this invariant.
7
22
  import { createRequire } from "node:module";
8
23
  import { existsSync, copyFileSync, mkdirSync, realpathSync } from "node:fs";
9
24
  import { dirname } from "node:path";
@@ -863,6 +878,50 @@ export function transaction(fn) {
863
878
  _txDepth--;
864
879
  }
865
880
  }
881
+ /**
882
+ * Wrap a block of reads in a DEFERRED transaction so that all SELECTs observe
883
+ * a consistent snapshot of the DB even if a concurrent writer commits between
884
+ * them. Use this for multi-query read flows (e.g. tool executors that query
885
+ * milestone + slices + counts and want one snapshot). Re-entrant — if already
886
+ * inside a transaction, runs fn() without starting a nested one.
887
+ */
888
+ export function readTransaction(fn) {
889
+ if (!currentDb)
890
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
891
+ if (_txDepth > 0) {
892
+ _txDepth++;
893
+ try {
894
+ return fn();
895
+ }
896
+ finally {
897
+ _txDepth--;
898
+ }
899
+ }
900
+ _txDepth++;
901
+ currentDb.exec("BEGIN DEFERRED");
902
+ try {
903
+ const result = fn();
904
+ currentDb.exec("COMMIT");
905
+ return result;
906
+ }
907
+ catch (err) {
908
+ try {
909
+ currentDb.exec("ROLLBACK");
910
+ }
911
+ catch (rollbackErr) {
912
+ // A failed ROLLBACK after a failed read is a split-brain signal —
913
+ // the transaction is in an indeterminate state. Surface it via the
914
+ // logger instead of swallowing it.
915
+ logError("db", "snapshotState ROLLBACK failed", {
916
+ error: rollbackErr.message,
917
+ });
918
+ }
919
+ throw err;
920
+ }
921
+ finally {
922
+ _txDepth--;
923
+ }
924
+ }
866
925
  export function insertDecision(d) {
867
926
  if (!currentDb)
868
927
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
@@ -2045,3 +2104,265 @@ export function getPendingGatesForTurn(milestoneId, sliceId, turn, taskId) {
2045
2104
  export function getPendingGateCountForTurn(milestoneId, sliceId, turn) {
2046
2105
  return getPendingGatesForTurn(milestoneId, sliceId, turn).length;
2047
2106
  }
2107
+ // ─── Single-writer bypass wrappers ───────────────────────────────────────
2108
+ // These wrappers exist so modules outside this file never need to call
2109
+ // `_getAdapter()` for writes. Each one is a byte-equivalent replacement for
2110
+ // a raw prepare/run previously issued from another module. Keep them
2111
+ // minimal and direct — they exist to hold SQL text in one place, not to
2112
+ // add new behavior.
2113
+ /** Delete a decision row by id. Used by db-writer.ts rollback on disk-write failure. */
2114
+ export function deleteDecisionById(id) {
2115
+ if (!currentDb)
2116
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2117
+ currentDb.prepare("DELETE FROM decisions WHERE id = :id").run({ ":id": id });
2118
+ }
2119
+ /** Delete a requirement row by id. Used by db-writer.ts rollback on disk-write failure. */
2120
+ export function deleteRequirementById(id) {
2121
+ if (!currentDb)
2122
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2123
+ currentDb.prepare("DELETE FROM requirements WHERE id = :id").run({ ":id": id });
2124
+ }
2125
+ /** Delete an artifact row by path. Used by db-writer.ts rollback on disk-write failure. */
2126
+ export function deleteArtifactByPath(path) {
2127
+ if (!currentDb)
2128
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2129
+ currentDb.prepare("DELETE FROM artifacts WHERE path = :path").run({ ":path": path });
2130
+ }
2131
+ /**
2132
+ * Drop all rows from tasks/slices/milestones in dependency order inside a
2133
+ * transaction. Used by `gsd recover` to rebuild engine state from markdown.
2134
+ */
2135
+ export function clearEngineHierarchy() {
2136
+ if (!currentDb)
2137
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2138
+ transaction(() => {
2139
+ currentDb.exec("DELETE FROM tasks");
2140
+ currentDb.exec("DELETE FROM slices");
2141
+ currentDb.exec("DELETE FROM milestones");
2142
+ });
2143
+ }
2144
+ /**
2145
+ * INSERT OR IGNORE a slice during event replay (workflow-reconcile.ts).
2146
+ * Strict insert-or-ignore semantics are required here to avoid the
2147
+ * `insertSlice` ON CONFLICT path that could downgrade an already-completed
2148
+ * slice back to 'pending'.
2149
+ */
2150
+ export function insertOrIgnoreSlice(args) {
2151
+ if (!currentDb)
2152
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2153
+ currentDb.prepare(`INSERT OR IGNORE INTO slices (milestone_id, id, title, status, created_at)
2154
+ VALUES (:mid, :sid, :title, 'pending', :ts)`).run({
2155
+ ":mid": args.milestoneId,
2156
+ ":sid": args.sliceId,
2157
+ ":title": args.title,
2158
+ ":ts": args.createdAt,
2159
+ });
2160
+ }
2161
+ /**
2162
+ * INSERT OR IGNORE a task during event replay (workflow-reconcile.ts).
2163
+ * Same rationale as `insertOrIgnoreSlice`.
2164
+ */
2165
+ export function insertOrIgnoreTask(args) {
2166
+ if (!currentDb)
2167
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2168
+ currentDb.prepare(`INSERT OR IGNORE INTO tasks (milestone_id, slice_id, id, title, status, created_at)
2169
+ VALUES (:mid, :sid, :tid, :title, 'pending', :ts)`).run({
2170
+ ":mid": args.milestoneId,
2171
+ ":sid": args.sliceId,
2172
+ ":tid": args.taskId,
2173
+ ":title": args.title,
2174
+ ":ts": args.createdAt,
2175
+ });
2176
+ }
2177
+ /**
2178
+ * Stamp the `replan_triggered_at` column on a slice. Used by triage-resolution
2179
+ * when a user capture requests a replan so the dispatcher can detect the
2180
+ * trigger via DB in addition to the on-disk REPLAN-TRIGGER.md marker.
2181
+ */
2182
+ export function setSliceReplanTriggeredAt(milestoneId, sliceId, ts) {
2183
+ if (!currentDb)
2184
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2185
+ currentDb.prepare("UPDATE slices SET replan_triggered_at = :ts WHERE milestone_id = :mid AND id = :sid").run({ ":ts": ts, ":mid": milestoneId, ":sid": sliceId });
2186
+ }
2187
+ /**
2188
+ * INSERT OR REPLACE a quality_gates row. Used by milestone-validation-gates.ts
2189
+ * to persist milestone-level (MV*) gate outcomes after validate-milestone runs.
2190
+ */
2191
+ export function upsertQualityGate(g) {
2192
+ if (!currentDb)
2193
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2194
+ currentDb.prepare(`INSERT OR REPLACE INTO quality_gates
2195
+ (milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
2196
+ VALUES (:mid, :sid, :gid, :scope, :tid, :status, :verdict, :rationale, :findings, :evaluated_at)`).run({
2197
+ ":mid": g.milestoneId,
2198
+ ":sid": g.sliceId,
2199
+ ":gid": g.gateId,
2200
+ ":scope": g.scope,
2201
+ ":tid": g.taskId,
2202
+ ":status": g.status,
2203
+ ":verdict": g.verdict,
2204
+ ":rationale": g.rationale,
2205
+ ":findings": g.findings,
2206
+ ":evaluated_at": g.evaluatedAt,
2207
+ });
2208
+ }
2209
+ /**
2210
+ * Atomically replace all workflow state from a manifest. Lifted verbatim from
2211
+ * workflow-manifest.ts so the single-writer invariant holds. Only touches
2212
+ * engine tables + decisions. Does NOT modify artifacts or memories.
2213
+ */
2214
+ export function restoreManifest(manifest) {
2215
+ if (!currentDb)
2216
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2217
+ const db = currentDb;
2218
+ transaction(() => {
2219
+ // Clear engine tables (order matters for foreign-key-like consistency)
2220
+ db.exec("DELETE FROM verification_evidence");
2221
+ db.exec("DELETE FROM tasks");
2222
+ db.exec("DELETE FROM slices");
2223
+ db.exec("DELETE FROM milestones");
2224
+ db.exec("DELETE FROM decisions WHERE 1=1");
2225
+ // Restore milestones
2226
+ const msStmt = db.prepare(`INSERT INTO milestones (id, title, status, depends_on, created_at, completed_at,
2227
+ vision, success_criteria, key_risks, proof_strategy,
2228
+ verification_contract, verification_integration, verification_operational, verification_uat,
2229
+ definition_of_done, requirement_coverage, boundary_map_markdown)
2230
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
2231
+ for (const m of manifest.milestones) {
2232
+ msStmt.run(m.id, m.title, m.status, JSON.stringify(m.depends_on), m.created_at, m.completed_at, m.vision, JSON.stringify(m.success_criteria), JSON.stringify(m.key_risks), JSON.stringify(m.proof_strategy), m.verification_contract, m.verification_integration, m.verification_operational, m.verification_uat, JSON.stringify(m.definition_of_done), m.requirement_coverage, m.boundary_map_markdown);
2233
+ }
2234
+ // Restore slices
2235
+ const slStmt = db.prepare(`INSERT INTO slices (milestone_id, id, title, status, risk, depends, demo,
2236
+ created_at, completed_at, full_summary_md, full_uat_md,
2237
+ goal, success_criteria, proof_level, integration_closure, observability_impact,
2238
+ sequence, replan_triggered_at)
2239
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
2240
+ for (const s of manifest.slices) {
2241
+ slStmt.run(s.milestone_id, s.id, s.title, s.status, s.risk, JSON.stringify(s.depends), s.demo, s.created_at, s.completed_at, s.full_summary_md, s.full_uat_md, s.goal, s.success_criteria, s.proof_level, s.integration_closure, s.observability_impact, s.sequence, s.replan_triggered_at);
2242
+ }
2243
+ // Restore tasks
2244
+ const tkStmt = db.prepare(`INSERT INTO tasks (milestone_id, slice_id, id, title, status,
2245
+ one_liner, narrative, verification_result, duration, completed_at,
2246
+ blocker_discovered, deviations, known_issues, key_files, key_decisions,
2247
+ full_summary_md, description, estimate, files, verify,
2248
+ inputs, expected_output, observability_impact, sequence)
2249
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
2250
+ for (const t of manifest.tasks) {
2251
+ tkStmt.run(t.milestone_id, t.slice_id, t.id, t.title, t.status, t.one_liner, t.narrative, t.verification_result, t.duration, t.completed_at, t.blocker_discovered ? 1 : 0, t.deviations, t.known_issues, JSON.stringify(t.key_files), JSON.stringify(t.key_decisions), t.full_summary_md, t.description, t.estimate, JSON.stringify(t.files), t.verify, JSON.stringify(t.inputs), JSON.stringify(t.expected_output), t.observability_impact, t.sequence);
2252
+ }
2253
+ // Restore decisions
2254
+ const dcStmt = db.prepare(`INSERT INTO decisions (seq, id, when_context, scope, decision, choice, rationale, revisable, made_by, superseded_by)
2255
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
2256
+ for (const d of manifest.decisions) {
2257
+ dcStmt.run(d.seq, d.id, d.when_context, d.scope, d.decision, d.choice, d.rationale, d.revisable, d.made_by, d.superseded_by);
2258
+ }
2259
+ // Restore verification evidence
2260
+ const evStmt = db.prepare(`INSERT INTO verification_evidence (task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at)
2261
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
2262
+ for (const e of manifest.verification_evidence) {
2263
+ evStmt.run(e.task_id, e.slice_id, e.milestone_id, e.command, e.exit_code, e.verdict, e.duration_ms, e.created_at);
2264
+ }
2265
+ });
2266
+ }
2267
+ /**
2268
+ * Bulk delete + insert a legacy milestone hierarchy for markdown → DB migration.
2269
+ * Used by workflow-migration.ts to populate engine tables from parsed ROADMAP/PLAN
2270
+ * files. All operations run inside a single transaction.
2271
+ */
2272
+ export function bulkInsertLegacyHierarchy(payload) {
2273
+ if (!currentDb)
2274
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2275
+ const db = currentDb;
2276
+ const { milestones, slices, tasks, clearMilestoneIds, createdAt } = payload;
2277
+ if (clearMilestoneIds.length === 0)
2278
+ return;
2279
+ const placeholders = clearMilestoneIds.map(() => "?").join(",");
2280
+ transaction(() => {
2281
+ db.prepare(`DELETE FROM tasks WHERE milestone_id IN (${placeholders})`).run(...clearMilestoneIds);
2282
+ db.prepare(`DELETE FROM slices WHERE milestone_id IN (${placeholders})`).run(...clearMilestoneIds);
2283
+ db.prepare(`DELETE FROM milestones WHERE id IN (${placeholders})`).run(...clearMilestoneIds);
2284
+ const insertMilestone = db.prepare("INSERT INTO milestones (id, title, status, created_at) VALUES (?, ?, ?, ?)");
2285
+ for (const m of milestones) {
2286
+ insertMilestone.run(m.id, m.title, m.status, createdAt);
2287
+ }
2288
+ const insertSliceStmt = db.prepare("INSERT INTO slices (id, milestone_id, title, status, risk, depends, sequence, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
2289
+ for (const s of slices) {
2290
+ insertSliceStmt.run(s.id, s.milestoneId, s.title, s.status, s.risk, "[]", s.sequence, createdAt);
2291
+ }
2292
+ const insertTaskStmt = db.prepare("INSERT INTO tasks (id, slice_id, milestone_id, title, description, status, estimate, files, sequence) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
2293
+ for (const t of tasks) {
2294
+ insertTaskStmt.run(t.id, t.sliceId, t.milestoneId, t.title, "", t.status, "", "[]", t.sequence);
2295
+ }
2296
+ });
2297
+ }
2298
+ // ─── Memory store writers ────────────────────────────────────────────────
2299
+ // All memory writes go through gsd-db.ts so the single-writer invariant
2300
+ // holds. These are direct pass-throughs to the SQL previously in
2301
+ // memory-store.ts — same bindings, same behavior.
2302
+ export function insertMemoryRow(args) {
2303
+ if (!currentDb)
2304
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2305
+ currentDb.prepare(`INSERT INTO memories (id, category, content, confidence, source_unit_type, source_unit_id, created_at, updated_at)
2306
+ VALUES (:id, :category, :content, :confidence, :source_unit_type, :source_unit_id, :created_at, :updated_at)`).run({
2307
+ ":id": args.id,
2308
+ ":category": args.category,
2309
+ ":content": args.content,
2310
+ ":confidence": args.confidence,
2311
+ ":source_unit_type": args.sourceUnitType,
2312
+ ":source_unit_id": args.sourceUnitId,
2313
+ ":created_at": args.createdAt,
2314
+ ":updated_at": args.updatedAt,
2315
+ });
2316
+ }
2317
+ export function rewriteMemoryId(placeholderId, realId) {
2318
+ if (!currentDb)
2319
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2320
+ currentDb.prepare("UPDATE memories SET id = :real_id WHERE id = :placeholder").run({
2321
+ ":real_id": realId,
2322
+ ":placeholder": placeholderId,
2323
+ });
2324
+ }
2325
+ export function updateMemoryContentRow(id, content, confidence, updatedAt) {
2326
+ if (!currentDb)
2327
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2328
+ if (confidence != null) {
2329
+ currentDb.prepare("UPDATE memories SET content = :content, confidence = :confidence, updated_at = :updated_at WHERE id = :id").run({ ":content": content, ":confidence": confidence, ":updated_at": updatedAt, ":id": id });
2330
+ }
2331
+ else {
2332
+ currentDb.prepare("UPDATE memories SET content = :content, updated_at = :updated_at WHERE id = :id").run({ ":content": content, ":updated_at": updatedAt, ":id": id });
2333
+ }
2334
+ }
2335
+ export function incrementMemoryHitCount(id, updatedAt) {
2336
+ if (!currentDb)
2337
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2338
+ currentDb.prepare("UPDATE memories SET hit_count = hit_count + 1, updated_at = :updated_at WHERE id = :id").run({ ":updated_at": updatedAt, ":id": id });
2339
+ }
2340
+ export function supersedeMemoryRow(oldId, newId, updatedAt) {
2341
+ if (!currentDb)
2342
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2343
+ currentDb.prepare("UPDATE memories SET superseded_by = :new_id, updated_at = :updated_at WHERE id = :old_id").run({ ":new_id": newId, ":updated_at": updatedAt, ":old_id": oldId });
2344
+ }
2345
+ export function markMemoryUnitProcessed(unitKey, activityFile, processedAt) {
2346
+ if (!currentDb)
2347
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2348
+ currentDb.prepare(`INSERT OR IGNORE INTO memory_processed_units (unit_key, activity_file, processed_at)
2349
+ VALUES (:key, :file, :at)`).run({ ":key": unitKey, ":file": activityFile, ":at": processedAt });
2350
+ }
2351
+ export function decayMemoriesBefore(cutoffTs, now) {
2352
+ if (!currentDb)
2353
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2354
+ currentDb.prepare(`UPDATE memories
2355
+ SET confidence = MAX(0.1, confidence - 0.1), updated_at = :now
2356
+ WHERE superseded_by IS NULL AND updated_at < :cutoff AND confidence > 0.1`).run({ ":now": now, ":cutoff": cutoffTs });
2357
+ }
2358
+ export function supersedeLowestRankedMemories(limit, now) {
2359
+ if (!currentDb)
2360
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2361
+ currentDb.prepare(`UPDATE memories SET superseded_by = 'CAP_EXCEEDED', updated_at = :now
2362
+ WHERE id IN (
2363
+ SELECT id FROM memories
2364
+ WHERE superseded_by IS NULL
2365
+ ORDER BY (confidence * (1.0 + hit_count * 0.1)) ASC
2366
+ LIMIT :limit
2367
+ )`).run({ ":now": now, ":limit": limit });
2368
+ }
@@ -1,5 +1,18 @@
1
1
  export { isDepthConfirmationAnswer, isDepthVerified, isGateQuestionId, isQueuePhaseActive, setQueuePhaseActive, shouldBlockContextWrite, shouldBlockPendingGate, shouldBlockPendingGateBash, shouldBlockQueueExecution, setPendingGate, clearPendingGate, getPendingGate, } from "./bootstrap/write-gate.js";
2
2
  export default async function registerExtension(pi) {
3
- const { registerGsdExtension } = await import("./bootstrap/register-extension.js");
4
- registerGsdExtension(pi);
3
+ // Always register the core /gsd command first, in isolation.
4
+ // This ensures /gsd is available even if the full bootstrap (shortcuts,
5
+ // tools, hooks) fails — e.g. due to a Windows-specific import error.
6
+ const { registerGSDCommand } = await import("./commands/index.js");
7
+ registerGSDCommand(pi);
8
+ // Full setup (shortcuts, tools, hooks) in a separate try/catch so that
9
+ // any platform-specific load failure doesn't take out the core command.
10
+ try {
11
+ const { registerGsdExtension } = await import("./bootstrap/register-extension.js");
12
+ registerGsdExtension(pi);
13
+ }
14
+ catch (err) {
15
+ const { logWarning } = await import("./workflow-logger.js");
16
+ logWarning("bootstrap", `Extension setup partially failed — /gsd commands are available but shortcuts/tools may be missing: ${err instanceof Error ? err.message : String(err)}`);
17
+ }
5
18
  }
@@ -5,7 +5,7 @@
5
5
  // Exports: parseDecisionsTable, parseRequirementsSections, migrateFromMarkdown
6
6
  import { readFileSync, readdirSync, existsSync } from 'node:fs';
7
7
  import { join } from 'node:path';
8
- import { upsertDecision, upsertRequirement, insertArtifact, insertMilestone, insertSlice, insertTask, openDatabase, transaction, _getAdapter, } from './gsd-db.js';
8
+ import { upsertDecision, upsertRequirement, insertArtifact, insertMilestone, insertSlice, insertTask, openDatabase, transaction, updateSliceStatus, _getAdapter, } from './gsd-db.js';
9
9
  import { resolveGsdRootFile, resolveMilestoneFile, resolveSliceFile, resolveTasksDir, milestonesDir, gsdRoot, resolveTaskFiles, } from './paths.js';
10
10
  import { findMilestoneIds } from './guided-flow.js';
11
11
  import { parseRoadmap, parsePlan } from './parsers-legacy.js';
@@ -571,9 +571,8 @@ export function migrateHierarchyToDb(basePath) {
571
571
  return t.done && existsSync(summaryFile);
572
572
  });
573
573
  if (allTasksDone && hasSliceSummary) {
574
- const adapter = _getAdapter();
575
- if (adapter) {
576
- adapter.prepare(`UPDATE slices SET status = 'complete' WHERE id = :sid AND milestone_id = :mid`).run({ ':sid': sliceEntry.id, ':mid': milestoneId });
574
+ if (_getAdapter()) {
575
+ updateSliceStatus(milestoneId, sliceEntry.id, 'complete');
577
576
  process.stderr.write(`gsd-migrate: ${milestoneId}/${sliceEntry.id} all tasks + slice summary complete — upgrading slice to complete\n`);
578
577
  }
579
578
  }
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // Storage layer for auto-learned project memories. Follows context-store.ts patterns.
4
4
  // All functions degrade gracefully: return empty results when DB unavailable, never throw.
5
- import { isDbAvailable, _getAdapter, transaction } from './gsd-db.js';
5
+ import { isDbAvailable, _getAdapter, transaction, insertMemoryRow, rewriteMemoryId, updateMemoryContentRow, incrementMemoryHitCount, supersedeMemoryRow, markMemoryUnitProcessed, decayMemoriesBefore, supersedeLowestRankedMemories, } from './gsd-db.js';
6
6
  // ─── Category Display Order ─────────────────────────────────────────────────
7
7
  const CATEGORY_PRIORITY = {
8
8
  gotcha: 0,
@@ -112,27 +112,23 @@ export function createMemory(fields) {
112
112
  const now = new Date().toISOString();
113
113
  // Insert with a temporary placeholder ID — seq is auto-assigned
114
114
  const placeholder = `_TMP_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
115
- adapter.prepare(`INSERT INTO memories (id, category, content, confidence, source_unit_type, source_unit_id, created_at, updated_at)
116
- VALUES (:id, :category, :content, :confidence, :source_unit_type, :source_unit_id, :created_at, :updated_at)`).run({
117
- ':id': placeholder,
118
- ':category': fields.category,
119
- ':content': fields.content,
120
- ':confidence': fields.confidence ?? 0.8,
121
- ':source_unit_type': fields.source_unit_type ?? null,
122
- ':source_unit_id': fields.source_unit_id ?? null,
123
- ':created_at': now,
124
- ':updated_at': now,
115
+ insertMemoryRow({
116
+ id: placeholder,
117
+ category: fields.category,
118
+ content: fields.content,
119
+ confidence: fields.confidence ?? 0.8,
120
+ sourceUnitType: fields.source_unit_type ?? null,
121
+ sourceUnitId: fields.source_unit_id ?? null,
122
+ createdAt: now,
123
+ updatedAt: now,
125
124
  });
126
- // Derive the real ID from the assigned seq
125
+ // Derive the real ID from the assigned seq (SELECT is still fine via adapter)
127
126
  const row = adapter.prepare('SELECT seq FROM memories WHERE id = :id').get({ ':id': placeholder });
128
127
  if (!row)
129
128
  return placeholder; // fallback — should not happen
130
129
  const seq = row['seq'];
131
130
  const realId = `MEM${String(seq).padStart(3, '0')}`;
132
- adapter.prepare('UPDATE memories SET id = :real_id WHERE id = :placeholder').run({
133
- ':real_id': realId,
134
- ':placeholder': placeholder,
135
- });
131
+ rewriteMemoryId(placeholder, realId);
136
132
  return realId;
137
133
  }
138
134
  catch {
@@ -145,17 +141,8 @@ export function createMemory(fields) {
145
141
  export function updateMemoryContent(id, content, confidence) {
146
142
  if (!isDbAvailable())
147
143
  return false;
148
- const adapter = _getAdapter();
149
- if (!adapter)
150
- return false;
151
144
  try {
152
- const now = new Date().toISOString();
153
- if (confidence != null) {
154
- adapter.prepare('UPDATE memories SET content = :content, confidence = :confidence, updated_at = :updated_at WHERE id = :id').run({ ':content': content, ':confidence': confidence, ':updated_at': now, ':id': id });
155
- }
156
- else {
157
- adapter.prepare('UPDATE memories SET content = :content, updated_at = :updated_at WHERE id = :id').run({ ':content': content, ':updated_at': now, ':id': id });
158
- }
145
+ updateMemoryContentRow(id, content, confidence, new Date().toISOString());
159
146
  return true;
160
147
  }
161
148
  catch {
@@ -168,11 +155,8 @@ export function updateMemoryContent(id, content, confidence) {
168
155
  export function reinforceMemory(id) {
169
156
  if (!isDbAvailable())
170
157
  return false;
171
- const adapter = _getAdapter();
172
- if (!adapter)
173
- return false;
174
158
  try {
175
- adapter.prepare('UPDATE memories SET hit_count = hit_count + 1, updated_at = :updated_at WHERE id = :id').run({ ':updated_at': new Date().toISOString(), ':id': id });
159
+ incrementMemoryHitCount(id, new Date().toISOString());
176
160
  return true;
177
161
  }
178
162
  catch {
@@ -185,11 +169,8 @@ export function reinforceMemory(id) {
185
169
  export function supersedeMemory(oldId, newId) {
186
170
  if (!isDbAvailable())
187
171
  return false;
188
- const adapter = _getAdapter();
189
- if (!adapter)
190
- return false;
191
172
  try {
192
- adapter.prepare('UPDATE memories SET superseded_by = :new_id, updated_at = :updated_at WHERE id = :old_id').run({ ':new_id': newId, ':updated_at': new Date().toISOString(), ':old_id': oldId });
173
+ supersedeMemoryRow(oldId, newId, new Date().toISOString());
193
174
  return true;
194
175
  }
195
176
  catch {
@@ -220,12 +201,8 @@ export function isUnitProcessed(unitKey) {
220
201
  export function markUnitProcessed(unitKey, activityFile) {
221
202
  if (!isDbAvailable())
222
203
  return false;
223
- const adapter = _getAdapter();
224
- if (!adapter)
225
- return false;
226
204
  try {
227
- adapter.prepare(`INSERT OR IGNORE INTO memory_processed_units (unit_key, activity_file, processed_at)
228
- VALUES (:key, :file, :at)`).run({ ':key': unitKey, ':file': activityFile, ':at': new Date().toISOString() });
205
+ markMemoryUnitProcessed(unitKey, activityFile, new Date().toISOString());
229
206
  return true;
230
207
  }
231
208
  catch {
@@ -244,16 +221,14 @@ export function decayStaleMemories(thresholdUnits = 20) {
244
221
  if (!adapter)
245
222
  return;
246
223
  try {
247
- // Find the timestamp of the Nth most recent processed unit
224
+ // Find the timestamp of the Nth most recent processed unit (read-only SELECT)
248
225
  const row = adapter.prepare(`SELECT processed_at FROM memory_processed_units
249
226
  ORDER BY processed_at DESC
250
227
  LIMIT 1 OFFSET :offset`).get({ ':offset': thresholdUnits - 1 });
251
228
  if (!row)
252
229
  return; // not enough processed units yet
253
230
  const cutoff = row['processed_at'];
254
- adapter.prepare(`UPDATE memories
255
- SET confidence = MAX(0.1, confidence - 0.1), updated_at = :now
256
- WHERE superseded_by IS NULL AND updated_at < :cutoff AND confidence > 0.1`).run({ ':now': new Date().toISOString(), ':cutoff': cutoff });
231
+ decayMemoriesBefore(cutoff, new Date().toISOString());
257
232
  }
258
233
  catch {
259
234
  // non-fatal
@@ -274,14 +249,7 @@ export function enforceMemoryCap(max = 50) {
274
249
  if (count <= max)
275
250
  return;
276
251
  const excess = count - max;
277
- // Batch update: supersede lowest-ranked active memories in a single statement
278
- adapter.prepare(`UPDATE memories SET superseded_by = 'CAP_EXCEEDED', updated_at = :now
279
- WHERE id IN (
280
- SELECT id FROM memories
281
- WHERE superseded_by IS NULL
282
- ORDER BY (confidence * (1.0 + hit_count * 0.1)) ASC
283
- LIMIT :limit
284
- )`).run({ ':now': new Date().toISOString(), ':limit': excess });
252
+ supersedeLowestRankedMemories(excess, new Date().toISOString());
285
253
  }
286
254
  catch {
287
255
  // non-fatal
@@ -10,7 +10,7 @@
10
10
  * gate registry so the definitions stay in lockstep with prompt builders,
11
11
  * dispatch rules, and state derivation. See gate-registry.ts.
12
12
  */
13
- import { _getAdapter } from "./gsd-db.js";
13
+ import { isDbAvailable, upsertQualityGate } from "./gsd-db.js";
14
14
  import { getGatesForTurn } from "./gate-registry.js";
15
15
  /**
16
16
  * Insert milestone-level quality_gates records for a validation run.
@@ -24,21 +24,22 @@ import { getGatesForTurn } from "./gate-registry.js";
24
24
  * in gate-registry.ts automatically flows through here.
25
25
  */
26
26
  export function insertMilestoneValidationGates(milestoneId, sliceId, verdict, evaluatedAt) {
27
- const db = _getAdapter();
28
- if (!db)
27
+ if (!isDbAvailable())
29
28
  return;
30
29
  const gateVerdict = verdict === "pass" ? "pass" : "flag";
31
30
  const milestoneGates = getGatesForTurn("validate-milestone");
32
31
  for (const def of milestoneGates) {
33
- db.prepare(`INSERT OR REPLACE INTO quality_gates
34
- (milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
35
- VALUES (:mid, :sid, :gid, 'milestone', '', 'complete', :verdict, :rationale, '', :evaluated_at)`).run({
36
- ":mid": milestoneId,
37
- ":sid": sliceId,
38
- ":gid": def.id,
39
- ":verdict": gateVerdict,
40
- ":rationale": `${def.promptSection} — milestone validation verdict: ${verdict}`,
41
- ":evaluated_at": evaluatedAt,
32
+ upsertQualityGate({
33
+ milestoneId,
34
+ sliceId,
35
+ gateId: def.id,
36
+ scope: "milestone",
37
+ taskId: "",
38
+ status: "complete",
39
+ verdict: gateVerdict,
40
+ rationale: `${def.promptSection} — milestone validation verdict: ${verdict}`,
41
+ findings: "",
42
+ evaluatedAt,
42
43
  });
43
44
  }
44
45
  }
@@ -4,7 +4,7 @@
4
4
  //
5
5
  // Both READ and WRITE operations are native — push operations remain as
6
6
  // execSync calls because git2 credential handling is too complex.
7
- import { execSync, execFileSync } from "node:child_process";
7
+ import { execFileSync } from "node:child_process";
8
8
  import { GSDError, GSD_GIT_ERROR } from "./errors.js";
9
9
  import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
10
10
  import { getErrorMessage } from "./error-utils.js";
@@ -206,7 +206,7 @@ export function nativeIsRepo(basePath) {
206
206
  return native.gitIsRepo(basePath);
207
207
  }
208
208
  try {
209
- execSync("git rev-parse --git-dir", { cwd: basePath, stdio: "pipe" });
209
+ execFileSync("git", ["rev-parse", "--git-dir"], { cwd: basePath, stdio: "pipe" });
210
210
  return true;
211
211
  }
212
212
  catch {
@@ -629,7 +629,10 @@ export function nativeCommit(basePath, message, options) {
629
629
  }
630
630
  // Fallback: use git commit with stdin pipe for safe multi-line messages
631
631
  try {
632
- const result = execSync(`git commit --no-verify -F -${options?.allowEmpty ? " --allow-empty" : ""}`, {
632
+ const args = ["commit", "--no-verify", "-F", "-"];
633
+ if (options?.allowEmpty)
634
+ args.push("--allow-empty");
635
+ const result = execFileSync("git", args, {
633
636
  cwd: basePath,
634
637
  stdio: ["pipe", "pipe", "pipe"],
635
638
  encoding: "utf-8",
@@ -767,7 +770,7 @@ export function nativeResetHard(basePath) {
767
770
  native.gitResetHard(basePath);
768
771
  return;
769
772
  }
770
- execSync("git reset --hard HEAD", { cwd: basePath, stdio: "pipe" });
773
+ execFileSync("git", ["reset", "--hard", "HEAD"], { cwd: basePath, stdio: "pipe" });
771
774
  }
772
775
  /**
773
776
  * Soft reset to a target ref (git reset --soft <ref>).
@@ -0,0 +1,35 @@
1
+ You are generating tests for recently completed GSD work.
2
+
3
+ ## Slice: {{sliceId}} — {{sliceTitle}}
4
+
5
+ ### Summary
6
+
7
+ {{sliceSummary}}
8
+
9
+ ### Existing Test Patterns
10
+
11
+ {{existingTestPatterns}}
12
+
13
+ ## Working Directory
14
+
15
+ `{{workingDirectory}}`
16
+
17
+ ## Instructions
18
+
19
+ 1. Read the slice summary above to understand what was built
20
+ 2. Identify the source files that were created or modified for this slice
21
+ 3. Read the implementation code to understand behavior, edge cases, and error paths
22
+ 4. Write comprehensive tests following the project's existing test patterns and framework
23
+ 5. Run the tests to verify they pass
24
+ 6. Fix any failures
25
+
26
+ ### Rules
27
+
28
+ - Follow the project's existing test patterns (framework, assertions, file structure)
29
+ - Test behavior, not implementation details
30
+ - Cover: happy path, edge cases, error conditions, boundary values
31
+ - Do NOT modify implementation files — only create or update test files
32
+ - Name test files consistently with the project's conventions
33
+ - Keep tests focused and readable
34
+
35
+ {{skillActivation}}
@@ -271,7 +271,11 @@ function reconcileDiskToDb(basePath) {
271
271
  try {
272
272
  roadmapContent = readFileSync(roadmapPath, "utf-8");
273
273
  }
274
- catch {
274
+ catch (err) {
275
+ logWarning("state", "reconcileDiskToDb: roadmap read failed, skipping milestone", {
276
+ mid,
277
+ error: err.message,
278
+ });
275
279
  continue;
276
280
  }
277
281
  const parsed = parseRoadmap(roadmapContent);