gsd-pi 2.81.0 → 2.82.0-dev.c22380fc3

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 (521) hide show
  1. package/README.md +60 -30
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/browser-tools/tools/screenshot.js +1 -0
  4. package/dist/resources/extensions/browser-tools/tools/zoom.js +1 -0
  5. package/dist/resources/extensions/gsd/auto/loop.js +111 -8
  6. package/dist/resources/extensions/gsd/auto/orchestrator.js +113 -6
  7. package/dist/resources/extensions/gsd/auto/phases.js +199 -97
  8. package/dist/resources/extensions/gsd/auto/run-unit.js +66 -3
  9. package/dist/resources/extensions/gsd/auto/session.js +9 -0
  10. package/dist/resources/extensions/gsd/auto/verification-retry-policy.js +43 -0
  11. package/dist/resources/extensions/gsd/auto-dashboard.js +182 -178
  12. package/dist/resources/extensions/gsd/auto-dispatch.js +14 -11
  13. package/dist/resources/extensions/gsd/auto-post-unit.js +7 -1
  14. package/dist/resources/extensions/gsd/auto-prompts.js +11 -3
  15. package/dist/resources/extensions/gsd/auto-recovery.js +6 -181
  16. package/dist/resources/extensions/gsd/auto-runtime-state.js +5 -0
  17. package/dist/resources/extensions/gsd/auto-start.js +20 -23
  18. package/dist/resources/extensions/gsd/auto-unit-closeout.js +33 -5
  19. package/dist/resources/extensions/gsd/auto-verification.js +12 -6
  20. package/dist/resources/extensions/gsd/auto-worktree.js +8 -0
  21. package/dist/resources/extensions/gsd/auto.js +371 -106
  22. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +13 -6
  23. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +13 -2
  24. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +4 -8
  25. package/dist/resources/extensions/gsd/bootstrap/system-context.js +55 -12
  26. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -1
  27. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +4 -10
  28. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +9 -0
  29. package/dist/resources/extensions/gsd/commands-handlers.js +15 -2
  30. package/dist/resources/extensions/gsd/context-store.js +112 -0
  31. package/dist/resources/extensions/gsd/db-writer.js +150 -84
  32. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  33. package/dist/resources/extensions/gsd/doctor-git-checks.js +41 -6
  34. package/dist/resources/extensions/gsd/git-service.js +2 -1
  35. package/dist/resources/extensions/gsd/gsd-db.js +7 -23
  36. package/dist/resources/extensions/gsd/health-widget-core.js +1 -1
  37. package/dist/resources/extensions/gsd/health-widget.js +4 -10
  38. package/dist/resources/extensions/gsd/knowledge-backfill.js +144 -0
  39. package/dist/resources/extensions/gsd/knowledge-capture.js +136 -0
  40. package/dist/resources/extensions/gsd/knowledge-parser.js +154 -0
  41. package/dist/resources/extensions/gsd/knowledge-projection.js +210 -0
  42. package/dist/resources/extensions/gsd/markdown-renderer.js +6 -96
  43. package/dist/resources/extensions/gsd/md-importer.js +1 -1
  44. package/dist/resources/extensions/gsd/memory-backfill.js +73 -17
  45. package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +222 -0
  46. package/dist/resources/extensions/gsd/migrate/command.js +5 -0
  47. package/dist/resources/extensions/gsd/migrate/preview.js +9 -0
  48. package/dist/resources/extensions/gsd/migrate/transformer.js +51 -4
  49. package/dist/resources/extensions/gsd/migrate/writer.js +11 -1
  50. package/dist/resources/extensions/gsd/native-git-bridge.js +14 -14
  51. package/dist/resources/extensions/gsd/notification-overlay.js +35 -40
  52. package/dist/resources/extensions/gsd/parallel-merge.js +53 -30
  53. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +25 -33
  54. package/dist/resources/extensions/gsd/prompts/complete-slice.md +14 -12
  55. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +20 -2
  56. package/dist/resources/extensions/gsd/prompts/discuss.md +20 -2
  57. package/dist/resources/extensions/gsd/prompts/system.md +2 -2
  58. package/dist/resources/extensions/gsd/provider-switch-observer.js +146 -0
  59. package/dist/resources/extensions/gsd/recovery-classification.js +15 -1
  60. package/dist/resources/extensions/gsd/session-lock.js +40 -0
  61. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +131 -0
  62. package/dist/resources/extensions/gsd/state-reconciliation/drift/merge-state.js +247 -0
  63. package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +50 -0
  64. package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +87 -0
  65. package/dist/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.js +50 -0
  66. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +124 -0
  67. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +32 -0
  68. package/dist/resources/extensions/gsd/state-reconciliation/errors.js +41 -0
  69. package/dist/resources/extensions/gsd/state-reconciliation/index.js +99 -0
  70. package/dist/resources/extensions/gsd/state-reconciliation/registry.js +24 -0
  71. package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +43 -0
  72. package/dist/resources/extensions/gsd/state-reconciliation/types.js +3 -0
  73. package/dist/resources/extensions/gsd/state-reconciliation.js +5 -26
  74. package/dist/resources/extensions/gsd/templates/knowledge.md +2 -2
  75. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +119 -0
  76. package/dist/resources/extensions/gsd/tui/render-kit.js +74 -0
  77. package/dist/resources/extensions/gsd/watch/header-renderer.js +92 -69
  78. package/dist/resources/extensions/gsd/watch/splash-palette.js +10 -0
  79. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
  80. package/dist/resources/extensions/gsd/worktree-lifecycle.js +743 -318
  81. package/dist/resources/extensions/gsd/worktree-telemetry.js +3 -1
  82. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  83. package/dist/web/standalone/.next/BUILD_ID +1 -1
  84. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  85. package/dist/web/standalone/.next/build-manifest.json +3 -3
  86. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  87. package/dist/web/standalone/.next/required-server-files.json +3 -3
  88. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  89. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  99. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  108. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  115. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  127. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  147. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  157. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  163. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  179. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  183. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  185. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  187. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  189. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  190. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  191. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  192. package/dist/web/standalone/.next/server/app/index.html +1 -1
  193. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  194. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  195. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  196. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  197. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  198. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  199. package/dist/web/standalone/.next/server/app/page.js +2 -2
  200. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  201. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  202. package/dist/web/standalone/.next/server/chunks/63.js +3 -3
  203. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  204. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  205. package/dist/web/standalone/.next/server/middleware.js +2 -2
  206. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  207. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  208. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  209. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  210. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  211. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  212. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  213. package/dist/web/standalone/.next/static/chunks/app/page-752f1e2ebdaa3e45.js +1 -0
  214. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  215. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  216. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  217. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  218. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  219. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  220. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  221. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  222. package/dist/web/standalone/server.js +1 -1
  223. package/dist/welcome-screen.d.ts +0 -7
  224. package/dist/welcome-screen.js +60 -69
  225. package/package.json +3 -2
  226. package/packages/contracts/dist/rpc.test.js +7 -0
  227. package/packages/contracts/dist/rpc.test.js.map +1 -1
  228. package/packages/contracts/dist/workflow.d.ts +21 -0
  229. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  230. package/packages/contracts/dist/workflow.js +24 -0
  231. package/packages/contracts/dist/workflow.js.map +1 -1
  232. package/packages/contracts/src/rpc.test.ts +8 -0
  233. package/packages/contracts/src/workflow.ts +24 -0
  234. package/packages/daemon/package.json +2 -2
  235. package/packages/mcp-server/README.md +14 -3
  236. package/packages/mcp-server/dist/workflow-tools.d.ts +0 -3
  237. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  238. package/packages/mcp-server/dist/workflow-tools.js +80 -0
  239. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  240. package/packages/mcp-server/package.json +2 -2
  241. package/packages/mcp-server/src/workflow-tools-parity.test.ts +244 -0
  242. package/packages/mcp-server/src/workflow-tools.test.ts +22 -0
  243. package/packages/mcp-server/src/workflow-tools.ts +168 -0
  244. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  245. package/packages/native/package.json +1 -1
  246. package/packages/pi-agent-core/package.json +1 -1
  247. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  248. package/packages/pi-ai/dist/index.d.ts +2 -2
  249. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  250. package/packages/pi-ai/dist/index.js +1 -1
  251. package/packages/pi-ai/dist/index.js.map +1 -1
  252. package/packages/pi-ai/dist/providers/transform-messages.d.ts +11 -0
  253. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  254. package/packages/pi-ai/dist/providers/transform-messages.js +20 -0
  255. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  256. package/packages/pi-ai/package.json +1 -1
  257. package/packages/pi-ai/src/index.ts +7 -2
  258. package/packages/pi-ai/src/providers/transform-messages.ts +24 -0
  259. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  260. package/packages/pi-coding-agent/dist/core/system-prompt.js +4 -4
  261. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  262. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.d.ts +2 -0
  263. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.d.ts.map +1 -0
  264. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.js +47 -0
  265. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.js.map +1 -0
  266. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +76 -9
  267. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  268. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.d.ts +2 -0
  269. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.d.ts.map +1 -0
  270. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.js +40 -0
  271. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.js.map +1 -0
  272. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts +0 -1
  273. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts.map +1 -1
  274. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +30 -29
  275. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -1
  276. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js +10 -3
  277. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js.map +1 -1
  278. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  279. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +13 -13
  280. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  281. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +1 -3
  282. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  283. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +58 -3
  284. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
  285. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts +2 -2
  286. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts.map +1 -1
  287. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +12 -6
  288. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
  289. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  290. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +14 -41
  291. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  292. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +0 -1
  293. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  294. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +86 -82
  295. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  296. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.d.ts +35 -0
  297. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.d.ts.map +1 -0
  298. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.js +152 -0
  299. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.js.map +1 -0
  300. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.d.ts +16 -0
  301. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.d.ts.map +1 -0
  302. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.js +73 -0
  303. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.js.map +1 -0
  304. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +1 -1
  305. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  306. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +12 -8
  307. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
  308. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.d.ts +2 -0
  309. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.d.ts.map +1 -0
  310. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.js +17 -0
  311. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.js.map +1 -0
  312. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  313. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +105 -1
  314. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  315. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  316. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +27 -26
  317. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  318. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +9 -6
  319. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -1
  320. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts +2 -0
  321. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts.map +1 -0
  322. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js +17 -0
  323. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js.map +1 -0
  324. package/packages/pi-coding-agent/package.json +1 -1
  325. package/packages/pi-coding-agent/src/core/system-prompt.ts +4 -4
  326. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/assistant-message-design.test.ts +56 -0
  327. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +113 -9
  328. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/user-message-design.test.ts +48 -0
  329. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.test.ts +10 -3
  330. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +43 -42
  331. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +14 -14
  332. package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +64 -3
  333. package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +13 -7
  334. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +15 -42
  335. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -104
  336. package/packages/pi-coding-agent/src/modes/interactive/components/transcript-design.ts +196 -0
  337. package/packages/pi-coding-agent/src/modes/interactive/components/tui-style-kit.ts +94 -0
  338. package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +14 -9
  339. package/packages/pi-coding-agent/src/modes/interactive/theme/theme-highlight.test.ts +23 -0
  340. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +106 -1
  341. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +27 -26
  342. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +9 -6
  343. package/packages/pi-coding-agent/src/tests/system-prompt-file-safety.test.ts +22 -0
  344. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  345. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +14 -1
  346. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  347. package/packages/pi-tui/dist/overlay-layout.d.ts.map +1 -1
  348. package/packages/pi-tui/dist/overlay-layout.js +9 -6
  349. package/packages/pi-tui/dist/overlay-layout.js.map +1 -1
  350. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  351. package/packages/pi-tui/dist/tui.js +5 -0
  352. package/packages/pi-tui/dist/tui.js.map +1 -1
  353. package/packages/pi-tui/package.json +1 -1
  354. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +20 -1
  355. package/packages/pi-tui/src/overlay-layout.ts +10 -7
  356. package/packages/pi-tui/src/tui.ts +6 -0
  357. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  358. package/packages/rpc-client/package.json +1 -1
  359. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  360. package/pkg/dist/modes/interactive/theme/theme-highlight.test.d.ts +2 -0
  361. package/pkg/dist/modes/interactive/theme/theme-highlight.test.d.ts.map +1 -0
  362. package/pkg/dist/modes/interactive/theme/theme-highlight.test.js +17 -0
  363. package/pkg/dist/modes/interactive/theme/theme-highlight.test.js.map +1 -0
  364. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  365. package/pkg/dist/modes/interactive/theme/theme.js +105 -1
  366. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  367. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  368. package/pkg/dist/modes/interactive/theme/themes.js +27 -26
  369. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  370. package/pkg/package.json +1 -1
  371. package/src/resources/extensions/browser-tools/tools/screenshot.ts +1 -0
  372. package/src/resources/extensions/browser-tools/tools/zoom.ts +1 -0
  373. package/src/resources/extensions/gsd/auto/contracts.ts +46 -11
  374. package/src/resources/extensions/gsd/auto/loop-deps.ts +9 -5
  375. package/src/resources/extensions/gsd/auto/loop.ts +113 -9
  376. package/src/resources/extensions/gsd/auto/orchestrator.ts +118 -6
  377. package/src/resources/extensions/gsd/auto/phases.ts +158 -19
  378. package/src/resources/extensions/gsd/auto/run-unit.ts +69 -4
  379. package/src/resources/extensions/gsd/auto/session.ts +10 -0
  380. package/src/resources/extensions/gsd/auto/verification-retry-policy.ts +82 -0
  381. package/src/resources/extensions/gsd/auto-dashboard.ts +230 -183
  382. package/src/resources/extensions/gsd/auto-dispatch.ts +15 -1
  383. package/src/resources/extensions/gsd/auto-post-unit.ts +7 -1
  384. package/src/resources/extensions/gsd/auto-prompts.ts +11 -3
  385. package/src/resources/extensions/gsd/auto-recovery.ts +7 -209
  386. package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
  387. package/src/resources/extensions/gsd/auto-start.ts +22 -22
  388. package/src/resources/extensions/gsd/auto-unit-closeout.ts +51 -0
  389. package/src/resources/extensions/gsd/auto-verification.ts +12 -6
  390. package/src/resources/extensions/gsd/auto-worktree.ts +8 -0
  391. package/src/resources/extensions/gsd/auto.ts +411 -106
  392. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +21 -6
  393. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +12 -2
  394. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +5 -8
  395. package/src/resources/extensions/gsd/bootstrap/system-context.ts +58 -15
  396. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
  397. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +4 -10
  398. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +12 -0
  399. package/src/resources/extensions/gsd/commands-handlers.ts +19 -2
  400. package/src/resources/extensions/gsd/context-store.ts +120 -1
  401. package/src/resources/extensions/gsd/db-writer.ts +167 -84
  402. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  403. package/src/resources/extensions/gsd/doctor-git-checks.ts +44 -6
  404. package/src/resources/extensions/gsd/doctor-types.ts +2 -0
  405. package/src/resources/extensions/gsd/git-service.ts +2 -0
  406. package/src/resources/extensions/gsd/gsd-db.ts +7 -23
  407. package/src/resources/extensions/gsd/health-widget-core.ts +1 -1
  408. package/src/resources/extensions/gsd/health-widget.ts +6 -10
  409. package/src/resources/extensions/gsd/journal.ts +2 -0
  410. package/src/resources/extensions/gsd/knowledge-backfill.ts +164 -0
  411. package/src/resources/extensions/gsd/knowledge-capture.ts +160 -0
  412. package/src/resources/extensions/gsd/knowledge-parser.ts +174 -0
  413. package/src/resources/extensions/gsd/knowledge-projection.ts +241 -0
  414. package/src/resources/extensions/gsd/markdown-renderer.ts +10 -96
  415. package/src/resources/extensions/gsd/md-importer.ts +1 -1
  416. package/src/resources/extensions/gsd/memory-backfill.ts +89 -17
  417. package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +277 -0
  418. package/src/resources/extensions/gsd/migrate/command.ts +5 -0
  419. package/src/resources/extensions/gsd/migrate/preview.ts +10 -0
  420. package/src/resources/extensions/gsd/migrate/transformer.ts +58 -4
  421. package/src/resources/extensions/gsd/migrate/writer.ts +14 -1
  422. package/src/resources/extensions/gsd/native-git-bridge.ts +14 -13
  423. package/src/resources/extensions/gsd/notification-overlay.ts +50 -46
  424. package/src/resources/extensions/gsd/parallel-merge.ts +61 -34
  425. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +33 -35
  426. package/src/resources/extensions/gsd/prompts/complete-slice.md +14 -12
  427. package/src/resources/extensions/gsd/prompts/discuss-headless.md +20 -2
  428. package/src/resources/extensions/gsd/prompts/discuss.md +20 -2
  429. package/src/resources/extensions/gsd/prompts/system.md +2 -2
  430. package/src/resources/extensions/gsd/provider-switch-observer.ts +185 -0
  431. package/src/resources/extensions/gsd/recovery-classification.ts +18 -1
  432. package/src/resources/extensions/gsd/session-lock.ts +41 -0
  433. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +172 -0
  434. package/src/resources/extensions/gsd/state-reconciliation/drift/merge-state.ts +337 -0
  435. package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +69 -0
  436. package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +109 -0
  437. package/src/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.ts +68 -0
  438. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +185 -0
  439. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +46 -0
  440. package/src/resources/extensions/gsd/state-reconciliation/errors.ts +67 -0
  441. package/src/resources/extensions/gsd/state-reconciliation/index.ts +142 -0
  442. package/src/resources/extensions/gsd/state-reconciliation/registry.ts +27 -0
  443. package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +60 -0
  444. package/src/resources/extensions/gsd/state-reconciliation/types.ts +83 -0
  445. package/src/resources/extensions/gsd/state-reconciliation.ts +21 -53
  446. package/src/resources/extensions/gsd/templates/knowledge.md +2 -2
  447. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +1 -1
  448. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +99 -0
  449. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +729 -176
  450. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +408 -4
  451. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +292 -4
  452. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +20 -5
  453. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +18 -0
  454. package/src/resources/extensions/gsd/tests/auto-unit-closeout.test.ts +68 -0
  455. package/src/resources/extensions/gsd/tests/browser-tools-compatibility-declarations.test.ts +62 -0
  456. package/src/resources/extensions/gsd/tests/context-store-decisions-from-memories.test.ts +312 -0
  457. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +28 -1
  458. package/src/resources/extensions/gsd/tests/db-writer.test.ts +13 -8
  459. package/src/resources/extensions/gsd/tests/decisions-projection-from-memories.test.ts +453 -0
  460. package/src/resources/extensions/gsd/tests/decisions-stop-table-writes.test.ts +348 -0
  461. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +20 -2
  462. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +44 -0
  463. package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +8 -4
  464. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +11 -7
  465. package/src/resources/extensions/gsd/tests/header-renderer.test.ts +40 -0
  466. package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +10 -0
  467. package/src/resources/extensions/gsd/tests/health-widget.test.ts +14 -4
  468. package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +44 -0
  469. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +26 -0
  470. package/src/resources/extensions/gsd/tests/integration/integration-lifecycle.test.ts +13 -5
  471. package/src/resources/extensions/gsd/tests/integration/integration-proof.test.ts +1 -1
  472. package/src/resources/extensions/gsd/tests/integration/migrate-command.test.ts +48 -3
  473. package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +116 -24
  474. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -1
  475. package/src/resources/extensions/gsd/tests/knowledge-backfill-projection.test.ts +323 -0
  476. package/src/resources/extensions/gsd/tests/knowledge-capture.test.ts +242 -0
  477. package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -2
  478. package/src/resources/extensions/gsd/tests/load-knowledge-block-rules-only.test.ts +209 -0
  479. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +1 -1
  480. package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +316 -0
  481. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +46 -11
  482. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +5 -1
  483. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +6 -1
  484. package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +78 -41
  485. package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +44 -0
  486. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +12 -217
  487. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +38 -6
  488. package/src/resources/extensions/gsd/tests/plan-milestone-sketch-render.test.ts +157 -0
  489. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +2 -2
  490. package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +1 -1
  491. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +32 -1
  492. package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +252 -0
  493. package/src/resources/extensions/gsd/tests/resume-dispatch-worktree.test.ts +7 -3
  494. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +6 -3
  495. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +16 -4
  496. package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +24 -0
  497. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +71 -58
  498. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +952 -0
  499. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -0
  500. package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +121 -1
  501. package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +66 -0
  502. package/src/resources/extensions/gsd/tests/verification-retry-policy.test.ts +83 -0
  503. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +6 -0
  504. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +158 -58
  505. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +597 -118
  506. package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +59 -2
  507. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +18 -0
  508. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +135 -0
  509. package/src/resources/extensions/gsd/tui/render-kit.ts +109 -0
  510. package/src/resources/extensions/gsd/watch/header-renderer.ts +121 -79
  511. package/src/resources/extensions/gsd/watch/splash-palette.ts +11 -0
  512. package/src/resources/extensions/gsd/workflow-logger.ts +4 -0
  513. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
  514. package/src/resources/extensions/gsd/worktree-lifecycle.ts +1171 -526
  515. package/src/resources/extensions/gsd/worktree-telemetry.ts +7 -2
  516. package/dist/web/standalone/.next/static/chunks/app/page-200592a7f3baf579.js +0 -1
  517. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  518. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  519. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +0 -1544
  520. /package/dist/web/standalone/.next/static/{drLMkgfHQ8lzS229_HWYR → Wop3A7KRGyR06H3rla_1-}/_buildManifest.js +0 -0
  521. /package/dist/web/standalone/.next/static/{drLMkgfHQ8lzS229_HWYR → Wop3A7KRGyR06H3rla_1-}/_ssgManifest.js +0 -0
