gsd-pi 2.45.0 → 2.46.0-dev.cc9d310

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 (347) hide show
  1. package/dist/help-text.js +1 -1
  2. package/dist/loader.js +34 -0
  3. package/dist/resources/extensions/gsd/auto/phases.js +27 -42
  4. package/dist/resources/extensions/gsd/auto/run-unit.js +6 -3
  5. package/dist/resources/extensions/gsd/auto/session.js +0 -11
  6. package/dist/resources/extensions/gsd/auto-artifact-paths.js +112 -0
  7. package/dist/resources/extensions/gsd/auto-post-unit.js +25 -96
  8. package/dist/resources/extensions/gsd/auto-start.js +2 -3
  9. package/dist/resources/extensions/gsd/auto-worktree.js +5 -4
  10. package/dist/resources/extensions/gsd/auto.js +12 -57
  11. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +15 -12
  12. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +18 -0
  13. package/dist/resources/extensions/gsd/commands/context.js +0 -4
  14. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +1 -1
  15. package/dist/resources/extensions/gsd/crash-recovery.js +2 -4
  16. package/dist/resources/extensions/gsd/dashboard-overlay.js +0 -44
  17. package/dist/resources/extensions/gsd/db-writer.js +9 -9
  18. package/dist/resources/extensions/gsd/doctor-checks.js +167 -2
  19. package/dist/resources/extensions/gsd/doctor.js +5 -3
  20. package/dist/resources/extensions/gsd/gsd-db.js +16 -3
  21. package/dist/resources/extensions/gsd/guided-flow.js +1 -2
  22. package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
  23. package/dist/resources/extensions/gsd/parallel-orchestrator.js +5 -18
  24. package/dist/resources/extensions/gsd/preferences-types.js +2 -2
  25. package/dist/resources/extensions/gsd/preferences.js +8 -4
  26. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +21 -8
  27. package/dist/resources/extensions/gsd/prompts/complete-slice.md +10 -23
  28. package/dist/resources/extensions/gsd/prompts/discuss.md +2 -2
  29. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -15
  30. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  31. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  32. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  33. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  34. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  35. package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  36. package/dist/resources/extensions/gsd/prompts/queue.md +2 -2
  37. package/dist/resources/extensions/gsd/prompts/quick-task.md +2 -0
  38. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
  39. package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -3
  40. package/dist/resources/extensions/gsd/prompts/rethink.md +7 -2
  41. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  42. package/dist/resources/extensions/gsd/session-lock.js +1 -3
  43. package/dist/resources/extensions/gsd/state.js +7 -0
  44. package/dist/resources/extensions/gsd/sync-lock.js +89 -0
  45. package/dist/resources/extensions/gsd/tools/complete-milestone.js +61 -11
  46. package/dist/resources/extensions/gsd/tools/complete-slice.js +56 -11
  47. package/dist/resources/extensions/gsd/tools/complete-task.js +50 -2
  48. package/dist/resources/extensions/gsd/tools/plan-milestone.js +37 -1
  49. package/dist/resources/extensions/gsd/tools/plan-slice.js +30 -1
  50. package/dist/resources/extensions/gsd/tools/plan-task.js +27 -1
  51. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +32 -2
  52. package/dist/resources/extensions/gsd/tools/reopen-slice.js +86 -0
  53. package/dist/resources/extensions/gsd/tools/reopen-task.js +90 -0
  54. package/dist/resources/extensions/gsd/tools/replan-slice.js +32 -2
  55. package/dist/resources/extensions/gsd/unit-ownership.js +85 -0
  56. package/dist/resources/extensions/gsd/workflow-events.js +102 -0
  57. package/dist/resources/extensions/gsd/workflow-logger.js +193 -0
  58. package/dist/resources/extensions/gsd/workflow-manifest.js +244 -0
  59. package/dist/resources/extensions/gsd/workflow-migration.js +280 -0
  60. package/dist/resources/extensions/gsd/workflow-projections.js +373 -0
  61. package/dist/resources/extensions/gsd/workflow-reconcile.js +411 -0
  62. package/dist/resources/extensions/gsd/worktree-manager.js +4 -3
  63. package/dist/resources/extensions/gsd/worktree-resolver.js +37 -0
  64. package/dist/resources/extensions/gsd/write-intercept.js +84 -0
  65. package/dist/resources/extensions/voice/index.js +11 -16
  66. package/dist/resources/extensions/voice/linux-ready.js +67 -0
  67. package/dist/web/standalone/.next/BUILD_ID +1 -1
  68. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  69. package/dist/web/standalone/.next/build-manifest.json +3 -3
  70. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  71. package/dist/web/standalone/.next/required-server-files.json +3 -3
  72. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  73. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  75. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  83. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  99. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
  137. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  143. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  157. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  159. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  161. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  163. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  172. package/dist/web/standalone/.next/server/app/index.html +1 -1
  173. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  174. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  175. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  176. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  177. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  178. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  179. package/dist/web/standalone/.next/server/app/page.js +2 -2
  180. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  181. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  182. package/dist/web/standalone/.next/server/chunks/229.js +1 -1
  183. package/dist/web/standalone/.next/server/chunks/471.js +3 -3
  184. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  185. package/dist/web/standalone/.next/server/middleware.js +2 -2
  186. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  187. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  188. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  189. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  190. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  191. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  192. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  193. package/dist/web/standalone/.next/static/chunks/app/page-6654a8cca61a3d1c.js +1 -0
  194. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  195. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  196. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  197. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  198. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  199. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  200. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  201. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  202. package/dist/web/standalone/server.js +1 -1
  203. package/package.json +2 -1
  204. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
  205. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +2 -0
  206. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
  207. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
  208. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  209. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  210. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts +4 -0
  211. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts.map +1 -1
  212. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js +10 -5
  213. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js.map +1 -1
  214. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts +2 -0
  215. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts.map +1 -0
  216. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js +185 -0
  217. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js.map +1 -0
  218. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +239 -10
  219. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  220. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +2 -1
  221. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  222. package/packages/pi-coding-agent/dist/core/model-registry.js +20 -2
  223. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  224. package/packages/pi-coding-agent/dist/core/package-commands.test.js +206 -195
  225. package/packages/pi-coding-agent/dist/core/package-commands.test.js.map +1 -1
  226. package/packages/pi-coding-agent/package.json +1 -1
  227. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +2 -0
  228. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -1
  229. package/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts +227 -0
  230. package/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +11 -5
  231. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +297 -11
  232. package/packages/pi-coding-agent/src/core/model-registry.ts +30 -3
  233. package/packages/pi-coding-agent/src/core/package-commands.test.ts +227 -205
  234. package/pkg/package.json +1 -1
  235. package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -19
  236. package/src/resources/extensions/gsd/auto/phases.ts +24 -44
  237. package/src/resources/extensions/gsd/auto/run-unit.ts +6 -3
  238. package/src/resources/extensions/gsd/auto/session.ts +0 -18
  239. package/src/resources/extensions/gsd/auto-artifact-paths.ts +131 -0
  240. package/src/resources/extensions/gsd/auto-dashboard.ts +0 -1
  241. package/src/resources/extensions/gsd/auto-post-unit.ts +25 -106
  242. package/src/resources/extensions/gsd/auto-start.ts +1 -3
  243. package/src/resources/extensions/gsd/auto-worktree.ts +8 -5
  244. package/src/resources/extensions/gsd/auto.ts +7 -83
  245. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +15 -12
  246. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
  247. package/src/resources/extensions/gsd/commands/context.ts +0 -5
  248. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +1 -1
  249. package/src/resources/extensions/gsd/crash-recovery.ts +1 -5
  250. package/src/resources/extensions/gsd/dashboard-overlay.ts +0 -50
  251. package/src/resources/extensions/gsd/db-writer.ts +9 -17
  252. package/src/resources/extensions/gsd/doctor-checks.ts +180 -2
  253. package/src/resources/extensions/gsd/doctor-types.ts +7 -1
  254. package/src/resources/extensions/gsd/doctor.ts +6 -3
  255. package/src/resources/extensions/gsd/gsd-db.ts +16 -3
  256. package/src/resources/extensions/gsd/guided-flow.ts +1 -2
  257. package/src/resources/extensions/gsd/journal.ts +6 -1
  258. package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
  259. package/src/resources/extensions/gsd/parallel-orchestrator.ts +5 -21
  260. package/src/resources/extensions/gsd/preferences-types.ts +2 -2
  261. package/src/resources/extensions/gsd/preferences.ts +7 -3
  262. package/src/resources/extensions/gsd/prompts/complete-milestone.md +21 -8
  263. package/src/resources/extensions/gsd/prompts/complete-slice.md +10 -23
  264. package/src/resources/extensions/gsd/prompts/discuss.md +2 -2
  265. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -15
  266. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  267. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  268. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  269. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  270. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  271. package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  272. package/src/resources/extensions/gsd/prompts/queue.md +2 -2
  273. package/src/resources/extensions/gsd/prompts/quick-task.md +2 -0
  274. package/src/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
  275. package/src/resources/extensions/gsd/prompts/research-slice.md +3 -3
  276. package/src/resources/extensions/gsd/prompts/rethink.md +7 -2
  277. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  278. package/src/resources/extensions/gsd/session-lock.ts +0 -4
  279. package/src/resources/extensions/gsd/state.ts +8 -0
  280. package/src/resources/extensions/gsd/sync-lock.ts +94 -0
  281. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +5 -13
  282. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +6 -10
  283. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +96 -0
  284. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +264 -228
  285. package/src/resources/extensions/gsd/tests/complete-task.test.ts +317 -250
  286. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +2 -8
  287. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +0 -3
  288. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  289. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +1 -1
  290. package/src/resources/extensions/gsd/tests/integration-proof.test.ts +15 -24
  291. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -3
  292. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  293. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  294. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -1
  295. package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +8 -9
  296. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +42 -3
  297. package/src/resources/extensions/gsd/tests/parallel-budget-atomicity.test.ts +0 -1
  298. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +0 -7
  299. package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +7 -8
  300. package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +20 -24
  301. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +0 -2
  302. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +9 -6
  303. package/src/resources/extensions/gsd/tests/post-mutation-hook.test.ts +171 -0
  304. package/src/resources/extensions/gsd/tests/preferences.test.ts +7 -9
  305. package/src/resources/extensions/gsd/tests/projection-regression.test.ts +174 -0
  306. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +15 -14
  307. package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +155 -0
  308. package/src/resources/extensions/gsd/tests/reopen-task.test.ts +165 -0
  309. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +1 -4
  310. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +2 -3
  311. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +122 -0
  312. package/src/resources/extensions/gsd/tests/unit-ownership.test.ts +175 -0
  313. package/src/resources/extensions/gsd/tests/workflow-events.test.ts +205 -0
  314. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +275 -0
  315. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +186 -0
  316. package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +171 -0
  317. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +220 -0
  318. package/src/resources/extensions/gsd/tests/write-intercept.test.ts +76 -0
  319. package/src/resources/extensions/gsd/tools/complete-milestone.ts +74 -11
  320. package/src/resources/extensions/gsd/tools/complete-slice.ts +68 -11
  321. package/src/resources/extensions/gsd/tools/complete-task.ts +63 -1
  322. package/src/resources/extensions/gsd/tools/plan-milestone.ts +45 -0
  323. package/src/resources/extensions/gsd/tools/plan-slice.ts +38 -0
  324. package/src/resources/extensions/gsd/tools/plan-task.ts +35 -1
  325. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +39 -1
  326. package/src/resources/extensions/gsd/tools/reopen-slice.ts +125 -0
  327. package/src/resources/extensions/gsd/tools/reopen-task.ts +129 -0
  328. package/src/resources/extensions/gsd/tools/replan-slice.ts +38 -1
  329. package/src/resources/extensions/gsd/types.ts +8 -0
  330. package/src/resources/extensions/gsd/unit-ownership.ts +104 -0
  331. package/src/resources/extensions/gsd/workflow-events.ts +154 -0
  332. package/src/resources/extensions/gsd/workflow-logger.ts +243 -0
  333. package/src/resources/extensions/gsd/workflow-manifest.ts +334 -0
  334. package/src/resources/extensions/gsd/workflow-migration.ts +345 -0
  335. package/src/resources/extensions/gsd/workflow-projections.ts +425 -0
  336. package/src/resources/extensions/gsd/workflow-reconcile.ts +503 -0
  337. package/src/resources/extensions/gsd/worktree-manager.ts +4 -9
  338. package/src/resources/extensions/gsd/worktree-resolver.ts +37 -0
  339. package/src/resources/extensions/gsd/write-intercept.ts +90 -0
  340. package/src/resources/extensions/voice/index.ts +11 -21
  341. package/src/resources/extensions/voice/linux-ready.ts +87 -0
  342. package/src/resources/extensions/voice/tests/linux-ready.test.ts +124 -0
  343. package/dist/web/standalone/.next/static/chunks/app/page-12dd5ece0df4badc.js +0 -1
  344. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  345. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  346. /package/dist/web/standalone/.next/static/{wUzEX1U3CmFcMry2SUDJn → ZIDqryyYDroh_8AnaAOSG}/_buildManifest.js +0 -0
  347. /package/dist/web/standalone/.next/static/{wUzEX1U3CmFcMry2SUDJn → ZIDqryyYDroh_8AnaAOSG}/_ssgManifest.js +0 -0
