gsd-pi 2.80.0-dev.96f4d89d5 → 2.80.0-dev.b2a374d5b

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 (239) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/GSD-WORKFLOW.md +2 -2
  3. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  4. package/dist/resources/extensions/gsd/auto/phases.js +37 -30
  5. package/dist/resources/extensions/gsd/auto-dispatch.js +10 -0
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +10 -10
  7. package/dist/resources/extensions/gsd/auto-prompts.js +111 -1
  8. package/dist/resources/extensions/gsd/auto-recovery.js +154 -8
  9. package/dist/resources/extensions/gsd/auto-start.js +2 -3
  10. package/dist/resources/extensions/gsd/auto.js +16 -3
  11. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -1
  12. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +129 -1
  13. package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
  14. package/dist/resources/extensions/gsd/commands/dispatcher.js +5 -0
  15. package/dist/resources/extensions/gsd/commands-extract-learnings.js +17 -12
  16. package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
  17. package/dist/resources/extensions/gsd/db-base-schema.js +14 -0
  18. package/dist/resources/extensions/gsd/db-migration-steps.js +16 -0
  19. package/dist/resources/extensions/gsd/detection.js +106 -0
  20. package/dist/resources/extensions/gsd/gsd-db.js +102 -2
  21. package/dist/resources/extensions/gsd/guided-flow.js +63 -12
  22. package/dist/resources/extensions/gsd/migrate/command.js +40 -1
  23. package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
  24. package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
  25. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  26. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  27. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  28. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  29. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
  30. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  31. package/dist/resources/extensions/gsd/quick.js +34 -2
  32. package/dist/resources/extensions/gsd/safety/evidence-collector.js +10 -2
  33. package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
  34. package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
  35. package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
  36. package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
  37. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  38. package/dist/web/standalone/.next/BUILD_ID +1 -1
  39. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  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 +13 -13
  66. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  68. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  69. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  70. package/package.json +4 -2
  71. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  72. package/packages/pi-coding-agent/dist/core/agent-session.js +3 -0
  73. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  74. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +76 -0
  75. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  76. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
  77. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  78. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
  79. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  80. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
  81. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
  82. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
  83. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
  84. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
  85. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
  86. package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
  87. package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
  88. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
  89. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
  90. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
  91. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
  92. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -0
  93. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/extensions/runner.js +17 -1
  95. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  96. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +99 -0
  97. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  98. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
  99. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  100. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
  102. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  103. package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
  104. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js +6 -4
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +50 -19
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +2 -2
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -1
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +2 -1
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +7 -6
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +13 -0
  119. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +77 -13
  121. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +68 -2
  124. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
  126. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
  129. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  131. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +36 -27
  132. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  133. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +21 -0
  134. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
  136. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
  137. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
  138. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
  139. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
  141. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
  142. package/packages/pi-coding-agent/src/core/agent-session.ts +3 -0
  143. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +89 -0
  144. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
  145. package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
  146. package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
  147. package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
  148. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +110 -0
  149. package/packages/pi-coding-agent/src/core/extensions/runner.ts +19 -1
  150. package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
  151. package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
  152. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.ts +7 -5
  153. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +68 -19
  154. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +2 -2
  155. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +1 -0
  156. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +2 -1
  157. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +10 -9
  158. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +88 -13
  159. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
  160. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +91 -3
  161. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
  162. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +36 -27
  163. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +25 -0
  164. package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
  165. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
  166. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  167. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  168. package/packages/pi-tui/dist/tui.js +17 -7
  169. package/packages/pi-tui/dist/tui.js.map +1 -1
  170. package/packages/pi-tui/src/tui.ts +19 -7
  171. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  172. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  173. package/pkg/dist/modes/interactive/theme/themes.js +36 -27
  174. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  175. package/src/resources/GSD-WORKFLOW.md +2 -2
  176. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  177. package/src/resources/extensions/gsd/auto/loop.ts +50 -8
  178. package/src/resources/extensions/gsd/auto/phases.ts +42 -28
  179. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -0
  180. package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
  181. package/src/resources/extensions/gsd/auto-prompts.ts +116 -1
  182. package/src/resources/extensions/gsd/auto-recovery.ts +153 -7
  183. package/src/resources/extensions/gsd/auto-start.ts +7 -6
  184. package/src/resources/extensions/gsd/auto.ts +23 -3
  185. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +39 -1
  186. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +135 -1
  187. package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
  188. package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
  189. package/src/resources/extensions/gsd/commands-extract-learnings.ts +17 -12
  190. package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
  191. package/src/resources/extensions/gsd/db-base-schema.ts +15 -0
  192. package/src/resources/extensions/gsd/db-migration-steps.ts +17 -0
  193. package/src/resources/extensions/gsd/detection.ts +128 -0
  194. package/src/resources/extensions/gsd/gsd-db.ts +119 -1
  195. package/src/resources/extensions/gsd/guided-flow.ts +66 -12
  196. package/src/resources/extensions/gsd/migrate/command.ts +47 -1
  197. package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
  198. package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
  199. package/src/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  200. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  201. package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  202. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  203. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
  204. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  205. package/src/resources/extensions/gsd/quick.ts +37 -2
  206. package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -2
  207. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -1
  208. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +170 -1
  209. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
  210. package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +9 -0
  211. package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
  212. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
  213. package/src/resources/extensions/gsd/tests/db-schema-metadata.test.ts +2 -2
  214. package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
  215. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +44 -0
  216. package/src/resources/extensions/gsd/tests/has-pending-deep-stage.test.ts +33 -1
  217. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
  218. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
  219. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
  220. package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
  221. package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
  222. package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
  223. package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
  224. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
  225. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +38 -0
  226. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +63 -2
  227. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
  228. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +7 -0
  229. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
  230. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
  231. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
  232. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
  233. package/src/resources/extensions/gsd/tests/worktree-write-gate.test.ts +179 -0
  234. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
  235. package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
  236. package/src/resources/extensions/gsd/unit-runtime.ts +14 -0
  237. package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
  238. /package/dist/web/standalone/.next/static/{y7d--cnc6Cibuva-Lzzw_ → FU0iL9dhdc2IBzEYBDOlY}/_buildManifest.js +0 -0
  239. /package/dist/web/standalone/.next/static/{y7d--cnc6Cibuva-Lzzw_ → FU0iL9dhdc2IBzEYBDOlY}/_ssgManifest.js +0 -0