@@ -0,0 +1,952 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: ADR-017 contract tests for drift-driven State Reconciliation.
3
+ // Covers sketch-flag (#5700), merge-state (#5701), stale-render (#5702),
4
+ // stale-worker (#5703), unregistered-milestone (#5704), roadmap-divergence
5
+ // (#5705), and missing-completion-timestamp (#5706) drift end-to-end, plus
6
+ // the repair-throw and persistent-drift error paths and Recovery
7
+ // Classification mapping for ReconciliationFailedError.
8
+
9
+ import test from "node:test";
10
+ import assert from "node:assert/strict";
11
+ import { execFileSync } from "node:child_process";
12
+ import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { tmpdir } from "node:os";
15
+ import { randomUUID } from "node:crypto";
16
+
17
+ import {
18
+ openDatabase,
19
+ closeDatabase,
20
+ insertMilestone,
21
+ insertSlice,
22
+ insertTask,
23
+ getMilestone,
24
+ getSlice,
25
+ getSliceTasks,
26
+ setSliceSummaryMd,
27
+ updateSliceStatus,
28
+ updateTaskStatus,
29
+ } from "../gsd-db.ts";
30
+ import { clearParseCache } from "../files.ts";
31
+ import { clearPathCache } from "../paths.ts";
32
+ import { detectStaleRenders } from "../markdown-renderer.ts";
33
+ import { invalidateStateCache } from "../state.ts";
34
+ import {
35
+ reconcileBeforeDispatch,
36
+ reconcileBeforeSpawn,
37
+ ReconciliationFailedError,
38
+ type DriftHandler,
39
+ type DriftRecord,
40
+ } from "../state-reconciliation.ts";
41
+ import { classifyFailure } from "../recovery-classification.ts";
42
+ import type { GSDState } from "../types.ts";
43
+
44
+ function makeState(overrides: Partial<GSDState> = {}): GSDState {
45
+ return {
46
+ activeMilestone: { id: "M001", title: "Milestone" },
47
+ activeSlice: null,
48
+ activeTask: null,
49
+ phase: "planning",
50
+ recentDecisions: [],
51
+ blockers: [],
52
+ nextAction: "Plan milestone",
53
+ registry: [],
54
+ requirements: { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 },
55
+ progress: { milestones: { done: 0, total: 1 } },
56
+ ...overrides,
57
+ };
58
+ }
59
+
60
+ function makeFixtureBase(): string {
61
+ const base = mkdtempSync(join(tmpdir(), "gsd-adr017-drift-"));
62
+ mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S02"), { recursive: true });
63
+ return base;
64
+ }
65
+
66
+ function cleanup(base: string): void {
67
+ try {
68
+ closeDatabase();
69
+ } catch {
70
+ /* noop */
71
+ }
72
+ try {
73
+ rmSync(base, { recursive: true, force: true });
74
+ } catch {
75
+ /* noop */
76
+ }
77
+ }
78
+
79
+ test("ADR-017 (#5700): sketch-flag drift detected and repaired end-to-end", async (t) => {
80
+ const base = makeFixtureBase();
81
+ t.after(() => cleanup(base));
82
+
83
+ openDatabase(join(base, ".gsd", "gsd.db"));
84
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
85
+ insertSlice({
86
+ id: "S02",
87
+ milestoneId: "M001",
88
+ title: "Feature",
89
+ status: "pending",
90
+ risk: "medium",
91
+ depends: [],
92
+ demo: "S02 demo.",
93
+ sequence: 1,
94
+ isSketch: true,
95
+ sketchScope: "limited",
96
+ });
97
+
98
+ // Simulate the post-crash scenario: PLAN.md exists on disk but the
99
+ // is_sketch flag is still 1.
100
+ writeFileSync(
101
+ join(base, ".gsd", "milestones", "M001", "slices", "S02", "S02-PLAN.md"),
102
+ "# S02 Plan\n",
103
+ );
104
+ assert.equal(getSlice("M001", "S02")?.is_sketch, 1, "pre: flagged as sketch");
105
+
106
+ const state = makeState({ activeMilestone: { id: "M001", title: "Test" } });
107
+ const result = await reconcileBeforeDispatch(base, {
108
+ invalidateStateCache: () => {},
109
+ deriveState: async () => state,
110
+ });
111
+
112
+ assert.equal(result.ok, true);
113
+ assert.equal(getSlice("M001", "S02")?.is_sketch, 0, "post: flag cleared");
114
+ assert.equal(result.repaired.length, 1);
115
+ assert.equal(result.repaired[0]?.kind, "stale-sketch-flag");
116
+ if (result.repaired[0]?.kind === "stale-sketch-flag") {
117
+ assert.equal(result.repaired[0].mid, "M001");
118
+ assert.equal(result.repaired[0].sid, "S02");
119
+ }
120
+ });
121
+
122
+ test("ADR-017 (#5700): repair failure throws ReconciliationFailedError with shape", async () => {
123
+ const seenDrift: DriftRecord = { kind: "stale-sketch-flag", mid: "M001", sid: "S02" };
124
+ const handler: DriftHandler = {
125
+ kind: "stale-sketch-flag",
126
+ detect: () => [seenDrift],
127
+ repair: () => {
128
+ throw new Error("simulated repair failure");
129
+ },
130
+ };
131
+
132
+ await assert.rejects(
133
+ () =>
134
+ reconcileBeforeDispatch("/project", {
135
+ invalidateStateCache: () => {},
136
+ deriveState: async () => makeState(),
137
+ registry: [handler],
138
+ }),
139
+ (err: unknown) => {
140
+ assert.ok(err instanceof ReconciliationFailedError, "must be ReconciliationFailedError");
141
+ assert.equal(err.failures.length, 1);
142
+ assert.equal(err.failures[0]?.drift.kind, "stale-sketch-flag");
143
+ assert.ok(err.failures[0]?.cause instanceof Error);
144
+ assert.equal((err.failures[0]?.cause as Error).message, "simulated repair failure");
145
+ assert.equal(err.pass, 0);
146
+ assert.equal(err.persistentDrift.length, 0);
147
+ return true;
148
+ },
149
+ );
150
+ });
151
+
152
+ test("ADR-017 (#5700): detector failure throws ReconciliationFailedError with shape", async () => {
153
+ const handler: DriftHandler = {
154
+ kind: "stale-sketch-flag",
155
+ detect: () => {
156
+ throw new Error("simulated detect failure");
157
+ },
158
+ repair: () => {
159
+ /* detect fails before repair */
160
+ },
161
+ };
162
+
163
+ await assert.rejects(
164
+ () =>
165
+ reconcileBeforeDispatch("/project", {
166
+ invalidateStateCache: () => {},
167
+ deriveState: async () => makeState(),
168
+ registry: [handler],
169
+ }),
170
+ (err: unknown) => {
171
+ assert.ok(err instanceof ReconciliationFailedError, "must be ReconciliationFailedError");
172
+ assert.equal(err.failures.length, 1);
173
+ assert.equal(err.failures[0]?.drift.kind, "stale-sketch-flag");
174
+ assert.ok(err.failures[0]?.cause instanceof Error);
175
+ assert.equal((err.failures[0]?.cause as Error).message, "simulated detect failure");
176
+ assert.equal(err.pass, 0);
177
+ assert.equal(err.detectionFailures.length, 0);
178
+ assert.equal(err.persistentDrift.length, 0);
179
+ return true;
180
+ },
181
+ );
182
+ });
183
+
184
+ test("ADR-017 (#5700): persistent drift after cap=2 throws ReconciliationFailedError", async () => {
185
+ // Detect always returns one drift; repair is a no-op (drift never goes away).
186
+ const persistent: DriftRecord = { kind: "stale-sketch-flag", mid: "M001", sid: "S02" };
187
+ const handler: DriftHandler = {
188
+ kind: "stale-sketch-flag",
189
+ detect: () => [persistent],
190
+ repair: () => {
191
+ /* no-op: drift cannot be cleared */
192
+ },
193
+ };
194
+
195
+ await assert.rejects(
196
+ () =>
197
+ reconcileBeforeDispatch("/project", {
198
+ invalidateStateCache: () => {},
199
+ deriveState: async () => makeState(),
200
+ registry: [handler],
201
+ }),
202
+ (err: unknown) => {
203
+ assert.ok(err instanceof ReconciliationFailedError);
204
+ assert.equal(err.failures.length, 0);
205
+ assert.equal(err.persistentDrift.length, 1);
206
+ assert.equal(err.persistentDrift[0]?.kind, "stale-sketch-flag");
207
+ return true;
208
+ },
209
+ );
210
+ });
211
+
212
+ test("ADR-017 (#5700): classifyFailure recognizes ReconciliationFailedError", () => {
213
+ const err = new ReconciliationFailedError({
214
+ failures: [
215
+ {
216
+ drift: { kind: "stale-sketch-flag", mid: "M001", sid: "S02" },
217
+ cause: new Error("boom"),
218
+ },
219
+ ],
220
+ pass: 0,
221
+ });
222
+
223
+ const result = classifyFailure({ error: err });
224
+
225
+ assert.equal(result.failureKind, "reconciliation-drift");
226
+ assert.equal(result.action, "escalate");
227
+ assert.equal(result.exitReason, "reconciliation-drift");
228
+ assert.match(result.remediation, /persistent or repair-failed drift kinds/);
229
+ });
230
+
231
+ // ─── #5701: merge-state drift ────────────────────────────────────────────────
232
+
233
+ function makeGitBase(): string {
234
+ const base = join(tmpdir(), `gsd-adr017-merge-${randomUUID()}`);
235
+ mkdirSync(base, { recursive: true });
236
+ execFileSync("git", ["init", "--initial-branch=main"], { cwd: base, stdio: "ignore" });
237
+ execFileSync("git", ["config", "user.email", "test@test.com"], { cwd: base, stdio: "ignore" });
238
+ execFileSync("git", ["config", "user.name", "Test"], { cwd: base, stdio: "ignore" });
239
+ writeFileSync(join(base, ".gitkeep"), "");
240
+ execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
241
+ execFileSync("git", ["commit", "-m", "initial"], { cwd: base, stdio: "ignore" });
242
+ return base;
243
+ }
244
+
245
+ function rmTreeQuiet(base: string): void {
246
+ try {
247
+ rmSync(base, { recursive: true, force: true });
248
+ } catch {
249
+ /* noop */
250
+ }
251
+ }
252
+
253
+ test("ADR-017 (#5701): merge-state drift detected and repaired end-to-end", async (t) => {
254
+ const base = makeGitBase();
255
+ t.after(() => rmTreeQuiet(base));
256
+
257
+ // Build a clean fast-forward-resolvable merge: feature branch with one file,
258
+ // then start merge --no-commit on main so MERGE_HEAD exists with no conflicts.
259
+ execFileSync("git", ["checkout", "-b", "feature"], { cwd: base, stdio: "ignore" });
260
+ writeFileSync(join(base, "feature.txt"), "feature content");
261
+ execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
262
+ execFileSync("git", ["commit", "-m", "add feature"], { cwd: base, stdio: "ignore" });
263
+ execFileSync("git", ["checkout", "main"], { cwd: base, stdio: "ignore" });
264
+ execFileSync("git", ["merge", "--no-ff", "--no-commit", "feature"], { cwd: base, stdio: "ignore" });
265
+
266
+ assert.ok(existsSync(join(base, ".git", "MERGE_HEAD")), "pre: MERGE_HEAD exists");
267
+
268
+ const result = await reconcileBeforeDispatch(base, {
269
+ invalidateStateCache: () => {},
270
+ deriveState: async () => makeState(),
271
+ });
272
+
273
+ assert.equal(result.ok, true);
274
+ assert.equal(
275
+ existsSync(join(base, ".git", "MERGE_HEAD")),
276
+ false,
277
+ "post: MERGE_HEAD cleared after reconciliation",
278
+ );
279
+ const mergeRepaired = result.repaired.find((d) => d.kind === "unmerged-merge-state");
280
+ assert.ok(mergeRepaired, "repaired list should include the merge-state drift record");
281
+ if (mergeRepaired?.kind === "unmerged-merge-state") {
282
+ assert.equal(mergeRepaired.basePath, base);
283
+ }
284
+ });
285
+
286
+ test("ADR-017 (#5701): merge-state drift is detected in linked worktrees", async (t) => {
287
+ const base = makeGitBase();
288
+ const worktree = join(tmpdir(), `gsd-adr017-worktree-${randomUUID()}`);
289
+ t.after(() => {
290
+ rmTreeQuiet(worktree);
291
+ rmTreeQuiet(base);
292
+ });
293
+
294
+ execFileSync("git", ["checkout", "-b", "feature"], { cwd: base, stdio: "ignore" });
295
+ writeFileSync(join(base, "feature.txt"), "feature content");
296
+ execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
297
+ execFileSync("git", ["commit", "-m", "add feature"], { cwd: base, stdio: "ignore" });
298
+ execFileSync("git", ["checkout", "main"], { cwd: base, stdio: "ignore" });
299
+ execFileSync("git", ["worktree", "add", "-b", "wt-main", worktree, "main"], {
300
+ cwd: base,
301
+ stdio: "ignore",
302
+ });
303
+ execFileSync("git", ["merge", "--no-ff", "--no-commit", "feature"], {
304
+ cwd: worktree,
305
+ stdio: "ignore",
306
+ });
307
+
308
+ const mergeHeadPath = execFileSync("git", ["rev-parse", "--git-path", "MERGE_HEAD"], {
309
+ cwd: worktree,
310
+ encoding: "utf-8",
311
+ }).trim();
312
+ assert.ok(existsSync(mergeHeadPath), "pre: MERGE_HEAD exists in resolved worktree gitdir");
313
+ assert.equal(existsSync(join(worktree, ".git", "MERGE_HEAD")), false);
314
+
315
+ const result = await reconcileBeforeDispatch(worktree, {
316
+ invalidateStateCache: () => {},
317
+ deriveState: async () => makeState(),
318
+ });
319
+
320
+ assert.equal(result.ok, true);
321
+ assert.equal(existsSync(mergeHeadPath), false, "post: MERGE_HEAD cleared after reconciliation");
322
+ assert.ok(
323
+ result.repaired.some((d) => d.kind === "unmerged-merge-state"),
324
+ "repaired list should include the worktree merge-state drift record",
325
+ );
326
+ });
327
+
328
+ test("ADR-017 (#5701): no merge state → detector returns no drift", async (t) => {
329
+ const base = makeGitBase();
330
+ t.after(() => rmTreeQuiet(base));
331
+
332
+ const result = await reconcileBeforeDispatch(base, {
333
+ invalidateStateCache: () => {},
334
+ deriveState: async () => makeState(),
335
+ });
336
+
337
+ assert.equal(result.ok, true);
338
+ assert.equal(
339
+ result.repaired.some((d) => d.kind === "unmerged-merge-state"),
340
+ false,
341
+ "no merge drift should be reported when the repo is clean",
342
+ );
343
+ });
344
+
345
+ // ─── #5702: stale-render drift ───────────────────────────────────────────────
346
+
347
+ function clearRendererCaches(): void {
348
+ clearParseCache();
349
+ clearPathCache();
350
+ invalidateStateCache();
351
+ }
352
+
353
+ function makeStalePlanContent(sliceId: string, tasks: Array<{ id: string; title: string; done: boolean }>): string {
354
+ const lines: string[] = [];
355
+ lines.push(`# ${sliceId}: Test Slice`);
356
+ lines.push("");
357
+ lines.push("**Goal:** Test slice goal");
358
+ lines.push("**Demo:** Test demo");
359
+ lines.push("");
360
+ lines.push("## Must-Haves");
361
+ lines.push("");
362
+ lines.push("- Everything works");
363
+ lines.push("");
364
+ lines.push("## Tasks");
365
+ lines.push("");
366
+ for (const t of tasks) {
367
+ const checkbox = t.done ? "[x]" : "[ ]";
368
+ lines.push(`- ${checkbox} **${t.id}: ${t.title}** \`est:1h\``);
369
+ }
370
+ lines.push("");
371
+ return lines.join("\n");
372
+ }
373
+
374
+ function makeStaleRoadmapContent(slices: Array<{ id: string; title: string; done: boolean }>): string {
375
+ const lines: string[] = [];
376
+ lines.push("# M001 Roadmap");
377
+ lines.push("");
378
+ lines.push("**Vision:** Test milestone");
379
+ lines.push("");
380
+ lines.push("## Slices");
381
+ lines.push("");
382
+ for (const s of slices) {
383
+ const checkbox = s.done ? "[x]" : "[ ]";
384
+ lines.push(`- ${checkbox} **${s.id}: ${s.title}** \`risk:medium\` \`depends:[]\``);
385
+ }
386
+ lines.push("");
387
+ return lines.join("\n");
388
+ }
389
+
390
+ test("ADR-017 (#5702): stale-render drift detected and repaired end-to-end", async (t) => {
391
+ const base = mkdtempSync(join(tmpdir(), "gsd-adr017-render-"));
392
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
393
+ mkdirSync(join(sliceDir, "tasks"), { recursive: true });
394
+ t.after(() => {
395
+ try { closeDatabase(); } catch { /* noop */ }
396
+ rmTreeQuiet(base);
397
+ });
398
+
399
+ openDatabase(join(base, ".gsd", "gsd.db"));
400
+ clearRendererCaches();
401
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
402
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "pending" });
403
+ insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "First task", status: "done" });
404
+ insertTask({ id: "T02", sliceId: "S01", milestoneId: "M001", title: "Second task", status: "done" });
405
+
406
+ // Plan with both tasks unchecked — DB says done, file disagrees.
407
+ const planPath = join(sliceDir, "S01-PLAN.md");
408
+ writeFileSync(planPath, makeStalePlanContent("S01", [
409
+ { id: "T01", title: "First task", done: false },
410
+ { id: "T02", title: "Second task", done: false },
411
+ ]));
412
+ clearRendererCaches();
413
+
414
+ const result = await reconcileBeforeDispatch(base, {
415
+ invalidateStateCache: () => {},
416
+ deriveState: async () => makeState(),
417
+ });
418
+
419
+ assert.equal(result.ok, true);
420
+ const renderRepaired = result.repaired.find((d) => d.kind === "stale-render");
421
+ assert.ok(renderRepaired, "repaired list should include the stale-render drift");
422
+
423
+ const repairedContent = readFileSync(planPath, "utf-8");
424
+ assert.match(repairedContent, /\[x\][^\n]*T01:/, "T01 checkbox should be checked after repair");
425
+ assert.match(repairedContent, /\[x\][^\n]*T02:/, "T02 checkbox should be checked after repair");
426
+ });
427
+
428
+ test("ADR-017 (#5702): stale-render detector reason strings match repair contract", (t) => {
429
+ const base = mkdtempSync(join(tmpdir(), "gsd-adr017-render-reasons-"));
430
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
431
+ mkdirSync(join(sliceDir, "tasks"), { recursive: true });
432
+ t.after(() => {
433
+ try { closeDatabase(); } catch { /* noop */ }
434
+ rmTreeQuiet(base);
435
+ });
436
+
437
+ openDatabase(join(base, ".gsd", "gsd.db"));
438
+ clearRendererCaches();
439
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
440
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "complete" });
441
+ insertTask({
442
+ id: "T01",
443
+ sliceId: "S01",
444
+ milestoneId: "M001",
445
+ title: "First task",
446
+ status: "done",
447
+ fullSummaryMd: "# T01 Summary\n",
448
+ });
449
+ setSliceSummaryMd("M001", "S01", "# S01 Summary\n", "# S01 UAT\n");
450
+
451
+ writeFileSync(
452
+ join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
453
+ makeStaleRoadmapContent([{ id: "S01", title: "Slice", done: false }]),
454
+ );
455
+ writeFileSync(
456
+ join(sliceDir, "S01-PLAN.md"),
457
+ makeStalePlanContent("S01", [{ id: "T01", title: "First task", done: false }]),
458
+ );
459
+ clearRendererCaches();
460
+
461
+ const reasons = detectStaleRenders(base).map((entry) => entry.reason).sort();
462
+
463
+ assert.deepEqual(reasons, [
464
+ "S01 is closed in DB but unchecked in roadmap",
465
+ "S01 is complete with UAT in DB but UAT.md missing on disk",
466
+ "S01 is complete with summary in DB but SUMMARY.md missing on disk",
467
+ "T01 is complete with summary in DB but SUMMARY.md missing on disk",
468
+ "T01 is done in DB but unchecked in plan",
469
+ ].sort());
470
+ });
471
+
472
+ // ─── #5703: stale-worker drift ───────────────────────────────────────────────
473
+
474
+ const DEAD_PID = 999_999_999; // far above any realistic system PID; process.kill(pid, 0) → ESRCH
475
+
476
+ function writeFakeSessionLock(base: string, pid: number): string {
477
+ const gsdDir = join(base, ".gsd");
478
+ mkdirSync(gsdDir, { recursive: true });
479
+ const lockFile = join(gsdDir, "auto.lock");
480
+ // Mirror SessionLockData minimum shape
481
+ writeFileSync(
482
+ lockFile,
483
+ JSON.stringify({
484
+ pid,
485
+ startedAt: new Date().toISOString(),
486
+ unitType: "starting",
487
+ unitId: "bootstrap",
488
+ }),
489
+ );
490
+ // Also create the proper-lockfile directory artifact at <gsdDir>.lock
491
+ mkdirSync(`${gsdDir}.lock`, { recursive: true });
492
+ return lockFile;
493
+ }
494
+
495
+ test("ADR-017 (#5703): stale-worker drift detected and orphaned lock cleared", async (t) => {
496
+ const base = mkdtempSync(join(tmpdir(), "gsd-adr017-worker-"));
497
+ t.after(() => rmSync(base, { recursive: true, force: true }));
498
+
499
+ const lockFile = writeFakeSessionLock(base, DEAD_PID);
500
+ assert.ok(existsSync(lockFile), "pre: lock file written");
501
+
502
+ const result = await reconcileBeforeDispatch(base, {
503
+ invalidateStateCache: () => {},
504
+ deriveState: async () => makeState(),
505
+ });
506
+
507
+ assert.equal(result.ok, true);
508
+ assert.equal(existsSync(lockFile), false, "post: orphaned lock file removed");
509
+ const workerRepaired = result.repaired.find((d) => d.kind === "stale-worker");
510
+ assert.ok(workerRepaired, "repaired list should include the stale-worker drift");
511
+ if (workerRepaired?.kind === "stale-worker") {
512
+ assert.equal(workerRepaired.pid, DEAD_PID);
513
+ }
514
+ });
515
+
516
+ test("ADR-017 (#5703): live worker lock is not cleared", async (t) => {
517
+ const base = mkdtempSync(join(tmpdir(), "gsd-adr017-worker-live-"));
518
+ t.after(() => rmSync(base, { recursive: true, force: true }));
519
+
520
+ // PID 1 (init/launchd): process.kill(1, 0) returns EPERM as non-root, which
521
+ // isPidAlive correctly treats as alive. process.pid would be rejected by the
522
+ // self-PID guard in isPidAlive (treated as not alive).
523
+ const ALIVE_PID = 1;
524
+ const lockFile = writeFakeSessionLock(base, ALIVE_PID);
525
+ assert.ok(existsSync(lockFile), "pre: lock file written");
526
+
527
+ const result = await reconcileBeforeDispatch(base, {
528
+ invalidateStateCache: () => {},
529
+ deriveState: async () => makeState(),
530
+ });
531
+
532
+ assert.equal(result.ok, true);
533
+ assert.equal(
534
+ existsSync(lockFile),
535
+ true,
536
+ "live lock must NOT be cleared (would steal the lock from a running session)",
537
+ );
538
+ assert.equal(
539
+ result.repaired.some((d) => d.kind === "stale-worker"),
540
+ false,
541
+ "no stale-worker drift should be reported when the lock owner is alive",
542
+ );
543
+ });
544
+
545
+ // ─── #5704: unregistered-milestone drift ────────────────────────────────────
546
+
547
+ test("ADR-017 (#5704): unregistered-milestone drift detected and DB row inserted", async (t) => {
548
+ const base = mkdtempSync(join(tmpdir(), "gsd-adr017-projmd-"));
549
+ const milestoneDir = join(base, ".gsd", "milestones", "M042");
550
+ mkdirSync(milestoneDir, { recursive: true });
551
+ // Roadmap with one slice — meaningful content, will be picked up by importer
552
+ writeFileSync(
553
+ join(milestoneDir, "M042-ROADMAP.md"),
554
+ [
555
+ "# M042: Test Milestone",
556
+ "",
557
+ "**Vision:** Verify unregistered-milestone drift",
558
+ "",
559
+ "## Slices",
560
+ "",
561
+ "- [ ] **S01: Foundation** `risk:medium` `depends:[]`",
562
+ "",
563
+ ].join("\n"),
564
+ );
565
+ t.after(() => {
566
+ try { closeDatabase(); } catch { /* noop */ }
567
+ rmSync(base, { recursive: true, force: true });
568
+ });
569
+
570
+ openDatabase(join(base, ".gsd", "gsd.db"));
571
+ // Pre-condition: filesystem has the milestone, DB does NOT.
572
+ assert.equal(getMilestone("M042"), null, "pre: DB has no row for M042");
573
+
574
+ const result = await reconcileBeforeDispatch(base, {
575
+ invalidateStateCache: () => {},
576
+ deriveState: async () => makeState(),
577
+ });
578
+
579
+ assert.equal(result.ok, true);
580
+ assert.ok(getMilestone("M042"), "post: DB row inserted for M042");
581
+ const milestoneRepaired = result.repaired.find(
582
+ (d) => d.kind === "unregistered-milestone",
583
+ );
584
+ assert.ok(milestoneRepaired, "repaired list should include the unregistered-milestone drift");
585
+ if (milestoneRepaired?.kind === "unregistered-milestone") {
586
+ assert.equal(milestoneRepaired.milestoneId, "M042");
587
+ }
588
+ });
589
+
590
+ test("ADR-017 (#5704): registered milestone (DB row present) → no drift", async (t) => {
591
+ const base = mkdtempSync(join(tmpdir(), "gsd-adr017-projmd-clean-"));
592
+ const milestoneDir = join(base, ".gsd", "milestones", "M001");
593
+ mkdirSync(milestoneDir, { recursive: true });
594
+ writeFileSync(
595
+ join(milestoneDir, "M001-ROADMAP.md"),
596
+ [
597
+ "# M001: Test",
598
+ "",
599
+ "**Vision:** Already-registered milestone",
600
+ "",
601
+ "## Slices",
602
+ "",
603
+ "- [ ] **S01: Slice** `risk:low` `depends:[]`",
604
+ "",
605
+ ].join("\n"),
606
+ );
607
+ t.after(() => {
608
+ try { closeDatabase(); } catch { /* noop */ }
609
+ rmSync(base, { recursive: true, force: true });
610
+ });
611
+
612
+ openDatabase(join(base, ".gsd", "gsd.db"));
613
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
614
+
615
+ const result = await reconcileBeforeDispatch(base, {
616
+ invalidateStateCache: () => {},
617
+ deriveState: async () => makeState(),
618
+ });
619
+
620
+ assert.equal(result.ok, true);
621
+ assert.equal(
622
+ result.repaired.some((d) => d.kind === "unregistered-milestone"),
623
+ false,
624
+ "no drift should be reported when the milestone is already in the DB",
625
+ );
626
+ });
627
+
628
+ // ─── #5705: roadmap-divergence drift ─────────────────────────────────────────
629
+
630
+ test("ADR-017 (#5705): roadmap-divergence drift detected and DB depends synced", async (t) => {
631
+ const base = mkdtempSync(join(tmpdir(), "gsd-adr017-roadmap-"));
632
+ const milestoneDir = join(base, ".gsd", "milestones", "M001");
633
+ mkdirSync(milestoneDir, { recursive: true });
634
+ // ROADMAP.md declares S02 depends on [S01]
635
+ writeFileSync(
636
+ join(milestoneDir, "M001-ROADMAP.md"),
637
+ [
638
+ "# M001: Test",
639
+ "",
640
+ "**Vision:** Verify roadmap-divergence drift",
641
+ "",
642
+ "## Slices",
643
+ "",
644
+ "- [ ] **S01: Foundation** `risk:medium` `depends:[]`",
645
+ "- [ ] **S02: Feature** `risk:medium` `depends:[S01]`",
646
+ "",
647
+ ].join("\n"),
648
+ );
649
+ t.after(() => {
650
+ try { closeDatabase(); } catch { /* noop */ }
651
+ rmSync(base, { recursive: true, force: true });
652
+ });
653
+
654
+ openDatabase(join(base, ".gsd", "gsd.db"));
655
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
656
+ // Seed DB with S02 depending on [] — diverges from ROADMAP.md
657
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Foundation", status: "pending", risk: "medium", depends: [], demo: "", sequence: 1 });
658
+ insertSlice({ id: "S02", milestoneId: "M001", title: "Feature", status: "pending", risk: "medium", depends: [], demo: "", sequence: 2 });
659
+
660
+ assert.deepEqual(getSlice("M001", "S02")?.depends, [], "pre: DB has S02.depends = []");
661
+
662
+ const result = await reconcileBeforeDispatch(base, {
663
+ invalidateStateCache: () => {},
664
+ deriveState: async () => makeState(),
665
+ });
666
+
667
+ assert.equal(result.ok, true);
668
+ assert.deepEqual(getSlice("M001", "S02")?.depends, ["S01"], "post: DB depends matches ROADMAP.md");
669
+ const roadmapRepaired = result.repaired.find((d) => d.kind === "roadmap-divergence");
670
+ assert.ok(roadmapRepaired, "repaired list should include the roadmap-divergence drift");
671
+ if (roadmapRepaired?.kind === "roadmap-divergence") {
672
+ assert.equal(roadmapRepaired.milestoneId, "M001");
673
+ }
674
+ });
675
+
676
+ test("ADR-017 (#5705): ROADMAP declares slice missing from DB → slice inserted and drift reported", async (t) => {
677
+ const base = mkdtempSync(join(tmpdir(), "gsd-adr017-roadmap-newslice-"));
678
+ const milestoneDir = join(base, ".gsd", "milestones", "M001");
679
+ mkdirSync(milestoneDir, { recursive: true });
680
+ // ROADMAP.md declares S01 and S02; DB will only have S01.
681
+ writeFileSync(
682
+ join(milestoneDir, "M001-ROADMAP.md"),
683
+ [
684
+ "# M001: Test",
685
+ "",
686
+ "**Vision:** Verify new-slice insertion via roadmap-divergence repair",
687
+ "",
688
+ "## Slices",
689
+ "",
690
+ "- [ ] **S01: Foundation** `risk:medium` `depends:[]`",
691
+ "- [ ] **S02: Feature** `risk:medium` `depends:[S01]`",
692
+ "",
693
+ ].join("\n"),
694
+ );
695
+ t.after(() => {
696
+ try { closeDatabase(); } catch { /* noop */ }
697
+ rmSync(base, { recursive: true, force: true });
698
+ });
699
+
700
+ openDatabase(join(base, ".gsd", "gsd.db"));
701
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
702
+ // Only insert S01 — S02 is intentionally absent from the DB.
703
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Foundation", status: "pending", risk: "medium", depends: [], demo: "", sequence: 1 });
704
+
705
+ assert.equal(getSlice("M001", "S02"), null, "pre: S02 has no DB row");
706
+
707
+ const result = await reconcileBeforeDispatch(base, {
708
+ invalidateStateCache: () => {},
709
+ deriveState: async () => makeState(),
710
+ });
711
+
712
+ assert.equal(result.ok, true);
713
+ const s02 = getSlice("M001", "S02");
714
+ assert.ok(s02, "post: S02 inserted into DB after repair");
715
+ assert.equal(s02?.sequence, 2, "post: S02 sequence matches ROADMAP order");
716
+ assert.deepEqual(s02?.depends, ["S01"], "post: S02 depends matches ROADMAP");
717
+ const roadmapRepaired = result.repaired.find((d) => d.kind === "roadmap-divergence");
718
+ assert.ok(roadmapRepaired, "repaired list should include the roadmap-divergence drift");
719
+ if (roadmapRepaired?.kind === "roadmap-divergence") {
720
+ assert.equal(roadmapRepaired.milestoneId, "M001");
721
+ }
722
+ });
723
+
724
+ test("ADR-017 (#5705): in-sync ROADMAP and DB → no roadmap-divergence drift", async (t) => {
725
+ const base = mkdtempSync(join(tmpdir(), "gsd-adr017-roadmap-clean-"));
726
+ const milestoneDir = join(base, ".gsd", "milestones", "M001");
727
+ mkdirSync(milestoneDir, { recursive: true });
728
+ writeFileSync(
729
+ join(milestoneDir, "M001-ROADMAP.md"),
730
+ [
731
+ "# M001: Test",
732
+ "",
733
+ "**Vision:** Already in sync",
734
+ "",
735
+ "## Slices",
736
+ "",
737
+ "- [ ] **S01: Foundation** `risk:low` `depends:[]`",
738
+ "",
739
+ ].join("\n"),
740
+ );
741
+ t.after(() => {
742
+ try { closeDatabase(); } catch { /* noop */ }
743
+ rmSync(base, { recursive: true, force: true });
744
+ });
745
+
746
+ openDatabase(join(base, ".gsd", "gsd.db"));
747
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
748
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Foundation", status: "pending", risk: "low", depends: [], demo: "", sequence: 1 });
749
+
750
+ const result = await reconcileBeforeDispatch(base, {
751
+ invalidateStateCache: () => {},
752
+ deriveState: async () => makeState(),
753
+ });
754
+
755
+ assert.equal(result.ok, true);
756
+ assert.equal(
757
+ result.repaired.some((d) => d.kind === "roadmap-divergence"),
758
+ false,
759
+ "no roadmap-divergence drift should be reported when DB matches markdown",
760
+ );
761
+ });
762
+
763
+ // ─── #5706: missing-completion-timestamp drift ──────────────────────────────
764
+
765
+ test("ADR-017 (#5706): task with SUMMARY but null completed_at → backfilled", async (t) => {
766
+ const base = mkdtempSync(join(tmpdir(), "gsd-adr017-completion-task-"));
767
+ const tasksDir = join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
768
+ mkdirSync(tasksDir, { recursive: true });
769
+ t.after(() => {
770
+ try { closeDatabase(); } catch { /* noop */ }
771
+ rmSync(base, { recursive: true, force: true });
772
+ });
773
+
774
+ openDatabase(join(base, ".gsd", "gsd.db"));
775
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
776
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "pending", risk: "low", depends: [], demo: "", sequence: 1 });
777
+ insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "Task", status: "pending" });
778
+
779
+ // Move T01 to complete WITHOUT setting completed_at (simulating drift after
780
+ // an external recovery path or a partial state migration).
781
+ updateTaskStatus("M001", "S01", "T01", "complete", undefined);
782
+ // SUMMARY.md attests to completion on disk.
783
+ const summaryPath = join(tasksDir, "T01-SUMMARY.md");
784
+ writeFileSync(summaryPath, "# T01 Summary\n");
785
+ const summaryMtimeMs = statSync(summaryPath).mtime.getTime();
786
+
787
+ const taskBefore = getSliceTasks("M001", "S01").find((t) => t.id === "T01");
788
+ assert.equal(taskBefore?.status, "complete");
789
+ assert.equal(taskBefore?.completed_at, null, "pre: completed_at is null");
790
+
791
+ const result = await reconcileBeforeDispatch(base, {
792
+ invalidateStateCache: () => {},
793
+ deriveState: async () => makeState({ activeMilestone: { id: "M001", title: "Test" } }),
794
+ });
795
+
796
+ assert.equal(result.ok, true);
797
+ const taskAfter = getSliceTasks("M001", "S01").find((t) => t.id === "T01");
798
+ assert.ok(taskAfter?.completed_at, "post: completed_at populated");
799
+ const completedAtMs = Date.parse(taskAfter?.completed_at ?? "");
800
+ assert.ok(Number.isFinite(completedAtMs), "post: completed_at is parseable ISO string");
801
+ assert.equal(completedAtMs, summaryMtimeMs, "post: completed_at derived from SUMMARY mtime");
802
+ const drift = result.repaired.find((d) => d.kind === "missing-completion-timestamp");
803
+ assert.ok(drift, "drift recorded");
804
+ if (drift?.kind === "missing-completion-timestamp") {
805
+ assert.equal(drift.entity, "task");
806
+ assert.deepEqual(drift.ids, ["M001/S01/T01"]);
807
+ }
808
+ });
809
+
810
+ test("ADR-017 (#5706): repair is idempotent — re-running preserves the timestamp", async (t) => {
811
+ const base = mkdtempSync(join(tmpdir(), "gsd-adr017-completion-idempotent-"));
812
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
813
+ mkdirSync(sliceDir, { recursive: true });
814
+ t.after(() => {
815
+ try { closeDatabase(); } catch { /* noop */ }
816
+ rmSync(base, { recursive: true, force: true });
817
+ });
818
+
819
+ openDatabase(join(base, ".gsd", "gsd.db"));
820
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
821
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "pending", risk: "low", depends: [], demo: "", sequence: 1 });
822
+ updateSliceStatus("M001", "S01", "complete", undefined);
823
+ const summaryPath = join(sliceDir, "S01-SUMMARY.md");
824
+ writeFileSync(summaryPath, "# S01 Summary\n");
825
+ const summaryMtimeMs = statSync(summaryPath).mtime.getTime();
826
+
827
+ const firstResult = await reconcileBeforeDispatch(base, {
828
+ invalidateStateCache: () => {},
829
+ deriveState: async () => makeState({ activeMilestone: { id: "M001", title: "Test" } }),
830
+ });
831
+ assert.equal(firstResult.ok, true);
832
+ const tsAfterFirst = getSlice("M001", "S01")?.completed_at;
833
+ assert.ok(tsAfterFirst, "first pass: completed_at populated");
834
+ const completedAtMs = Date.parse(tsAfterFirst ?? "");
835
+ assert.ok(Number.isFinite(completedAtMs), "first pass: completed_at is parseable ISO string");
836
+ assert.equal(completedAtMs, summaryMtimeMs, "first pass: completed_at derived from SUMMARY mtime");
837
+
838
+ // Second pass — drift is already cleared, no record should appear, and
839
+ // the existing timestamp is untouched.
840
+ const secondResult = await reconcileBeforeDispatch(base, {
841
+ invalidateStateCache: () => {},
842
+ deriveState: async () => makeState({ activeMilestone: { id: "M001", title: "Test" } }),
843
+ });
844
+ assert.equal(secondResult.ok, true);
845
+ assert.equal(
846
+ secondResult.repaired.some((d) => d.kind === "missing-completion-timestamp"),
847
+ false,
848
+ "second pass: no drift detected after first repair",
849
+ );
850
+ assert.equal(getSlice("M001", "S01")?.completed_at, tsAfterFirst, "timestamp unchanged");
851
+ });
852
+
853
+ // ─── #5707: caller closure (reconcileBeforeSpawn) ────────────────────────────
854
+
855
+ test("ADR-017 (#5707): reconcileBeforeSpawn returns ok=true on clean reconciliation", async () => {
856
+ const result = await reconcileBeforeSpawn("/project", {
857
+ invalidateStateCache: () => {},
858
+ deriveState: async () => makeState(),
859
+ registry: [],
860
+ });
861
+ assert.equal(result.ok, true);
862
+ });
863
+
864
+ test("ADR-017 (#5707): reconcileBeforeSpawn surfaces blockers as ok=false", async () => {
865
+ const result = await reconcileBeforeSpawn("/project", {
866
+ invalidateStateCache: () => {},
867
+ deriveState: async () => makeState({ phase: "blocked", blockers: ["lock missing"] }),
868
+ registry: [],
869
+ });
870
+ assert.equal(result.ok, false);
871
+ if (!result.ok) {
872
+ assert.match(result.reason, /lock missing/);
873
+ }
874
+ });
875
+
876
+ test("ADR-017 (#5707): reconcileBeforeSpawn catches ReconciliationFailedError → ok=false", async () => {
877
+ const persistent: DriftRecord = { kind: "stale-sketch-flag", mid: "M001", sid: "S02" };
878
+ const handler: DriftHandler = {
879
+ kind: "stale-sketch-flag",
880
+ detect: () => [persistent],
881
+ repair: () => { /* no-op: drift cannot be cleared, persists past cap=2 */ },
882
+ };
883
+
884
+ const result = await reconcileBeforeSpawn("/project", {
885
+ invalidateStateCache: () => {},
886
+ deriveState: async () => makeState(),
887
+ registry: [handler],
888
+ });
889
+
890
+ assert.equal(result.ok, false);
891
+ if (!result.ok) {
892
+ assert.match(result.reason, /stale-sketch-flag/);
893
+ }
894
+ });
895
+
896
+ test("ADR-017 (#5707): reconcileBeforeSpawn reports repaired drift in ok=true reason", async () => {
897
+ let detectCalls = 0;
898
+ const handler: DriftHandler = {
899
+ kind: "stale-sketch-flag",
900
+ detect: () => {
901
+ detectCalls++;
902
+ return detectCalls === 1
903
+ ? [{ kind: "stale-sketch-flag", mid: "M001", sid: "S02" }]
904
+ : [];
905
+ },
906
+ repair: () => { /* repair "succeeds" — second detect returns empty */ },
907
+ };
908
+
909
+ const result = await reconcileBeforeSpawn("/project", {
910
+ invalidateStateCache: () => {},
911
+ deriveState: async () => makeState(),
912
+ registry: [handler],
913
+ });
914
+
915
+ assert.equal(result.ok, true);
916
+ if (result.ok) {
917
+ assert.match(result.reason ?? "", /stale-sketch-flag/);
918
+ }
919
+ });
920
+
921
+ // ─── Lifecycle and classification ────────────────────────────────────────────
922
+
923
+ test("ADR-017 (#5700): cascading drift triggers second pass within cap", async () => {
924
+ // First pass detects drift A; repair "fixes" it. Second pass detects drift B
925
+ // (cascading); repair fixes it. Third call would see no drift. Cap=2 means
926
+ // we have exactly two repair passes available.
927
+ const detectedSequence: DriftRecord[][] = [
928
+ [{ kind: "stale-sketch-flag", mid: "M001", sid: "S02" }],
929
+ [{ kind: "stale-sketch-flag", mid: "M001", sid: "S03" }],
930
+ [],
931
+ ];
932
+ let detectCallIdx = 0;
933
+ const repaired: DriftRecord[] = [];
934
+
935
+ const handler: DriftHandler = {
936
+ kind: "stale-sketch-flag",
937
+ detect: () => detectedSequence[detectCallIdx++] ?? [],
938
+ repair: (record) => {
939
+ repaired.push(record);
940
+ },
941
+ };
942
+
943
+ const result = await reconcileBeforeDispatch("/project", {
944
+ invalidateStateCache: () => {},
945
+ deriveState: async () => makeState(),
946
+ registry: [handler],
947
+ });
948
+
949
+ assert.equal(result.ok, true);
950
+ assert.equal(result.repaired.length, 2, "both passes' repairs collected");
951
+ assert.equal(repaired.length, 2);
952
+ });