@@ -0,0 +1,165 @@
1
+ // GSD — reopen-task handler tests
2
+ // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
+
4
+ import test from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { tmpdir } from 'node:os';
9
+
10
+ import {
11
+ openDatabase,
12
+ closeDatabase,
13
+ insertMilestone,
14
+ insertSlice,
15
+ insertTask,
16
+ getTask,
17
+ } from '../gsd-db.ts';
18
+ import { handleReopenTask } from '../tools/reopen-task.ts';
19
+
20
+ function makeTmpBase(): string {
21
+ const base = mkdtempSync(join(tmpdir(), 'gsd-reopen-task-'));
22
+ mkdirSync(join(base, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'tasks'), { recursive: true });
23
+ return base;
24
+ }
25
+
26
+ function cleanup(base: string): void {
27
+ try { closeDatabase(); } catch { /* noop */ }
28
+ try { rmSync(base, { recursive: true, force: true }); } catch { /* noop */ }
29
+ }
30
+
31
+ function seedCompleteTask(): void {
32
+ insertMilestone({ id: 'M001', title: 'Test Milestone', status: 'active' });
33
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Test Slice', status: 'in_progress' });
34
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'Task One', status: 'complete' });
35
+ insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', title: 'Task Two', status: 'pending' });
36
+ }
37
+
38
+ // ─── Success path ────────────────────────────────────────────────────────
39
+
40
+ test('handleReopenTask: resets a complete task to pending', async () => {
41
+ const base = makeTmpBase();
42
+ openDatabase(join(base, '.gsd', 'gsd.db'));
43
+ try {
44
+ seedCompleteTask();
45
+
46
+ const result = await handleReopenTask({
47
+ milestoneId: 'M001',
48
+ sliceId: 'S01',
49
+ taskId: 'T01',
50
+ reason: 'verification failed after merge',
51
+ }, base);
52
+
53
+ assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
54
+ assert.equal(result.taskId, 'T01');
55
+
56
+ const task = getTask('M001', 'S01', 'T01');
57
+ assert.ok(task, 'task should still exist');
58
+ assert.equal(task!.status, 'pending', 'task status should be reset to pending');
59
+ } finally {
60
+ cleanup(base);
61
+ }
62
+ });
63
+
64
+ test('handleReopenTask: does not affect other tasks in the slice', async () => {
65
+ const base = makeTmpBase();
66
+ openDatabase(join(base, '.gsd', 'gsd.db'));
67
+ try {
68
+ seedCompleteTask();
69
+
70
+ await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: 'T01' }, base);
71
+
72
+ const t02 = getTask('M001', 'S01', 'T02');
73
+ assert.ok(t02, 'T02 should still exist');
74
+ assert.equal(t02!.status, 'pending', 'T02 status should be unchanged');
75
+ } finally {
76
+ cleanup(base);
77
+ }
78
+ });
79
+
80
+ // ─── Failure paths ───────────────────────────────────────────────────────
81
+
82
+ test('handleReopenTask: rejects empty taskId', async () => {
83
+ const base = makeTmpBase();
84
+ openDatabase(join(base, '.gsd', 'gsd.db'));
85
+ try {
86
+ const result = await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: '' }, base);
87
+ assert.ok('error' in result);
88
+ assert.match(result.error, /taskId/);
89
+ } finally {
90
+ cleanup(base);
91
+ }
92
+ });
93
+
94
+ test('handleReopenTask: rejects non-existent milestone', async () => {
95
+ const base = makeTmpBase();
96
+ openDatabase(join(base, '.gsd', 'gsd.db'));
97
+ try {
98
+ const result = await handleReopenTask({ milestoneId: 'M999', sliceId: 'S01', taskId: 'T01' }, base);
99
+ assert.ok('error' in result);
100
+ assert.match(result.error, /milestone not found/);
101
+ } finally {
102
+ cleanup(base);
103
+ }
104
+ });
105
+
106
+ test('handleReopenTask: rejects task in a closed milestone', async () => {
107
+ const base = makeTmpBase();
108
+ openDatabase(join(base, '.gsd', 'gsd.db'));
109
+ try {
110
+ insertMilestone({ id: 'M001', title: 'Done', status: 'complete' });
111
+ insertSlice({ id: 'S01', milestoneId: 'M001', status: 'complete' });
112
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', status: 'complete' });
113
+
114
+ const result = await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: 'T01' }, base);
115
+ assert.ok('error' in result);
116
+ assert.match(result.error, /closed milestone/);
117
+ } finally {
118
+ cleanup(base);
119
+ }
120
+ });
121
+
122
+ test('handleReopenTask: rejects task inside a closed slice', async () => {
123
+ const base = makeTmpBase();
124
+ openDatabase(join(base, '.gsd', 'gsd.db'));
125
+ try {
126
+ insertMilestone({ id: 'M001', title: 'Active', status: 'active' });
127
+ insertSlice({ id: 'S01', milestoneId: 'M001', status: 'complete' });
128
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', status: 'complete' });
129
+
130
+ const result = await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: 'T01' }, base);
131
+ assert.ok('error' in result);
132
+ assert.match(result.error, /closed slice/);
133
+ } finally {
134
+ cleanup(base);
135
+ }
136
+ });
137
+
138
+ test('handleReopenTask: rejects reopening a task that is not complete', async () => {
139
+ const base = makeTmpBase();
140
+ openDatabase(join(base, '.gsd', 'gsd.db'));
141
+ try {
142
+ seedCompleteTask();
143
+
144
+ const result = await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: 'T02' }, base);
145
+ assert.ok('error' in result);
146
+ assert.match(result.error, /not complete/);
147
+ } finally {
148
+ cleanup(base);
149
+ }
150
+ });
151
+
152
+ test('handleReopenTask: rejects non-existent task', async () => {
153
+ const base = makeTmpBase();
154
+ openDatabase(join(base, '.gsd', 'gsd.db'));
155
+ try {
156
+ insertMilestone({ id: 'M001', title: 'Active', status: 'active' });
157
+ insertSlice({ id: 'S01', milestoneId: 'M001', status: 'in_progress' });
158
+
159
+ const result = await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: 'T99' }, base);
160
+ assert.ok('error' in result);
161
+ assert.match(result.error, /task not found/);
162
+ } finally {
163
+ cleanup(base);
164
+ }
165
+ });
@@ -103,7 +103,7 @@ describe('session-lock-regression', async () => {
103
103
  try {
104
104
  acquireSessionLock(base);
105
105
 
106
- updateSessionLock(base, 'execute-task', 'M001/S01/T01', 5, '/tmp/session.json');
106
+ updateSessionLock(base, 'execute-task', 'M001/S01/T01', '/tmp/session.json');
107
107
 
108
108
  const data = readSessionLockData(base);
109
109
  assert.ok(data !== null, 'lock data readable after update');
@@ -111,7 +111,6 @@ describe('session-lock-regression', async () => {
111
111
  assert.deepStrictEqual(data.pid, process.pid, 'lock data has correct PID');
112
112
  assert.deepStrictEqual(data.unitType, 'execute-task', 'lock data has correct unit type');
113
113
  assert.deepStrictEqual(data.unitId, 'M001/S01/T01', 'lock data has correct unit ID');
114
- assert.deepStrictEqual(data.completedUnits, 5, 'lock data has correct completed count');
115
114
  assert.deepStrictEqual(data.sessionFile, '/tmp/session.json', 'lock data has session file');
116
115
  }
117
116
 
@@ -136,7 +135,6 @@ describe('session-lock-regression', async () => {
136
135
  unitType: 'execute-task',
137
136
  unitId: 'M001/S01/T01',
138
137
  unitStartedAt: new Date(Date.now() - 3600000).toISOString(),
139
- completedUnits: 3,
140
138
  };
141
139
  writeFileSync(lockFile, JSON.stringify(staleLock, null, 2));
142
140
 
@@ -233,7 +231,6 @@ describe('session-lock-regression', async () => {
233
231
  unitType: 'execute-task',
234
232
  unitId: 'M001/S01/T01',
235
233
  unitStartedAt: new Date().toISOString(),
236
- completedUnits: 0,
237
234
  }, null, 2));
238
235
 
239
236
  const status = getSessionLockStatus(base);
@@ -64,7 +64,7 @@ test("stopAutoRemote cleans up stale lock (dead PID) and returns found:false", (
64
64
  const base = makeTmpBase();
65
65
  try {
66
66
  // Write a lock with a PID that doesn't exist
67
- writeLock(base, "execute-task", "M001/S01/T01", 3);
67
+ writeLock(base, "execute-task", "M001/S01/T01");
68
68
  // Overwrite PID to a dead one
69
69
  const lock = readCrashLock(base)!;
70
70
  const staleData = { ...lock, pid: 999999999 };
@@ -111,7 +111,6 @@ test("stopAutoRemote sends SIGTERM to a live process and returns found:true", {
111
111
  unitType: "execute-task",
112
112
  unitId: "M001/S01/T01",
113
113
  unitStartedAt: new Date().toISOString(),
114
- completedUnits: 0,
115
114
  };
116
115
  writeFileSync(join(base, ".gsd", "auto.lock"), JSON.stringify(lockData, null, 2), "utf-8");
117
116
 
@@ -143,7 +142,7 @@ test("lock file should be discoverable at project root, not worktree path", () =
143
142
 
144
143
  try {
145
144
  // Simulate: auto-mode writes lock to project root (the fix)
146
- writeLock(projectRoot, "execute-task", "M001/S01/T01", 0);
145
+ writeLock(projectRoot, "execute-task", "M001/S01/T01");
147
146
 
148
147
  // Second terminal checks project root — should find the lock
149
148
  const lock = readCrashLock(projectRoot);
@@ -0,0 +1,122 @@
1
+ // GSD Extension — sync-lock unit tests
2
+ // Tests acquireSyncLock() and releaseSyncLock().
3
+
4
+ import test from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import * as fs from 'node:fs';
7
+ import * as path from 'node:path';
8
+ import * as os from 'node:os';
9
+ import { acquireSyncLock, releaseSyncLock } from '../sync-lock.ts';
10
+
11
+ function tempDir(): string {
12
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-sync-lock-'));
13
+ }
14
+
15
+ function cleanupDir(dirPath: string): void {
16
+ try { fs.rmSync(dirPath, { recursive: true, force: true }); } catch { /* best effort */ }
17
+ }
18
+
19
+ // ─── acquireSyncLock ─────────────────────────────────────────────────────
20
+
21
+ test('sync-lock: acquireSyncLock returns { acquired: true } when no lock exists', () => {
22
+ const base = tempDir();
23
+ fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
24
+ try {
25
+ const result = acquireSyncLock(base);
26
+ assert.strictEqual(result.acquired, true);
27
+ } finally {
28
+ cleanupDir(base);
29
+ }
30
+ });
31
+
32
+ test('sync-lock: acquireSyncLock creates lock file at .gsd/sync.lock', () => {
33
+ const base = tempDir();
34
+ fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
35
+ try {
36
+ acquireSyncLock(base);
37
+ const lockPath = path.join(base, '.gsd', 'sync.lock');
38
+ assert.ok(fs.existsSync(lockPath), 'sync.lock should exist after acquire');
39
+ } finally {
40
+ cleanupDir(base);
41
+ }
42
+ });
43
+
44
+ test('sync-lock: lock file contains pid and acquired_at fields', () => {
45
+ const base = tempDir();
46
+ fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
47
+ try {
48
+ acquireSyncLock(base);
49
+ const lockPath = path.join(base, '.gsd', 'sync.lock');
50
+ const content = JSON.parse(fs.readFileSync(lockPath, 'utf-8'));
51
+ assert.strictEqual(typeof content.pid, 'number');
52
+ assert.strictEqual(typeof content.acquired_at, 'string');
53
+ } finally {
54
+ cleanupDir(base);
55
+ }
56
+ });
57
+
58
+ // ─── releaseSyncLock ─────────────────────────────────────────────────────
59
+
60
+ test('sync-lock: releaseSyncLock removes lock file', () => {
61
+ const base = tempDir();
62
+ fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
63
+ try {
64
+ acquireSyncLock(base);
65
+ const lockPath = path.join(base, '.gsd', 'sync.lock');
66
+ assert.ok(fs.existsSync(lockPath), 'lock file should exist before release');
67
+ releaseSyncLock(base);
68
+ assert.ok(!fs.existsSync(lockPath), 'lock file should not exist after release');
69
+ } finally {
70
+ cleanupDir(base);
71
+ }
72
+ });
73
+
74
+ test('sync-lock: releaseSyncLock is a no-op when no lock file exists', () => {
75
+ const base = tempDir();
76
+ fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
77
+ try {
78
+ // Should not throw
79
+ releaseSyncLock(base);
80
+ } finally {
81
+ cleanupDir(base);
82
+ }
83
+ });
84
+
85
+ // ─── acquire → release → re-acquire round-trip ───────────────────────────
86
+
87
+ test('sync-lock: can re-acquire after release', () => {
88
+ const base = tempDir();
89
+ fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
90
+ try {
91
+ const r1 = acquireSyncLock(base);
92
+ assert.strictEqual(r1.acquired, true, 'first acquire should succeed');
93
+ releaseSyncLock(base);
94
+ const r2 = acquireSyncLock(base);
95
+ assert.strictEqual(r2.acquired, true, 're-acquire after release should succeed');
96
+ releaseSyncLock(base);
97
+ } finally {
98
+ cleanupDir(base);
99
+ }
100
+ });
101
+
102
+ // ─── stale lock override ─────────────────────────────────────────────────
103
+
104
+ test('sync-lock: overrides stale lock file (mtime backdated)', (t) => {
105
+ const base = tempDir();
106
+ fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
107
+ const lockPath = path.join(base, '.gsd', 'sync.lock');
108
+ try {
109
+ // Write a lock file with a very old mtime (simulating staleness)
110
+ fs.writeFileSync(lockPath, JSON.stringify({ pid: 99999, acquired_at: new Date(0).toISOString() }));
111
+ // Backdate mtime by 2 minutes
112
+ const staleTime = new Date(Date.now() - 120_000);
113
+ fs.utimesSync(lockPath, staleTime, staleTime);
114
+
115
+ // Should override stale lock and acquire
116
+ const result = acquireSyncLock(base, 500);
117
+ assert.strictEqual(result.acquired, true, 'should acquire over stale lock');
118
+ releaseSyncLock(base);
119
+ } finally {
120
+ cleanupDir(base);
121
+ }
122
+ });
@@ -0,0 +1,175 @@
1
+ // GSD — unit-ownership tests
2
+ // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
+
4
+ import test from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import { mkdtempSync, rmSync, existsSync, readFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { tmpdir } from 'node:os';
9
+
10
+ import {
11
+ claimUnit,
12
+ releaseUnit,
13
+ getOwner,
14
+ checkOwnership,
15
+ taskUnitKey,
16
+ sliceUnitKey,
17
+ } from '../unit-ownership.ts';
18
+
19
+ function makeTmpBase(): string {
20
+ return mkdtempSync(join(tmpdir(), 'gsd-ownership-'));
21
+ }
22
+
23
+ function cleanup(base: string): void {
24
+ try { rmSync(base, { recursive: true, force: true }); } catch { /* noop */ }
25
+ }
26
+
27
+ // ─── Key builders ────────────────────────────────────────────────────────
28
+
29
+ test('taskUnitKey: builds correct key', () => {
30
+ assert.equal(taskUnitKey('M001', 'S01', 'T01'), 'M001/S01/T01');
31
+ });
32
+
33
+ test('sliceUnitKey: builds correct key', () => {
34
+ assert.equal(sliceUnitKey('M001', 'S01'), 'M001/S01');
35
+ });
36
+
37
+ // ─── Claim / get / release ───────────────────────────────────────────────
38
+
39
+ test('claimUnit: creates claim file and records agent', () => {
40
+ const base = makeTmpBase();
41
+ try {
42
+ claimUnit(base, 'M001/S01/T01', 'executor-01');
43
+
44
+ assert.ok(existsSync(join(base, '.gsd', 'unit-claims.json')), 'claim file should exist');
45
+ assert.equal(getOwner(base, 'M001/S01/T01'), 'executor-01');
46
+ } finally {
47
+ cleanup(base);
48
+ }
49
+ });
50
+
51
+ test('claimUnit: overwrites existing claim (last writer wins)', () => {
52
+ const base = makeTmpBase();
53
+ try {
54
+ claimUnit(base, 'M001/S01/T01', 'executor-01');
55
+ claimUnit(base, 'M001/S01/T01', 'executor-02');
56
+
57
+ assert.equal(getOwner(base, 'M001/S01/T01'), 'executor-02');
58
+ } finally {
59
+ cleanup(base);
60
+ }
61
+ });
62
+
63
+ test('claimUnit: multiple units can be claimed independently', () => {
64
+ const base = makeTmpBase();
65
+ try {
66
+ claimUnit(base, 'M001/S01/T01', 'agent-a');
67
+ claimUnit(base, 'M001/S01/T02', 'agent-b');
68
+
69
+ assert.equal(getOwner(base, 'M001/S01/T01'), 'agent-a');
70
+ assert.equal(getOwner(base, 'M001/S01/T02'), 'agent-b');
71
+ } finally {
72
+ cleanup(base);
73
+ }
74
+ });
75
+
76
+ test('getOwner: returns null when no claim file exists', () => {
77
+ const base = makeTmpBase();
78
+ try {
79
+ assert.equal(getOwner(base, 'M001/S01/T01'), null);
80
+ } finally {
81
+ cleanup(base);
82
+ }
83
+ });
84
+
85
+ test('getOwner: returns null for unclaimed unit', () => {
86
+ const base = makeTmpBase();
87
+ try {
88
+ claimUnit(base, 'M001/S01/T01', 'agent-a');
89
+ assert.equal(getOwner(base, 'M001/S01/T99'), null);
90
+ } finally {
91
+ cleanup(base);
92
+ }
93
+ });
94
+
95
+ test('releaseUnit: removes claim', () => {
96
+ const base = makeTmpBase();
97
+ try {
98
+ claimUnit(base, 'M001/S01/T01', 'agent-a');
99
+ releaseUnit(base, 'M001/S01/T01');
100
+
101
+ assert.equal(getOwner(base, 'M001/S01/T01'), null);
102
+ } finally {
103
+ cleanup(base);
104
+ }
105
+ });
106
+
107
+ test('releaseUnit: no-op for non-existent claim', () => {
108
+ const base = makeTmpBase();
109
+ try {
110
+ // Should not throw
111
+ releaseUnit(base, 'M001/S01/T01');
112
+ } finally {
113
+ cleanup(base);
114
+ }
115
+ });
116
+
117
+ // ─── checkOwnership ──────────────────────────────────────────────────────
118
+
119
+ test('checkOwnership: returns null when no actorName provided (opt-in)', () => {
120
+ const base = makeTmpBase();
121
+ try {
122
+ claimUnit(base, 'M001/S01/T01', 'agent-a');
123
+
124
+ // No actorName → ownership not enforced
125
+ assert.equal(checkOwnership(base, 'M001/S01/T01', undefined), null);
126
+ } finally {
127
+ cleanup(base);
128
+ }
129
+ });
130
+
131
+ test('checkOwnership: returns null when no claim file exists', () => {
132
+ const base = makeTmpBase();
133
+ try {
134
+ assert.equal(checkOwnership(base, 'M001/S01/T01', 'agent-a'), null);
135
+ } finally {
136
+ cleanup(base);
137
+ }
138
+ });
139
+
140
+ test('checkOwnership: returns null when unit is unclaimed', () => {
141
+ const base = makeTmpBase();
142
+ try {
143
+ claimUnit(base, 'M001/S01/T01', 'agent-a');
144
+
145
+ // Different unit, unclaimed
146
+ assert.equal(checkOwnership(base, 'M001/S01/T99', 'agent-b'), null);
147
+ } finally {
148
+ cleanup(base);
149
+ }
150
+ });
151
+
152
+ test('checkOwnership: returns null when actor matches owner', () => {
153
+ const base = makeTmpBase();
154
+ try {
155
+ claimUnit(base, 'M001/S01/T01', 'agent-a');
156
+
157
+ assert.equal(checkOwnership(base, 'M001/S01/T01', 'agent-a'), null);
158
+ } finally {
159
+ cleanup(base);
160
+ }
161
+ });
162
+
163
+ test('checkOwnership: returns error string when actor does not match owner', () => {
164
+ const base = makeTmpBase();
165
+ try {
166
+ claimUnit(base, 'M001/S01/T01', 'agent-a');
167
+
168
+ const err = checkOwnership(base, 'M001/S01/T01', 'agent-b');
169
+ assert.ok(err !== null, 'should return error');
170
+ assert.match(err!, /owned by agent-a/);
171
+ assert.match(err!, /not agent-b/);
172
+ } finally {
173
+ cleanup(base);
174
+ }
175
+ });