gsd-pi 2.81.0 → 2.82.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/README.md +36 -24
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto/loop.js +111 -8
  4. package/dist/resources/extensions/gsd/auto/phases.js +190 -97
  5. package/dist/resources/extensions/gsd/auto/run-unit.js +66 -3
  6. package/dist/resources/extensions/gsd/auto/session.js +9 -0
  7. package/dist/resources/extensions/gsd/auto/verification-retry-policy.js +43 -0
  8. package/dist/resources/extensions/gsd/auto-dashboard.js +182 -178
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +14 -11
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +7 -1
  11. package/dist/resources/extensions/gsd/auto-recovery.js +6 -181
  12. package/dist/resources/extensions/gsd/auto-runtime-state.js +5 -0
  13. package/dist/resources/extensions/gsd/auto-start.js +20 -23
  14. package/dist/resources/extensions/gsd/auto-unit-closeout.js +33 -5
  15. package/dist/resources/extensions/gsd/auto-verification.js +12 -6
  16. package/dist/resources/extensions/gsd/auto-worktree.js +8 -0
  17. package/dist/resources/extensions/gsd/auto.js +265 -76
  18. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +13 -6
  19. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +8 -2
  20. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +4 -8
  21. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +4 -10
  22. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +9 -0
  23. package/dist/resources/extensions/gsd/git-service.js +2 -1
  24. package/dist/resources/extensions/gsd/gsd-db.js +7 -23
  25. package/dist/resources/extensions/gsd/health-widget-core.js +1 -1
  26. package/dist/resources/extensions/gsd/health-widget.js +4 -10
  27. package/dist/resources/extensions/gsd/markdown-renderer.js +0 -95
  28. package/dist/resources/extensions/gsd/native-git-bridge.js +14 -14
  29. package/dist/resources/extensions/gsd/notification-overlay.js +35 -40
  30. package/dist/resources/extensions/gsd/parallel-merge.js +53 -30
  31. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +25 -33
  32. package/dist/resources/extensions/gsd/prompts/complete-slice.md +14 -12
  33. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +20 -2
  34. package/dist/resources/extensions/gsd/prompts/discuss.md +20 -2
  35. package/dist/resources/extensions/gsd/recovery-classification.js +15 -1
  36. package/dist/resources/extensions/gsd/session-lock.js +40 -0
  37. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +131 -0
  38. package/dist/resources/extensions/gsd/state-reconciliation/drift/merge-state.js +247 -0
  39. package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +50 -0
  40. package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +87 -0
  41. package/dist/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.js +50 -0
  42. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +124 -0
  43. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +32 -0
  44. package/dist/resources/extensions/gsd/state-reconciliation/errors.js +41 -0
  45. package/dist/resources/extensions/gsd/state-reconciliation/index.js +99 -0
  46. package/dist/resources/extensions/gsd/state-reconciliation/registry.js +24 -0
  47. package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +43 -0
  48. package/dist/resources/extensions/gsd/state-reconciliation/types.js +3 -0
  49. package/dist/resources/extensions/gsd/state-reconciliation.js +5 -26
  50. package/dist/resources/extensions/gsd/tui/render-kit.js +74 -0
  51. package/dist/resources/extensions/gsd/watch/header-renderer.js +92 -69
  52. package/dist/resources/extensions/gsd/watch/splash-palette.js +10 -0
  53. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
  54. package/dist/resources/extensions/gsd/worktree-lifecycle.js +722 -316
  55. package/dist/resources/extensions/gsd/worktree-telemetry.js +3 -1
  56. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  57. package/dist/web/standalone/.next/BUILD_ID +1 -1
  58. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  59. package/dist/web/standalone/.next/build-manifest.json +2 -2
  60. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.html +1 -1
  77. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  84. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  86. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  87. package/dist/welcome-screen.d.ts +0 -7
  88. package/dist/welcome-screen.js +60 -69
  89. package/package.json +1 -1
  90. package/packages/daemon/package.json +2 -2
  91. package/packages/mcp-server/package.json +2 -2
  92. package/packages/native/package.json +1 -1
  93. package/packages/pi-agent-core/package.json +1 -1
  94. package/packages/pi-ai/package.json +1 -1
  95. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.d.ts +2 -0
  96. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.d.ts.map +1 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.js +47 -0
  98. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.js.map +1 -0
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +76 -9
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.d.ts +2 -0
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.d.ts.map +1 -0
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.js +40 -0
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.js.map +1 -0
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts +0 -1
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts.map +1 -1
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +30 -29
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -1
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js +10 -3
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.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 +13 -13
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +1 -3
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +58 -3
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts +2 -2
  119. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +12 -6
  121. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +14 -41
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +0 -1
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +86 -82
  128. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.d.ts +35 -0
  130. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.d.ts.map +1 -0
  131. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.js +152 -0
  132. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.js.map +1 -0
  133. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.d.ts +16 -0
  134. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.d.ts.map +1 -0
  135. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.js +73 -0
  136. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.js.map +1 -0
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +12 -8
  140. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.d.ts +2 -0
  142. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.d.ts.map +1 -0
  143. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.js +17 -0
  144. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.js.map +1 -0
  145. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  146. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +105 -1
  147. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  148. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  149. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +27 -26
  150. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +9 -6
  152. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -1
  153. package/packages/pi-coding-agent/package.json +1 -1
  154. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/assistant-message-design.test.ts +56 -0
  155. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +113 -9
  156. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/user-message-design.test.ts +48 -0
  157. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.test.ts +10 -3
  158. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +43 -42
  159. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +14 -14
  160. package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +64 -3
  161. package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +13 -7
  162. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +15 -42
  163. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -104
  164. package/packages/pi-coding-agent/src/modes/interactive/components/transcript-design.ts +196 -0
  165. package/packages/pi-coding-agent/src/modes/interactive/components/tui-style-kit.ts +94 -0
  166. package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +14 -9
  167. package/packages/pi-coding-agent/src/modes/interactive/theme/theme-highlight.test.ts +23 -0
  168. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +106 -1
  169. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +27 -26
  170. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +9 -6
  171. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  172. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +14 -1
  173. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  174. package/packages/pi-tui/dist/overlay-layout.d.ts.map +1 -1
  175. package/packages/pi-tui/dist/overlay-layout.js +9 -6
  176. package/packages/pi-tui/dist/overlay-layout.js.map +1 -1
  177. package/packages/pi-tui/package.json +1 -1
  178. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +20 -1
  179. package/packages/pi-tui/src/overlay-layout.ts +10 -7
  180. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  181. package/packages/rpc-client/package.json +1 -1
  182. package/pkg/dist/modes/interactive/theme/theme-highlight.test.d.ts +2 -0
  183. package/pkg/dist/modes/interactive/theme/theme-highlight.test.d.ts.map +1 -0
  184. package/pkg/dist/modes/interactive/theme/theme-highlight.test.js +17 -0
  185. package/pkg/dist/modes/interactive/theme/theme-highlight.test.js.map +1 -0
  186. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  187. package/pkg/dist/modes/interactive/theme/theme.js +105 -1
  188. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  189. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  190. package/pkg/dist/modes/interactive/theme/themes.js +27 -26
  191. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  192. package/pkg/package.json +1 -1
  193. package/src/resources/extensions/gsd/auto/loop-deps.ts +9 -5
  194. package/src/resources/extensions/gsd/auto/loop.ts +113 -9
  195. package/src/resources/extensions/gsd/auto/phases.ts +144 -19
  196. package/src/resources/extensions/gsd/auto/run-unit.ts +69 -4
  197. package/src/resources/extensions/gsd/auto/session.ts +10 -0
  198. package/src/resources/extensions/gsd/auto/verification-retry-policy.ts +82 -0
  199. package/src/resources/extensions/gsd/auto-dashboard.ts +230 -183
  200. package/src/resources/extensions/gsd/auto-dispatch.ts +15 -1
  201. package/src/resources/extensions/gsd/auto-post-unit.ts +7 -1
  202. package/src/resources/extensions/gsd/auto-recovery.ts +7 -209
  203. package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
  204. package/src/resources/extensions/gsd/auto-start.ts +22 -22
  205. package/src/resources/extensions/gsd/auto-unit-closeout.ts +51 -0
  206. package/src/resources/extensions/gsd/auto-verification.ts +12 -6
  207. package/src/resources/extensions/gsd/auto-worktree.ts +8 -0
  208. package/src/resources/extensions/gsd/auto.ts +295 -75
  209. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +21 -6
  210. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +6 -2
  211. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +5 -8
  212. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +4 -10
  213. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +12 -0
  214. package/src/resources/extensions/gsd/git-service.ts +2 -0
  215. package/src/resources/extensions/gsd/gsd-db.ts +7 -23
  216. package/src/resources/extensions/gsd/health-widget-core.ts +1 -1
  217. package/src/resources/extensions/gsd/health-widget.ts +6 -10
  218. package/src/resources/extensions/gsd/journal.ts +2 -0
  219. package/src/resources/extensions/gsd/markdown-renderer.ts +4 -95
  220. package/src/resources/extensions/gsd/native-git-bridge.ts +14 -13
  221. package/src/resources/extensions/gsd/notification-overlay.ts +50 -46
  222. package/src/resources/extensions/gsd/parallel-merge.ts +61 -34
  223. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +33 -35
  224. package/src/resources/extensions/gsd/prompts/complete-slice.md +14 -12
  225. package/src/resources/extensions/gsd/prompts/discuss-headless.md +20 -2
  226. package/src/resources/extensions/gsd/prompts/discuss.md +20 -2
  227. package/src/resources/extensions/gsd/recovery-classification.ts +18 -1
  228. package/src/resources/extensions/gsd/session-lock.ts +41 -0
  229. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +172 -0
  230. package/src/resources/extensions/gsd/state-reconciliation/drift/merge-state.ts +337 -0
  231. package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +69 -0
  232. package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +109 -0
  233. package/src/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.ts +68 -0
  234. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +185 -0
  235. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +46 -0
  236. package/src/resources/extensions/gsd/state-reconciliation/errors.ts +67 -0
  237. package/src/resources/extensions/gsd/state-reconciliation/index.ts +142 -0
  238. package/src/resources/extensions/gsd/state-reconciliation/registry.ts +27 -0
  239. package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +60 -0
  240. package/src/resources/extensions/gsd/state-reconciliation/types.ts +83 -0
  241. package/src/resources/extensions/gsd/state-reconciliation.ts +21 -53
  242. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +1 -1
  243. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +99 -0
  244. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +654 -176
  245. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +291 -4
  246. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +16 -1
  247. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +18 -0
  248. package/src/resources/extensions/gsd/tests/auto-unit-closeout.test.ts +68 -0
  249. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +28 -1
  250. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +20 -2
  251. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +44 -0
  252. package/src/resources/extensions/gsd/tests/header-renderer.test.ts +40 -0
  253. package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +10 -0
  254. package/src/resources/extensions/gsd/tests/health-widget.test.ts +14 -4
  255. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +26 -0
  256. package/src/resources/extensions/gsd/tests/integration/integration-proof.test.ts +1 -1
  257. package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +116 -24
  258. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -1
  259. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +1 -1
  260. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +46 -11
  261. package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +78 -41
  262. package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +44 -0
  263. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +12 -217
  264. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +38 -6
  265. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +2 -2
  266. package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +1 -1
  267. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +24 -1
  268. package/src/resources/extensions/gsd/tests/resume-dispatch-worktree.test.ts +7 -3
  269. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +6 -3
  270. package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +24 -0
  271. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +65 -58
  272. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +952 -0
  273. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -0
  274. package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +121 -1
  275. package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +66 -0
  276. package/src/resources/extensions/gsd/tests/verification-retry-policy.test.ts +83 -0
  277. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +6 -0
  278. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +158 -58
  279. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +572 -118
  280. package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +59 -2
  281. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +18 -0
  282. package/src/resources/extensions/gsd/tui/render-kit.ts +109 -0
  283. package/src/resources/extensions/gsd/watch/header-renderer.ts +121 -79
  284. package/src/resources/extensions/gsd/watch/splash-palette.ts +11 -0
  285. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
  286. package/src/resources/extensions/gsd/worktree-lifecycle.ts +1151 -524
  287. package/src/resources/extensions/gsd/worktree-telemetry.ts +7 -2
  288. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +0 -1544
  289. /package/dist/web/standalone/.next/static/{drLMkgfHQ8lzS229_HWYR → S44UQTFCUdA44dkjfYt6S}/_buildManifest.js +0 -0
  290. /package/dist/web/standalone/.next/static/{drLMkgfHQ8lzS229_HWYR → S44UQTFCUdA44dkjfYt6S}/_ssgManifest.js +0 -0