@@ -112,11 +112,12 @@ const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
112
112
  * the DB is unavailable (e.g. fresh project before init) we skip registration
113
113
  * silently rather than blocking session start.
114
114
  */
115
- function registerAutoWorkerForSession(session) {
115
+ function registerAutoWorkerForSession(session, projectRootOverride) {
116
116
  if (session.workerId)
117
117
  return; // already registered (e.g. resume re-runs)
118
118
  try {
119
- const projectRootRealpath = normalizeRealPath(session.scope?.workspace.projectRoot
119
+ const projectRootRealpath = normalizeRealPath(projectRootOverride
120
+ ?? session.scope?.workspace.projectRoot
120
121
  ?? (session.originalBasePath || session.basePath));
121
122
  session.workerId = registerAutoWorker({ projectRootRealpath });
122
123
  }
@@ -225,8 +226,16 @@ function synthesizePausedSessionRecovery(basePath, unitType, unitId, sessionFile
225
226
  export function _synthesizePausedSessionRecoveryForTest(basePath, unitType, unitId, sessionFile) {
226
227
  return synthesizePausedSessionRecovery(basePath, unitType, unitId, sessionFile);
227
228
  }
229
+ const DETACHED_AUTO_KEEPALIVE_INTERVAL_MS = 30_000;
230
+ function withDetachedAutoKeepalive(run) {
231
+ const keepAlive = setInterval(() => { }, DETACHED_AUTO_KEEPALIVE_INTERVAL_MS);
232
+ return run.finally(() => {
233
+ clearInterval(keepAlive);
234
+ });
235
+ }
236
+ export const _withDetachedAutoKeepaliveForTest = withDetachedAutoKeepalive;
228
237
  export function startAutoDetached(ctx, pi, base, verboseMode, options) {
229
- void startAuto(ctx, pi, base, verboseMode, options).catch((err) => {
238
+ void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch((err) => {
230
239
  const message = getErrorMessage(err);
231
240
  ctx.ui.notify(`Auto-start failed: ${message}`, "error");
232
241
  logWarning("engine", `auto start error: ${message}`, { file: "auto.ts" });
@@ -1623,6 +1632,10 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1623
1632
  lockBase,
1624
1633
  buildResolver,
1625
1634
  };
1635
+ // Register the worker before bootstrap enters a milestone worktree.
1636
+ // This ensures enterMilestone can claim a lease and seed dispatch claims
1637
+ // for crash-recovery fidelity (#5405).
1638
+ registerAutoWorkerForSession(s, base);
1626
1639
  const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps, freshStartAssessment);
1627
1640
  if (!ready)
1628
1641
  return;
@@ -2,7 +2,7 @@ import { join } from "node:path";
2
2
  import { isToolCallEventType } from "@gsd/pi-coding-agent";
3
3
  import { updateSnapshot } from "../ecosystem/gsd-extension-api.js";
4
4
  import { buildMilestoneFileName, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
5
- import { canonicalToolName, clearDiscussionFlowState, isDepthConfirmationAnswer, isQueuePhaseActive, markApprovalGateVerified, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockPlanningUnit, shouldBlockQueueExecution, isGateQuestionId, setPendingGate, clearPendingGate, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
5
+ import { canonicalToolName, clearDiscussionFlowState, isDepthConfirmationAnswer, isQueuePhaseActive, markApprovalGateVerified, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockPlanningUnit, shouldBlockQueueExecution, shouldBlockWorktreeWrite, isGateQuestionId, setPendingGate, clearPendingGate, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
6
6
  import { resolveManifest } from "../unit-context-manifest.js";
7
7
  import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
8
8
  import { loadFile, saveFile, formatContinue } from "../files.js";
@@ -52,6 +52,25 @@ async function applyDisabledModelProviderPolicy(ctx) {
52
52
  // Non-fatal: keep default provider visibility if preferences cannot be loaded.
53
53
  }
54
54
  }
55
+ /**
56
+ * Bridge `context_management.compaction_threshold_percent` from GSD preferences
57
+ * into the agent's runtime compaction settings (#5475). The preference is
58
+ * validated to (0.5, 0.95) at load time, but defense-in-depth normalization
59
+ * here protects against a stale or hand-edited prefs file. Calling with
60
+ * `undefined` clears any prior override so a removed preference does not leak.
61
+ */
62
+ async function applyCompactionThresholdOverride(ctx) {
63
+ try {
64
+ const { loadEffectiveGSDPreferences } = await import("../preferences.js");
65
+ const prefs = loadEffectiveGSDPreferences();
66
+ const raw = prefs?.preferences.context_management?.compaction_threshold_percent;
67
+ const value = typeof raw === "number" && Number.isFinite(raw) && raw > 0 && raw < 1 ? raw : undefined;
68
+ ctx.setCompactionThresholdOverride(value);
69
+ }
70
+ catch {
71
+ // Non-fatal: leave any existing override in place.
72
+ }
73
+ }
55
74
  export function resolveNotificationStoreBasePath(cwd = process.cwd()) {
56
75
  return resolveWorktreeProjectRoot(cwd);
57
76
  }
@@ -101,6 +120,7 @@ export function registerHooks(pi, ecosystemHandlers) {
101
120
  await resetAskUserQuestionsTurnCache();
102
121
  await syncServiceTierStatus(ctx);
103
122
  await applyDisabledModelProviderPolicy(ctx);
123
+ await applyCompactionThresholdOverride(ctx);
104
124
  // Skip MCP auto-prep when running inside an auto-worktree (see session_switch below).
105
125
  const { isInAutoWorktree } = await import("../auto-worktree.js");
106
126
  if (!isInAutoWorktree(process.cwd())) {
@@ -149,6 +169,7 @@ export function registerHooks(pi, ecosystemHandlers) {
149
169
  clearDiscussionFlowState(process.cwd());
150
170
  await syncServiceTierStatus(ctx);
151
171
  await applyDisabledModelProviderPolicy(ctx);
172
+ await applyCompactionThresholdOverride(ctx);
152
173
  // Skip MCP auto-prep when running inside an auto-worktree. The worktree
153
174
  // already has .mcp.json from createAutoWorktree, and re-running the writer
154
175
  // post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
@@ -432,6 +453,16 @@ export function registerHooks(pi, ecosystemHandlers) {
432
453
  return planningGuard;
433
454
  }
434
455
  }
456
+ // ── Worktree-isolation write gate (#5199) ────────────────────────────
457
+ // Block planning-write tools from landing code at the project root when
458
+ // git.isolation=worktree but auto-mode hasn't created the milestone
459
+ // worktree yet. Without this, writes silently orphan outside git history.
460
+ if (isToolCallEventType("write", event) || isToolCallEventType("edit", event)) {
461
+ const wtBasePath = resolveWorktreeProjectRoot(dash.basePath ?? discussionBasePath);
462
+ const wtGuard = shouldBlockWorktreeWrite(event.toolName, event.input.path, wtBasePath, isAutoActive(), dash.currentUnit?.type);
463
+ if (wtGuard.block)
464
+ return wtGuard;
465
+ }
435
466
  // ── Single-writer engine: block direct writes to STATE.md ──────────
436
467
  // Covers write, edit, and bash tools to prevent bypass vectors.
437
468
  if (isToolCallEventType("write", event)) {
@@ -1,7 +1,9 @@
1
- import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
1
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
2
2
  import { isAbsolute, join, relative, resolve, sep } from "node:path";
3
3
  import { minimatch } from "minimatch";
4
+ import { getIsolationMode } from "../preferences.js";
4
5
  import { logWarning } from "../workflow-logger.js";
6
+ import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "../worktree-root.js";
5
7
  /**
6
8
  * Regex matching milestone CONTEXT.md file names in both legacy M001
7
9
  * and unique M001-abc123 formats. Exported so regex-hardening tests
@@ -728,3 +730,129 @@ export function shouldBlockPlanningUnit(toolName, pathOrCommand, basePath, unitT
728
730
  // avoids breaking gsd_* MCP tools or future safe additions.
729
731
  return { block: false };
730
732
  }
733
+ // ─── Worktree isolation write gate (#5199) ────────────────────────────────
734
+ //
735
+ // When `git.isolation: worktree` is configured, the per-unit commit pipeline
736
+ // only runs inside the auto-mode loop (`auto-post-unit.ts`). If the LLM
737
+ // authors code at the project root before auto-mode is started, those writes
738
+ // land in the working tree but never reach a commit — they're silently
739
+ // orphaned outside git history. This guard blocks those writes at the
740
+ // tool_call seam so the agent receives a clear error instead.
741
+ const WORKTREE_GATE_BOOTSTRAP_UNITS = new Set([
742
+ "discuss-milestone",
743
+ "plan-milestone",
744
+ "init",
745
+ ]);
746
+ function realpathOrResolve(p) {
747
+ const abs = resolve(p);
748
+ try {
749
+ return realpathSync(abs);
750
+ }
751
+ catch {
752
+ // Path doesn't exist (yet) — realpath the deepest existing ancestor so
753
+ // platforms where /tmp -> /private/tmp don't break containment checks.
754
+ let dir = abs;
755
+ const tail = [];
756
+ while (dir && dir !== resolve(dir, "..")) {
757
+ try {
758
+ const real = realpathSync(dir);
759
+ return tail.length ? join(real, ...tail.reverse()) : real;
760
+ }
761
+ catch {
762
+ const idx = dir.lastIndexOf(sep);
763
+ if (idx <= 0)
764
+ break;
765
+ tail.push(dir.slice(idx + 1));
766
+ dir = dir.slice(0, idx) || sep;
767
+ }
768
+ }
769
+ return abs;
770
+ }
771
+ }
772
+ function isPathContained(target, container) {
773
+ if (target === container)
774
+ return true;
775
+ return target.startsWith(container.endsWith(sep) ? container : container + sep);
776
+ }
777
+ /**
778
+ * Block planning-write tool calls that would land code at the project root
779
+ * while `git.isolation: worktree` is in effect and auto-mode hasn't created
780
+ * (or flipped cwd into) the milestone worktree.
781
+ *
782
+ * Pure / unit-testable. Callers in `register-hooks.ts` supply the resolved
783
+ * project root and current auto liveness; this function does no I/O beyond
784
+ * realpath resolution.
785
+ *
786
+ * Allow rules (in order):
787
+ * 1. Tool isn't a planning-write (write/edit/multi_edit/notebook_edit).
788
+ * 2. `GSD_DISABLE_WORKTREE_WRITE_GUARD=1` self-hosting bypass.
789
+ * 3. Isolation mode is not "worktree".
790
+ * 4. Active unit is a bootstrap unit (discuss-milestone/plan-milestone/init).
791
+ * 5. Target is inside `<projectRoot>/.gsd/worktrees/` (a real worktree).
792
+ * 6. Target is inside `<projectRoot>/.gsd/` and isn't masquerading as a
793
+ * worktrees sibling (rejects the `.gsd/worktrees-extra/…` prefix trick).
794
+ * 7. Auto is live AND `effectiveBasePath` is itself a `.gsd/worktrees/…` path.
795
+ *
796
+ * Otherwise: block with a message that points the agent at `/gsd` to start
797
+ * auto-mode.
798
+ */
799
+ export function shouldBlockWorktreeWrite(toolName, targetPath, effectiveBasePath, isAutoLive, currentUnitType) {
800
+ const tool = canonicalToolName(toolName);
801
+ if (!PLANNING_WRITE_TOOLS.has(tool))
802
+ return { block: false };
803
+ if (process.env.GSD_DISABLE_WORKTREE_WRITE_GUARD === "1")
804
+ return { block: false };
805
+ if (getIsolationMode(effectiveBasePath) !== "worktree")
806
+ return { block: false };
807
+ if (currentUnitType && WORKTREE_GATE_BOOTSTRAP_UNITS.has(currentUnitType))
808
+ return { block: false };
809
+ if (!targetPath) {
810
+ return {
811
+ block: true,
812
+ reason: [
813
+ `HARD BLOCK: ${tool} called with empty path while \`git.isolation: worktree\` is configured`,
814
+ `and auto-mode is not active. Refusing to allow writes that cannot be located.`,
815
+ ].join(" "),
816
+ };
817
+ }
818
+ // Resolve the target relative to the project root, then realpath to defeat
819
+ // symlink-based escapes and prefix tricks (e.g. .gsd/worktrees-extra/).
820
+ const projectRoot = resolveWorktreeProjectRoot(effectiveBasePath);
821
+ const absTarget = isAbsolute(targetPath) ? targetPath : resolve(projectRoot, targetPath);
822
+ const realTarget = realpathOrResolve(absTarget);
823
+ const realRoot = realpathOrResolve(projectRoot);
824
+ const realGsd = realpathOrResolve(join(projectRoot, ".gsd"));
825
+ const realWorktreesDir = realpathOrResolve(join(projectRoot, ".gsd", "worktrees"));
826
+ // Allow writes inside the legitimate worktrees subtree.
827
+ if (isPathContained(realTarget, realWorktreesDir))
828
+ return { block: false };
829
+ // Allow writes to .gsd/ planning artifacts, but reject siblings whose name
830
+ // starts with "worktrees" (the worktrees-extra prefix trick — case 4).
831
+ if (isPathContained(realTarget, realGsd)) {
832
+ const rel = relative(realGsd, realTarget);
833
+ const firstSeg = rel.split(/[\/\\]/)[0] ?? "";
834
+ if (!firstSeg.startsWith("worktrees"))
835
+ return { block: false };
836
+ // fall through: looks like worktrees<something> sibling — block
837
+ }
838
+ // Auto is live and the caller is operating inside a worktree path —
839
+ // host tool's write happens in worktree context; let it through.
840
+ if (isAutoLive && isGsdWorktreePath(effectiveBasePath))
841
+ return { block: false };
842
+ // Block. Provide enough context that the agent can self-correct.
843
+ const displayTarget = isPathContained(realTarget, realRoot)
844
+ ? relative(realRoot, realTarget) || "."
845
+ : realTarget;
846
+ return {
847
+ block: true,
848
+ reason: [
849
+ `HARD BLOCK: Worktree isolation is configured (\`git.isolation: worktree\`) but auto-mode is`,
850
+ `not running and the target "${displayTarget}" is not inside \`.gsd/worktrees/<MID>/\`.`,
851
+ `Code edits at the project root would be lost — only the auto-mode commit pipeline`,
852
+ `(auto-post-unit) commits work, and it never runs outside the loop.`,
853
+ `Required action: start auto-mode with \`/gsd\` so the milestone worktree is created,`,
854
+ `then write inside it. To disable this guard for self-hosting development, set`,
855
+ `GSD_DISABLE_WORKTREE_WRITE_GUARD=1.`,
856
+ ].join(" "),
857
+ };
858
+ }
@@ -16,6 +16,31 @@ import { execFileSync } from "node:child_process";
16
16
  import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
17
17
  import { logWarning } from "./workflow-logger.js";
18
18
  import { nativeHasChanges } from "./native-git-bridge.js";
19
+ function findPreflightStashRef(basePath, milestoneId, stashMarker) {
20
+ const markerPrefix = `gsd-preflight-stash:${milestoneId}:`;
21
+ let fallbackRef = null;
22
+ try {
23
+ const list = execFileSync("git", ["stash", "list", "--format=%gd%x00%s"], {
24
+ cwd: basePath,
25
+ stdio: ["ignore", "pipe", "pipe"],
26
+ encoding: "utf-8",
27
+ env: GIT_NO_PROMPT_ENV,
28
+ });
29
+ for (const line of list.split("\n")) {
30
+ const [ref, subject] = line.split("\x00");
31
+ if (!ref || !subject)
32
+ continue;
33
+ if (stashMarker && subject.includes(stashMarker))
34
+ return ref;
35
+ if (!fallbackRef && subject.includes(markerPrefix))
36
+ fallbackRef = ref;
37
+ }
38
+ }
39
+ catch (err) {
40
+ logWarning("preflight", `stash list failed before restore: ${err instanceof Error ? err.message : String(err)}`);
41
+ }
42
+ return fallbackRef;
43
+ }
19
44
  /**
20
45
  * Check the working tree for dirty files before a milestone merge.
21
46
  *
@@ -47,7 +72,8 @@ export function preflightCleanRoot(basePath, milestoneId, notify) {
47
72
  notify(warnMsg, "warning");
48
73
  // Push the stash
49
74
  try {
50
- execFileSync("git", ["stash", "push", "--include-untracked", "-m", "gsd-preflight-stash"], {
75
+ const stashMarker = `gsd-preflight-stash:${milestoneId}:${process.pid}:${Date.now()}:${process.hrtime.bigint().toString(36)}`;
76
+ execFileSync("git", ["stash", "push", "--include-untracked", "-m", `gsd-preflight-stash [${stashMarker}]`], {
51
77
  cwd: basePath,
52
78
  stdio: ["ignore", "pipe", "pipe"],
53
79
  encoding: "utf-8",
@@ -55,6 +81,7 @@ export function preflightCleanRoot(basePath, milestoneId, notify) {
55
81
  });
56
82
  return {
57
83
  stashPushed: true,
84
+ stashMarker,
58
85
  summary: `Stashed uncommitted changes before merge (milestone ${milestoneId}).`,
59
86
  };
60
87
  }
@@ -73,9 +100,17 @@ export function preflightCleanRoot(basePath, milestoneId, notify) {
73
100
  * Any pop error (e.g. conflict) is logged and notified but does NOT throw —
74
101
  * the merge already completed successfully.
75
102
  */
76
- export function postflightPopStash(basePath, milestoneId, notify) {
103
+ export function postflightPopStash(basePath, milestoneId, stashMarker, notify) {
104
+ let stashRef = null;
77
105
  try {
78
- execFileSync("git", ["stash", "pop"], {
106
+ stashRef = findPreflightStashRef(basePath, milestoneId, stashMarker);
107
+ if (!stashRef) {
108
+ const msg = `No matching GSD preflight stash found for milestone ${milestoneId}; leaving stash list untouched.`;
109
+ logWarning("preflight", msg);
110
+ notify(msg, "warning");
111
+ return;
112
+ }
113
+ execFileSync("git", ["stash", "pop", stashRef], {
79
114
  cwd: basePath,
80
115
  stdio: ["ignore", "pipe", "pipe"],
81
116
  encoding: "utf-8",
@@ -86,7 +121,10 @@ export function postflightPopStash(basePath, milestoneId, notify) {
86
121
  catch (err) {
87
122
  // Pop conflicts mean the merged code collides with the stashed changes.
88
123
  // Log a warning — the user needs to resolve manually, but the merge succeeded.
89
- const msg = `git stash pop failed after merge of milestone ${milestoneId}: ${err instanceof Error ? err.message : String(err)}. Run "git stash pop" manually to restore your changes.`;
124
+ const restoreHint = stashRef
125
+ ? `Run "git stash pop ${stashRef}" or "git stash apply ${stashRef}" manually to restore the correct stash.`
126
+ : `Run "git stash list" to find the matching GSD preflight stash before restoring manually.`;
127
+ const msg = `git stash pop ${stashRef ?? ""}`.trim() + ` failed after merge of milestone ${milestoneId}: ${err instanceof Error ? err.message : String(err)}. ${restoreHint}`;
90
128
  logWarning("preflight", msg);
91
129
  notify(msg, "warning");
92
130
  }
@@ -33,5 +33,10 @@ export async function handleGSDCommand(args, ctx, pi) {
33
33
  }
34
34
  if (handled)
35
35
  return;
36
+ if (trimmed.includes(" ")) {
37
+ const { handleDo } = await import("../commands-do.js");
38
+ await handleDo(trimmed, ctx, pi);
39
+ return;
40
+ }
36
41
  ctx.ui.notify(`Unknown: /gsd ${trimmed}. Run /gsd help for available commands.`, "warning");
37
42
  }
@@ -114,14 +114,19 @@ Using the \`write\` tool, persist the full structured report to
114
114
  LEARNINGS.md is the full, cited audit trail. Write it first — subsequent steps
115
115
  feed from its content.
116
116
 
117
- ### Step 3 — Optionally pre-query the memory store for semantic duplicates
117
+ ### Step 3 — Run one bounded duplicate check
118
118
 
119
- Before persisting any extracted item in Steps 4–6, you may call
120
- \`memory_query\` with 2–3 keywords from the item to check whether the
121
- memory store already holds a semantically equivalent entry at high
122
- confidence. Skip those items in their respective steps. The memory store
123
- is the single source of truth for cross-session durable knowledge — no
124
- other persistence call is part of this flow.
119
+ Before persisting extracted items in Steps 4–6, do at most one duplicate-check
120
+ pass for the durable Decisions, Lessons, and Patterns from this milestone. Use
121
+ the already-written LEARNINGS.md content and call \`memory_query\` once with a
122
+ compact keyword summary for the whole batch. If that result clearly shows a
123
+ semantically equivalent high-confidence memory for an item, mark only that item
124
+ as already captured and skip it in its respective persistence step.
125
+
126
+ Do not re-read milestone artefacts or repeat memory queries category-by-category
127
+ after this point. The memory store is the single source of truth for
128
+ cross-session durable knowledge — no other persistence call is part of this
129
+ flow.
125
130
 
126
131
  ### Step 4 — Persist Patterns via \`capture_thought\`
127
132
 
@@ -160,11 +165,11 @@ later projection back to a human-visible decisions register stays lossless
160
165
 
161
166
  ### Step 7 — Deduplication rule (applies to Steps 4, 5, 6)
162
167
 
163
- Before each \`capture_thought\` call, optionally call \`memory_query\` with 2–3
164
- keywords from the entry. If a semantically equivalent memory is returned at
165
- high confidence, skip the capture entirely. Prefer skipping a near-duplicate
166
- over creating a second slightly-different row — redundancy degrades the
167
- signal.
168
+ Use only the duplicate-check result from Step 3. If that bounded check returned
169
+ a semantically equivalent memory at high confidence for an extracted item, skip
170
+ the capture entirely. Otherwise, persist the item once via \`capture_thought\`.
171
+ Prefer skipping a near-duplicate over creating a second slightly-different row
172
+ — redundancy degrades the signal.
168
173
 
169
174
  ### Step 8 — Surprises stay only in LEARNINGS.md
170
175
 
@@ -29,6 +29,7 @@ import { _getAdapter, isDbAvailable } from "./gsd-db.js";
29
29
  import { gsdRoot, normalizeRealPath } from "./paths.js";
30
30
  import { atomicWriteSync } from "./atomic-write.js";
31
31
  import { effectiveLockFile } from "./session-lock.js";
32
+ import { isInFlightRuntimePhase, listUnitRuntimeRecords } from "./unit-runtime.js";
32
33
  const SESSION_FILE_KV_KEY = "session_file";
33
34
  function lockPath(basePath) {
34
35
  return join(gsdRoot(basePath), effectiveLockFile());
@@ -72,9 +73,37 @@ function getLatestDispatchForWorker(workerId) {
72
73
  LIMIT 1`).get({ ":worker_id": workerId });
73
74
  return row ?? null;
74
75
  }
75
- function workerToLockData(worker) {
76
+ function latestInFlightRuntimeRecord(basePath) {
77
+ const records = listUnitRuntimeRecords(basePath).filter((record) => isInFlightRuntimePhase(record.phase));
78
+ if (records.length === 0)
79
+ return null;
80
+ return records.sort((a, b) => {
81
+ const bTime = b.updatedAt || b.startedAt || 0;
82
+ const aTime = a.updatedAt || a.startedAt || 0;
83
+ return bTime - aTime;
84
+ })[0] ?? null;
85
+ }
86
+ function runtimeRecordToLockData(worker, record, sessionFile) {
87
+ const startedAt = Number.isFinite(record.startedAt)
88
+ ? new Date(record.startedAt).toISOString()
89
+ : worker.started_at;
90
+ return {
91
+ pid: worker.pid,
92
+ startedAt: worker.started_at,
93
+ unitType: record.unitType,
94
+ unitId: record.unitId,
95
+ unitStartedAt: startedAt,
96
+ sessionFile,
97
+ };
98
+ }
99
+ function workerToLockData(basePath, worker) {
76
100
  const dispatch = getLatestDispatchForWorker(worker.worker_id);
77
101
  const sessionFile = getRuntimeKv("worker", worker.worker_id, SESSION_FILE_KV_KEY) ?? undefined;
102
+ if (!dispatch) {
103
+ const runtimeRecord = latestInFlightRuntimeRecord(basePath);
104
+ if (runtimeRecord)
105
+ return runtimeRecordToLockData(worker, runtimeRecord, sessionFile);
106
+ }
78
107
  return {
79
108
  pid: worker.pid,
80
109
  startedAt: worker.started_at,
@@ -172,7 +201,7 @@ export function readCrashLock(basePath) {
172
201
  const projectRoot = normalizeRealPath(basePath);
173
202
  const stale = findStaleWorkerForProject(projectRoot);
174
203
  if (stale)
175
- return workerToLockData(stale);
204
+ return workerToLockData(basePath, stale);
176
205
  }
177
206
  catch {
178
207
  // Fall through to the legacy lock-file compatibility path.
@@ -232,18 +261,32 @@ export function formatCrashInfo(lock) {
232
261
  export function emitCrashRecoveredUnitEnd(basePath, lock) {
233
262
  if (!lock.unitType || !lock.unitId || lock.unitType === "starting")
234
263
  return;
264
+ emitOpenUnitEndForUnit(basePath, lock.unitType, lock.unitId, "crash-recovered");
265
+ }
266
+ export function emitOpenUnitEndForUnit(basePath, unitType, unitId, status, errorContext) {
235
267
  try {
236
268
  const all = queryJournal(basePath);
237
- const starts = all.filter((e) => e.eventType === "unit-start" && e.data?.unitId === lock.unitId);
269
+ const starts = all.filter((e) => e.eventType === "unit-start" &&
270
+ e.data?.unitType === unitType &&
271
+ e.data?.unitId === unitId);
238
272
  if (starts.length === 0)
239
- return;
240
- const lastStart = starts[starts.length - 1];
273
+ return false;
274
+ const lastStart = [...starts].reverse().find((start) => {
275
+ return !all.some((e) => e.eventType === "unit-end" &&
276
+ e.data?.unitType === unitType &&
277
+ e.data?.unitId === unitId &&
278
+ e.causedBy?.flowId === start.flowId &&
279
+ e.causedBy?.seq === start.seq);
280
+ });
281
+ if (!lastStart)
282
+ return false;
241
283
  const alreadyClosed = all.some((e) => e.eventType === "unit-end" &&
242
- e.data?.unitId === lock.unitId &&
284
+ e.data?.unitType === unitType &&
285
+ e.data?.unitId === unitId &&
243
286
  e.causedBy?.flowId === lastStart.flowId &&
244
287
  e.causedBy?.seq === lastStart.seq);
245
288
  if (alreadyClosed)
246
- return;
289
+ return false;
247
290
  const maxSeq = all
248
291
  .filter((e) => e.flowId === lastStart.flowId)
249
292
  .reduce((max, e) => Math.max(max, e.seq), lastStart.seq);
@@ -253,16 +296,19 @@ export function emitCrashRecoveredUnitEnd(basePath, lock) {
253
296
  seq: maxSeq + 1,
254
297
  eventType: "unit-end",
255
298
  data: {
256
- unitType: lock.unitType,
257
- unitId: lock.unitId,
258
- status: "crash-recovered",
299
+ unitType,
300
+ unitId,
301
+ status,
259
302
  artifactVerified: false,
303
+ ...(errorContext ? { errorContext } : {}),
260
304
  },
261
305
  causedBy: { flowId: lastStart.flowId, seq: lastStart.seq },
262
306
  });
307
+ return true;
263
308
  }
264
309
  catch {
265
310
  // Never throw from crash recovery path.
311
+ return false;
266
312
  }
267
313
  }
268
314
  /**
@@ -294,6 +294,19 @@ export function createBaseSchemaObjects(db, hooks) {
294
294
  updated_at TEXT NOT NULL DEFAULT '',
295
295
  PRIMARY KEY (trace_id, turn_id, stage)
296
296
  )
297
+ `);
298
+ db.exec(`
299
+ CREATE TABLE IF NOT EXISTS milestone_commit_attributions (
300
+ commit_sha TEXT NOT NULL,
301
+ milestone_id TEXT NOT NULL,
302
+ slice_id TEXT DEFAULT NULL,
303
+ task_id TEXT DEFAULT NULL,
304
+ source TEXT NOT NULL DEFAULT 'recorded',
305
+ confidence REAL NOT NULL DEFAULT 1.0,
306
+ files_json TEXT NOT NULL DEFAULT '[]',
307
+ created_at TEXT NOT NULL DEFAULT '',
308
+ PRIMARY KEY (commit_sha, milestone_id)
309
+ )
297
310
  `);
298
311
  db.exec(`
299
312
  CREATE TABLE IF NOT EXISTS audit_events (
@@ -329,6 +342,7 @@ export function createBaseSchemaObjects(db, hooks) {
329
342
  db.exec("CREATE INDEX IF NOT EXISTS idx_gate_runs_turn ON gate_runs(trace_id, turn_id)");
330
343
  db.exec("CREATE INDEX IF NOT EXISTS idx_gate_runs_lookup ON gate_runs(milestone_id, slice_id, task_id, gate_id)");
331
344
  db.exec("CREATE INDEX IF NOT EXISTS idx_turn_git_tx_turn ON turn_git_transactions(trace_id, turn_id)");
345
+ db.exec("CREATE INDEX IF NOT EXISTS idx_milestone_commit_attr_milestone ON milestone_commit_attributions(milestone_id)");
332
346
  db.exec("CREATE INDEX IF NOT EXISTS idx_audit_events_trace ON audit_events(trace_id, ts)");
333
347
  db.exec("CREATE INDEX IF NOT EXISTS idx_audit_events_turn ON audit_events(trace_id, turn_id, ts)");
334
348
  db.exec("CREATE VIEW IF NOT EXISTS active_decisions AS SELECT * FROM decisions WHERE superseded_by IS NULL");
@@ -363,6 +363,22 @@ export function applyMigrationV21StructuredMemories(db) {
363
363
  export function applyMigrationV23MilestoneQueue(db) {
364
364
  ensureColumn(db, "milestones", "sequence", "ALTER TABLE milestones ADD COLUMN sequence INTEGER DEFAULT 0");
365
365
  }
366
+ export function applyMigrationV26MilestoneCommitAttributions(db) {
367
+ db.exec(`
368
+ CREATE TABLE IF NOT EXISTS milestone_commit_attributions (
369
+ commit_sha TEXT NOT NULL,
370
+ milestone_id TEXT NOT NULL,
371
+ slice_id TEXT DEFAULT NULL,
372
+ task_id TEXT DEFAULT NULL,
373
+ source TEXT NOT NULL DEFAULT 'recorded',
374
+ confidence REAL NOT NULL DEFAULT 1.0,
375
+ files_json TEXT NOT NULL DEFAULT '[]',
376
+ created_at TEXT NOT NULL DEFAULT '',
377
+ PRIMARY KEY (commit_sha, milestone_id)
378
+ )
379
+ `);
380
+ db.exec("CREATE INDEX IF NOT EXISTS idx_milestone_commit_attr_milestone ON milestone_commit_attributions(milestone_id)");
381
+ }
366
382
  export function applyMigrationV22QualityGateRepair(db, hooks) {
367
383
  const qgInfo = db.prepare("PRAGMA table_info(quality_gates)").all();
368
384
  const taskIdCol = qgInfo.find((r) => r["name"] === "task_id");