@@ -1,12 +1,5 @@
1
- /**
2
- * GSD Parallel Monitor Overlay
3
- *
4
- * Full-screen TUI overlay showing real-time parallel worker progress.
5
- * Opened via `/gsd parallel watch`, Ctrl+Alt+P (⌃⌥P on macOS),
6
- * or Ctrl+Shift+P fallback.
7
- * Reads the same data sources as `scripts/parallel-monitor.mjs` but
8
- * renders as a native pi-tui overlay with theme integration.
9
- */
1
+ // Project/App: GSD-2
2
+ // File Purpose: Parallel worker monitor overlay with width-safe operations-console rendering.
10
3
  import { existsSync, statSync, readFileSync, openSync, readSync, closeSync, readdirSync } from "node:fs";
11
4
  import { join } from "node:path";
12
5
  import { spawnSync } from "node:child_process";
@@ -14,6 +7,7 @@ import { matchesKey, Key } from "@gsd/pi-tui";
14
7
  import { formatDuration } from "../shared/mod.js";
15
8
  import { formattedShortcutPair } from "./shortcut-defs.js";
16
9
  import { resolveGsdPathContract } from "./paths.js";
10
+ import { renderBar, renderKeyHints, renderProgressBar, safeLine, statusGlyph, } from "./tui/render-kit.js";
17
11
  // ─── Data Helpers ─────────────────────────────────────────────────────────
18
12
  function readJsonSafe(filePath) {
19
13
  try {
@@ -226,17 +220,6 @@ function unitTypeLabel(unitType) {
226
220
  };
227
221
  return labels[unitType || ""] || (unitType || "---").toUpperCase().slice(0, 5);
228
222
  }
229
- function progressBar(done, total, width) {
230
- if (total === 0)
231
- return "░".repeat(width);
232
- const filled = Math.round((done / total) * width);
233
- return "█".repeat(filled) + "░".repeat(width - filled);
234
- }
235
- function healthGlyph(alive, heartbeatAge) {
236
- if (!alive)
237
- return "○";
238
- return "●";
239
- }
240
223
  // ─── Overlay Class ────────────────────────────────────────────────────────
241
224
  export class ParallelMonitorOverlay {
242
225
  tui;
@@ -247,6 +230,7 @@ export class ParallelMonitorOverlay {
247
230
  workers = [];
248
231
  events = [];
249
232
  cachedLines;
233
+ cachedWidth;
250
234
  scrollOffset = 0;
251
235
  disposed = false;
252
236
  resizeHandler = null;
@@ -279,6 +263,7 @@ export class ParallelMonitorOverlay {
279
263
  }
280
264
  this.events = this.events.slice(-10);
281
265
  this.cachedLines = undefined;
266
+ this.cachedWidth = undefined;
282
267
  this.tui.requestRender();
283
268
  }
284
269
  dispose() {
@@ -313,13 +298,14 @@ export class ParallelMonitorOverlay {
313
298
  }
314
299
  invalidate() {
315
300
  this.cachedLines = undefined;
301
+ this.cachedWidth = undefined;
316
302
  }
317
303
  render(width) {
318
- if (this.cachedLines)
304
+ if (this.cachedLines && this.cachedWidth === width)
319
305
  return this.cachedLines;
320
306
  const t = this.theme;
321
307
  const lines = [];
322
- const w = Math.max(width, 60);
308
+ const w = Math.max(1, width);
323
309
  // Header
324
310
  const totalCost = this.workers.reduce((s, wk) => s + wk.cost, 0);
325
311
  const aliveCount = this.workers.filter((wk) => wk.alive).length;
@@ -328,7 +314,7 @@ export class ParallelMonitorOverlay {
328
314
  lines.push(t.fg("muted", ` ${now} │ ${aliveCount}/${this.workers.length} alive │ Total: `) +
329
315
  t.bold(`$${totalCost.toFixed(2)}`) +
330
316
  t.fg("muted", " │ 5s refresh"));
331
- lines.push(t.fg("muted", "─".repeat(w)));
317
+ lines.push(renderBar(t, w));
332
318
  if (this.workers.length === 0) {
333
319
  lines.push("");
334
320
  lines.push(t.fg("warning", " No parallel workers found."));
@@ -339,7 +325,7 @@ export class ParallelMonitorOverlay {
339
325
  lines.push("");
340
326
  // Health + ID + state
341
327
  const healthColor = wk.alive ? "success" : "error";
342
- const glyph = healthGlyph(wk.alive, wk.heartbeatAge);
328
+ const glyph = statusGlyph(t, wk.alive ? "active" : "idle");
343
329
  const stateText = wk.alive
344
330
  ? t.fg("success", "RUNNING")
345
331
  : t.fg("error", t.bold("DEAD"));
@@ -374,21 +360,25 @@ export class ParallelMonitorOverlay {
374
360
  });
375
361
  lines.push(` ${t.fg("muted", "slices")} ${chips.join(" ")}`);
376
362
  // Task progress bar
377
- const bar = progressBar(wk.doneTasks, wk.totalTasks, 25);
363
+ const barWidth = Math.max(6, Math.min(25, w - 32));
364
+ const bar = renderProgressBar(t, wk.doneTasks, wk.totalTasks, barWidth, {
365
+ filledChar: "█",
366
+ emptyChar: "░",
367
+ emptyColor: "dim",
368
+ });
378
369
  const pct = wk.totalTasks > 0 ? Math.round((wk.doneTasks / wk.totalTasks) * 100) : 0;
379
- lines.push(` ${t.fg("muted", "tasks")} ${t.fg("success", bar)} ${wk.doneTasks}/${wk.totalTasks} ` +
370
+ lines.push(` ${t.fg("muted", "tasks")} ${bar} ${wk.doneTasks}/${wk.totalTasks} ` +
380
371
  t.fg("muted", `(${pct}%) │ slices done ${wk.doneSlices}/${wk.totalSlices}`));
381
372
  }
382
373
  // Errors
383
374
  for (const err of wk.errors.slice(-2)) {
384
- const truncated = err.length > w - 10 ? err.slice(0, w - 11) + "…" : err;
385
- lines.push(` ${t.fg("error", "⚠ " + truncated)}`);
375
+ lines.push(` ${t.fg("error", "! " + err)}`);
386
376
  }
387
377
  }
388
378
  }
389
379
  // Event feed
390
380
  lines.push("");
391
- lines.push(t.fg("muted", "─".repeat(w)));
381
+ lines.push(renderBar(t, w));
392
382
  lines.push(` ${t.bold("Recent Events")}`);
393
383
  if (this.events.length === 0) {
394
384
  lines.push(t.fg("muted", " No events yet..."));
@@ -396,8 +386,7 @@ export class ParallelMonitorOverlay {
396
386
  else {
397
387
  for (const evt of this.events.slice(-8)) {
398
388
  const mid = evt.match(/^✓ (M\d+)\//)?.[1] || "";
399
- const truncated = evt.length > w - 10 ? evt.slice(0, w - 11) + "" : evt;
400
- lines.push(` ${t.fg("muted", "│")} ${t.fg("accent", mid)} ${truncated.replace(/^✓ M\d+\//, "")}`);
389
+ lines.push(` ${t.fg("muted", "│")} ${t.fg("accent", mid)} ${evt.replace(/^✓ M\d+\//, "")}`);
401
390
  }
402
391
  }
403
392
  // Footer
@@ -411,13 +400,16 @@ export class ParallelMonitorOverlay {
411
400
  }
412
401
  lines.push(` ${t.bold("Total: $" + this.workers.reduce((s, wk) => s + wk.cost, 0).toFixed(2))}`);
413
402
  }
414
- lines.push(t.fg("muted", ` ESC/q/${formattedShortcutPair("parallel")} close↑↓ scroll`));
403
+ lines.push(renderKeyHints(t, [`ESC/q/${formattedShortcutPair("parallel")} close`, "↑↓ scroll"], w));
415
404
  // Apply scroll — use terminal rows as height estimate
416
405
  const termHeight = process.stdout.rows || 40;
417
406
  const maxScroll = Math.max(0, lines.length - termHeight);
418
407
  this.scrollOffset = Math.min(Math.max(this.scrollOffset, 0), maxScroll);
419
- const visible = lines.slice(this.scrollOffset, this.scrollOffset + termHeight);
408
+ const visible = lines
409
+ .slice(this.scrollOffset, this.scrollOffset + termHeight)
410
+ .map((line) => safeLine(line, w));
420
411
  this.cachedLines = visible;
412
+ this.cachedWidth = width;
421
413
  return visible;
422
414
  }
423
415
  }
@@ -22,22 +22,24 @@ Use `subagent` only for fresh-context review when useful: reviewer for cross-cut
22
22
 
23
23
  1. Use the inlined Slice Summary and UAT templates.
24
24
  2. {{skillActivation}}
25
- 3. Run all slice-level verification checks from the slice plan. Fix failures before marking done; refresh current state if needed.
26
- 4. Task summaries use a flat file layout under `tasks/` such as `T01-SUMMARY.md`, not inside per-task subdirectories like `tasks/T01/SUMMARY.md`. Never use `tasks/*/SUMMARY.md`.
27
- 5. If observability/diagnostics were planned, verify them unless the slice is simple.
28
- 6. Address every gate in Gates to Close. Q8 maps to **Operational Readiness**: health signal, failure signal, recovery procedure, monitoring gaps. Empty sections are recorded as omitted.
29
- 7. If requirement status changed, call `gsd_requirement_update`; do not write `.gsd/REQUIREMENTS.md` directly.
30
- 8. Prepare `gsd_slice_complete` content with camelCase fields `milestoneId`, `sliceId`, `sliceTitle`, `oneLiner`, `narrative`, `verification`, and `uatContent`.
31
- 9. Draft concrete UAT with preconditions, numbered steps, expected outcomes, edge cases, UAT Type, and Not Proven By This UAT.
32
- 10. Review the inlined task-summary excerpts for DECISIONS.md and KNOWLEDGE.md-worthy decisions, patterns, and gotchas. Read full `*-SUMMARY.md` files only when an excerpt is absent, truncated, or lacks the specific evidence needed for the slice narrative. Capture significant items with `capture_thought`; do not append knowledge files directly.
33
- 11. Call `gsd_slice_complete`. The DB-backed tool is the canonical write path. Do **not** manually write `{{sliceSummaryPath}}`. Do **not** manually write `{{sliceUatPath}}`. Do not edit roadmap checkboxes; the tool renders files and updates projections.
34
- 12. Do not run git commands.
35
- 13. Update `.gsd/PROJECT.md` with a full `write` only if the current project state needs refresh.
25
+ 3. Run all slice-level verification checks from the slice plan through the closeout-safe verification surface (`gsd_exec` / Context Mode verification evidence); refresh current state if needed. Do not use direct `bash` for verification commands.
26
+ 4. Complete the slice only when every required verification check passes. If verification fails or the fix requires source changes, do **not** edit source files in this unit and do **not** call `gsd_slice_complete`.
27
+ 5. For task-specific failures, call `gsd_task_reopen` with the failing completed task and a concrete reason so execution can redo the work. For plan-invalidating failures, call `gsd_replan_slice` with the blocker and updated execution tasks. Then stop with: "Slice {{sliceId}} needs execution follow-up."
28
+ 6. Task summaries use a flat file layout under `tasks/` such as `T01-SUMMARY.md`, not inside per-task subdirectories like `tasks/T01/SUMMARY.md`. Never use `tasks/*/SUMMARY.md`.
29
+ 7. If observability/diagnostics were planned, verify them unless the slice is simple.
30
+ 8. Address every gate in Gates to Close. Q8 maps to **Operational Readiness**: health signal, failure signal, recovery procedure, monitoring gaps. Empty sections are recorded as omitted.
31
+ 9. If requirement status changed, call `gsd_requirement_update`; do not write `.gsd/REQUIREMENTS.md` directly.
32
+ 10. Prepare `gsd_slice_complete` content with camelCase fields `milestoneId`, `sliceId`, `sliceTitle`, `oneLiner`, `narrative`, `verification`, and `uatContent`.
33
+ 11. Draft concrete UAT with preconditions, numbered steps, expected outcomes, edge cases, UAT Type, and Not Proven By This UAT.
34
+ 12. Review the inlined task-summary excerpts for DECISIONS.md and KNOWLEDGE.md-worthy decisions, patterns, and gotchas. Read full `*-SUMMARY.md` files only when an excerpt is absent, truncated, or lacks the specific evidence needed for the slice narrative. Capture significant items with `capture_thought`; do not append knowledge files directly.
35
+ 13. When verification passes, call `gsd_slice_complete`. The DB-backed tool is the canonical write path. Do **not** manually write `{{sliceSummaryPath}}`. Do **not** manually write `{{sliceUatPath}}`. Do not edit roadmap checkboxes; the tool renders files and updates projections.
36
+ 14. Do not run git commands.
37
+ 15. Update `.gsd/PROJECT.md` with a full `write` only if the current project state needs refresh.
36
38
 
37
39
  **Autonomous execution:** no human is available. Do not call `ask_user_questions` or `secure_env_collect`; make reasonable assumptions and document them.
38
40
 
39
41
  **File system safety:** if re-reading task summaries, use `find .gsd/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks -name "*-SUMMARY.md"` or `ls .gsd/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks/*-SUMMARY.md`. Never pass `{{slicePath}}` or any directory path directly to the `read` tool.
40
42
 
41
- **You MUST call `gsd_slice_complete` with summary and UAT content before finishing.**
43
+ **You MUST call `gsd_slice_complete` with summary and UAT content before finishing only after verification passes.**
42
44
 
43
45
  When done, say: "Slice {{sliceId}} complete."
@@ -129,7 +129,16 @@ If ANY box is unchecked, **STOP**. Do NOT emit the ready phrase. Emit the missin
129
129
 
130
130
  Do not announce the ready phrase as something you are "about to" do. The ready phrase is a post-write signal, not an intent signal.
131
131
 
132
- After completing steps 1–7 above, say exactly: "Milestone {{milestoneId}} ready." — nothing else. Auto-mode will start automatically.
132
+ After completing steps 1–7 above, end with this concise user-facing handoff:
133
+
134
+ ```
135
+ Milestone {{milestoneId}} ready.
136
+
137
+ Next steps:
138
+ - Run `/gsd auto` to start execution if it does not begin automatically.
139
+ - Use `/gsd status` or `/gsd visualize` to inspect the roadmap.
140
+ - Use `/gsd notifications` to review or configure delivery alerts.
141
+ ```
133
142
 
134
143
  ### Multi-Milestone
135
144
 
@@ -215,7 +224,16 @@ If ANY box is unchecked, **STOP**. Do NOT emit the ready phrase. Emit the missin
215
224
 
216
225
  Do not announce the ready phrase as something you are "about to" do. The ready phrase is a post-write signal, not an intent signal.
217
226
 
218
- After completing every step above, say exactly: "Milestone {{milestoneId}} ready." — nothing else. Auto-mode will start automatically.
227
+ After completing every step above, end with this concise user-facing handoff:
228
+
229
+ ```
230
+ Milestone {{milestoneId}} ready.
231
+
232
+ Next steps:
233
+ - Run `/gsd auto` to start execution if it does not begin automatically.
234
+ - Use `/gsd status` or `/gsd visualize` to inspect the roadmap.
235
+ - Use `/gsd notifications` to review or configure delivery alerts.
236
+ ```
219
237
 
220
238
  ## Critical Rules
221
239
 
@@ -252,7 +252,16 @@ If ANY box is unchecked, **STOP**. Do NOT emit the ready phrase. Emit the missin
252
252
 
253
253
  Do not announce the ready phrase as something you are "about to" do. It is a post-write signal, not intent.
254
254
 
255
- After completing steps 1–7 above, say exactly: "Milestone {{milestoneId}} ready." — nothing else. Auto-mode will start automatically.
255
+ After completing steps 1–7 above, end with this concise user-facing handoff:
256
+
257
+ ```
258
+ Milestone {{milestoneId}} ready.
259
+
260
+ Next steps:
261
+ - Run `/gsd auto` to start execution if it does not begin automatically.
262
+ - Use `/gsd status` or `/gsd visualize` to inspect the roadmap.
263
+ - Use `/gsd notifications` to review or configure delivery alerts.
264
+ ```
256
265
 
257
266
  ### Multi-Milestone
258
267
 
@@ -345,6 +354,15 @@ If ANY box is unchecked, **STOP**. Do NOT emit the ready phrase. Emit the missin
345
354
 
346
355
  Do not announce the ready phrase as something you are "about to" do. It is a post-write signal, not intent.
347
356
 
348
- After completing all phases above, say exactly: "Milestone M001 ready." — nothing else. Auto-mode will start automatically.
357
+ After completing all phases above, end with this concise user-facing handoff:
358
+
359
+ ```
360
+ Milestone M001 ready.
361
+
362
+ Next steps:
363
+ - Run `/gsd auto` to start execution if it does not begin automatically.
364
+ - Use `/gsd status` or `/gsd visualize` to inspect the roadmap.
365
+ - Use `/gsd notifications` to review or configure delivery alerts.
366
+ ```
349
367
 
350
368
  {{inlinedTemplates}}
@@ -1,9 +1,15 @@
1
1
  // Project/App: GSD-2
2
2
  // File Purpose: ADR-015 Recovery Classification module for runtime failure taxonomy.
3
3
  import { classifyError, isTransient } from "./error-classifier.js";
4
+ import { ReconciliationFailedError } from "./state-reconciliation.js";
4
5
  export function classifyFailure(input) {
5
6
  const message = errorMessage(input.error);
6
- const failureKind = input.failureKind ?? inferFailureKind(message);
7
+ // ADR-017: ReconciliationFailedError is a typed throw from the State
8
+ // Reconciliation Module. Recognize it by class regardless of caller-supplied
9
+ // failureKind so the taxonomy stays consistent.
10
+ const failureKind = input.error instanceof ReconciliationFailedError
11
+ ? "reconciliation-drift"
12
+ : input.failureKind ?? inferFailureKind(message);
7
13
  switch (failureKind) {
8
14
  case "tool-schema":
9
15
  return {
@@ -45,6 +51,14 @@ export function classifyFailure(input) {
45
51
  exitReason: "verification-drift",
46
52
  remediation: "Inspect the verification artifact and reconcile the state snapshot before resuming.",
47
53
  };
54
+ case "reconciliation-drift":
55
+ return {
56
+ failureKind,
57
+ action: "escalate",
58
+ reason: `Reconciliation drift${unitSuffix(input)}: ${message}`,
59
+ exitReason: "reconciliation-drift",
60
+ remediation: "Inspect the persistent or repair-failed drift kinds reported by the State Reconciliation Module before resuming.",
61
+ };
48
62
  case "provider": {
49
63
  const providerClass = classifyError(message, input.retryAfterMs);
50
64
  return {
@@ -521,6 +521,46 @@ export function readSessionLockData(basePath) {
521
521
  export function isSessionLockProcessAlive(data) {
522
522
  return isPidAlive(data.pid);
523
523
  }
524
+ /**
525
+ * ADR-017 raw primitive: remove orphaned lock artifacts (lock dir + lock file)
526
+ * when the recorded PID is dead or no metadata is present. Mirrors the
527
+ * pre-flight cleanup logic in acquireSessionLock so the stale-worker drift
528
+ * handler can clear the orphan proactively without going through the full
529
+ * acquire path. No-op when the lock is held by an alive process.
530
+ *
531
+ * Returns true when artifacts were removed (drift was present).
532
+ */
533
+ export function removeStaleSessionLock(basePath) {
534
+ const lp = lockPath(basePath);
535
+ const gsdDir = gsdRoot(basePath);
536
+ const lockTarget = effectiveLockTarget(gsdDir);
537
+ const lockDir = lockTarget + ".lock";
538
+ const existingData = readExistingLockData(lp);
539
+ const isOrphan = !existingData ||
540
+ (typeof existingData.pid === "number" && !isPidAlive(existingData.pid));
541
+ if (!isOrphan)
542
+ return false;
543
+ let removed = false;
544
+ if (existsSync(lockDir)) {
545
+ try {
546
+ rmSync(lockDir, { recursive: true, force: true });
547
+ removed = true;
548
+ }
549
+ catch {
550
+ /* best-effort */
551
+ }
552
+ }
553
+ if (existsSync(lp)) {
554
+ try {
555
+ unlinkSync(lp);
556
+ removed = true;
557
+ }
558
+ catch {
559
+ /* best-effort */
560
+ }
561
+ }
562
+ return removed;
563
+ }
524
564
  /**
525
565
  * Returns true if we currently hold a session lock for the given path.
526
566
  */
@@ -0,0 +1,131 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: ADR-017 missing-completion-timestamp drift handler. Detects
3
+ // tasks/slices/milestones marked complete (status = 'complete' | 'done') in
4
+ // the DB but whose `completed_at` column is null, and where the on-disk
5
+ // SUMMARY.md attests to completion. Backfills `completed_at` from the
6
+ // SUMMARY.md mtime — deterministic and idempotent (re-running yields the
7
+ // same value).
8
+ import { existsSync, statSync } from "node:fs";
9
+ import { getMilestone, getMilestoneSlices, getSliceTasks, isDbAvailable, updateMilestoneStatus, updateSliceStatus, updateTaskStatus, } from "../../gsd-db.js";
10
+ import { resolveMilestoneFile, resolveSliceFile, resolveTaskFile, } from "../../paths.js";
11
+ const COMPLETE_STATUSES = new Set(["complete", "done"]);
12
+ function summaryMtimeIso(path) {
13
+ try {
14
+ return statSync(path).mtime.toISOString();
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ }
20
+ export function detectMissingCompletionTimestampDrift(state, ctx) {
21
+ if (!isDbAvailable())
22
+ return [];
23
+ const mid = state.activeMilestone?.id;
24
+ if (!mid)
25
+ return [];
26
+ const milestone = getMilestone(mid);
27
+ if (!milestone)
28
+ return [];
29
+ const drifts = [];
30
+ // Milestone-level
31
+ if (COMPLETE_STATUSES.has(milestone.status) &&
32
+ milestone.completed_at === null) {
33
+ const summary = resolveMilestoneFile(ctx.basePath, mid, "SUMMARY");
34
+ if (summary && existsSync(summary)) {
35
+ drifts.push({
36
+ kind: "missing-completion-timestamp",
37
+ entity: "milestone",
38
+ ids: [mid],
39
+ });
40
+ }
41
+ }
42
+ // Slice and task levels iterate independently — tasks can complete before
43
+ // the parent slice closes, so task drift must be checked even when the
44
+ // slice is still pending.
45
+ for (const slice of getMilestoneSlices(mid)) {
46
+ if (COMPLETE_STATUSES.has(slice.status) &&
47
+ slice.completed_at === null) {
48
+ const summary = resolveSliceFile(ctx.basePath, mid, slice.id, "SUMMARY");
49
+ if (summary && existsSync(summary)) {
50
+ drifts.push({
51
+ kind: "missing-completion-timestamp",
52
+ entity: "slice",
53
+ ids: [`${mid}/${slice.id}`],
54
+ });
55
+ }
56
+ }
57
+ for (const task of getSliceTasks(mid, slice.id)) {
58
+ if (!COMPLETE_STATUSES.has(task.status))
59
+ continue;
60
+ if (task.completed_at !== null)
61
+ continue;
62
+ const taskSummary = resolveTaskFile(ctx.basePath, mid, slice.id, task.id, "SUMMARY");
63
+ if (taskSummary && existsSync(taskSummary)) {
64
+ drifts.push({
65
+ kind: "missing-completion-timestamp",
66
+ entity: "task",
67
+ ids: [`${mid}/${slice.id}/${task.id}`],
68
+ });
69
+ }
70
+ }
71
+ }
72
+ return drifts;
73
+ }
74
+ export function repairMissingCompletionTimestamp(record, ctx) {
75
+ const composite = record.ids[0];
76
+ if (!composite)
77
+ return;
78
+ const parts = composite.split("/");
79
+ if (record.entity === "milestone") {
80
+ const [mid] = parts;
81
+ if (!mid)
82
+ return;
83
+ const milestone = getMilestone(mid);
84
+ if (!milestone ||
85
+ milestone.completed_at !== null ||
86
+ !COMPLETE_STATUSES.has(milestone.status))
87
+ return;
88
+ const summary = resolveMilestoneFile(ctx.basePath, mid, "SUMMARY");
89
+ const ts = summary ? summaryMtimeIso(summary) : null;
90
+ if (!ts)
91
+ return;
92
+ updateMilestoneStatus(mid, milestone.status, ts);
93
+ return;
94
+ }
95
+ if (record.entity === "slice") {
96
+ const [mid, sid] = parts;
97
+ if (!mid || !sid)
98
+ return;
99
+ const slice = getMilestoneSlices(mid).find((s) => s.id === sid);
100
+ if (!slice ||
101
+ slice.completed_at !== null ||
102
+ !COMPLETE_STATUSES.has(slice.status))
103
+ return;
104
+ const summary = resolveSliceFile(ctx.basePath, mid, sid, "SUMMARY");
105
+ const ts = summary ? summaryMtimeIso(summary) : null;
106
+ if (!ts)
107
+ return;
108
+ updateSliceStatus(mid, sid, slice.status, ts);
109
+ return;
110
+ }
111
+ if (record.entity === "task") {
112
+ const [mid, sid, tid] = parts;
113
+ if (!mid || !sid || !tid)
114
+ return;
115
+ const task = getSliceTasks(mid, sid).find((t) => t.id === tid);
116
+ if (!task ||
117
+ task.completed_at !== null ||
118
+ !COMPLETE_STATUSES.has(task.status))
119
+ return;
120
+ const summary = resolveTaskFile(ctx.basePath, mid, sid, tid, "SUMMARY");
121
+ const ts = summary ? summaryMtimeIso(summary) : null;
122
+ if (!ts)
123
+ return;
124
+ updateTaskStatus(mid, sid, tid, task.status, ts);
125
+ }
126
+ }
127
+ export const completionTimestampHandler = {
128
+ kind: "missing-completion-timestamp",
129
+ detect: detectMissingCompletionTimestampDrift,
130
+ repair: repairMissingCompletionTimestamp,
131
+ };