gsd-pi 2.80.0-dev.2848d9009 → 2.80.0-dev.3065f7bfa

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 (377) hide show
  1. package/README.md +4 -2
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  4. package/dist/resources/extensions/gsd/auto/phases.js +178 -108
  5. package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
  6. package/dist/resources/extensions/gsd/auto/run-unit.js +20 -30
  7. package/dist/resources/extensions/gsd/auto/session.js +8 -0
  8. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +5 -32
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -0
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +17 -4
  11. package/dist/resources/extensions/gsd/auto-prompts.js +103 -16
  12. package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
  13. package/dist/resources/extensions/gsd/auto-start.js +197 -6
  14. package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
  15. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
  16. package/dist/resources/extensions/gsd/auto-worktree.js +111 -1
  17. package/dist/resources/extensions/gsd/auto.js +86 -26
  18. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +42 -2
  19. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +49 -36
  20. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +15 -5
  21. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +33 -20
  22. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +7 -1
  23. package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +9 -3
  24. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +8 -2
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +259 -28
  26. package/dist/resources/extensions/gsd/bootstrap/system-context.js +82 -23
  27. package/dist/resources/extensions/gsd/clean-root-preflight.js +24 -6
  28. package/dist/resources/extensions/gsd/commands-handlers.js +23 -9
  29. package/dist/resources/extensions/gsd/context-budget.js +37 -2
  30. package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
  31. package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
  32. package/dist/resources/extensions/gsd/db-base-schema.js +4 -2
  33. package/dist/resources/extensions/gsd/db-migration-steps.js +6 -0
  34. package/dist/resources/extensions/gsd/ecosystem/gsd-extension-api.js +2 -0
  35. package/dist/resources/extensions/gsd/git-service.js +36 -4
  36. package/dist/resources/extensions/gsd/gsd-db.js +46 -13
  37. package/dist/resources/extensions/gsd/guided-flow.js +99 -38
  38. package/dist/resources/extensions/gsd/memory-store.js +69 -12
  39. package/dist/resources/extensions/gsd/migrate/command.js +40 -1
  40. package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
  41. package/dist/resources/extensions/gsd/native-git-bridge.js +32 -8
  42. package/dist/resources/extensions/gsd/orphan-stash-audit.js +101 -0
  43. package/dist/resources/extensions/gsd/parallel-orchestrator.js +13 -3
  44. package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
  45. package/dist/resources/extensions/gsd/pre-execution-checks.js +22 -0
  46. package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
  47. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
  48. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  49. package/dist/resources/extensions/gsd/prompts/execute-task.md +4 -2
  50. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  51. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  52. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
  53. package/dist/resources/extensions/gsd/prompts/replan-slice.md +2 -2
  54. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  55. package/dist/resources/extensions/gsd/quick.js +34 -2
  56. package/dist/resources/extensions/gsd/tools/context-mode-tool-result.js +15 -0
  57. package/dist/resources/extensions/gsd/tools/exec-search-tool.js +5 -0
  58. package/dist/resources/extensions/gsd/tools/exec-tool.js +3 -15
  59. package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
  60. package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
  61. package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
  62. package/dist/resources/extensions/gsd/tools/resume-tool.js +5 -0
  63. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
  64. package/dist/resources/extensions/gsd/unit-context-composer.js +12 -3
  65. package/dist/resources/extensions/gsd/unit-runtime.js +22 -0
  66. package/dist/resources/extensions/gsd/workflow-protocol.js +131 -0
  67. package/dist/resources/extensions/gsd/worktree-resolver.js +68 -21
  68. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  69. package/dist/web/standalone/.next/BUILD_ID +1 -1
  70. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  71. package/dist/web/standalone/.next/build-manifest.json +2 -2
  72. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  73. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.html +1 -1
  90. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  97. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  99. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  100. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  101. package/package.json +3 -3
  102. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  103. package/packages/mcp-server/dist/workflow-tools.js +22 -17
  104. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  105. package/packages/mcp-server/src/workflow-tools.test.ts +75 -2
  106. package/packages/mcp-server/src/workflow-tools.ts +30 -16
  107. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  108. package/packages/native/tsconfig.tsbuildinfo +1 -1
  109. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  110. package/packages/pi-agent-core/dist/agent-loop.js +4 -1
  111. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  112. package/packages/pi-agent-core/dist/agent.d.ts +5 -0
  113. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  114. package/packages/pi-agent-core/dist/agent.js +2 -0
  115. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  116. package/packages/pi-agent-core/dist/index.d.ts +1 -0
  117. package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
  118. package/packages/pi-agent-core/dist/index.js +2 -0
  119. package/packages/pi-agent-core/dist/index.js.map +1 -1
  120. package/packages/pi-agent-core/dist/token-audit.d.ts +47 -0
  121. package/packages/pi-agent-core/dist/token-audit.d.ts.map +1 -0
  122. package/packages/pi-agent-core/dist/token-audit.js +221 -0
  123. package/packages/pi-agent-core/dist/token-audit.js.map +1 -0
  124. package/packages/pi-agent-core/dist/types.d.ts +9 -0
  125. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  126. package/packages/pi-agent-core/dist/types.js.map +1 -1
  127. package/packages/pi-agent-core/src/agent-loop.test.ts +128 -0
  128. package/packages/pi-agent-core/src/agent-loop.ts +4 -1
  129. package/packages/pi-agent-core/src/agent.ts +8 -0
  130. package/packages/pi-agent-core/src/index.ts +2 -0
  131. package/packages/pi-agent-core/src/token-audit.test.ts +189 -0
  132. package/packages/pi-agent-core/src/token-audit.ts +287 -0
  133. package/packages/pi-agent-core/src/types.ts +14 -0
  134. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  135. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
  136. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +18 -0
  138. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
  140. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/agent-session.js +44 -7
  142. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +3 -1
  144. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  145. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
  146. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
  148. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
  150. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
  151. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
  152. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
  153. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
  154. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
  155. package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
  156. package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
  157. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
  158. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
  159. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
  160. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
  161. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/extensions/loader.js +8 -0
  163. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  164. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -0
  165. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  166. package/packages/pi-coding-agent/dist/core/extensions/runner.js +6 -6
  167. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  168. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +5 -3
  169. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  170. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +39 -1
  171. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  172. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  173. package/packages/pi-coding-agent/dist/core/hooks-runner.test.js +2 -0
  174. package/packages/pi-coding-agent/dist/core/hooks-runner.test.js.map +1 -1
  175. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts +2 -0
  176. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts.map +1 -0
  177. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js +46 -0
  178. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js.map +1 -0
  179. package/packages/pi-coding-agent/dist/core/sdk.d.ts +10 -2
  180. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  181. package/packages/pi-coding-agent/dist/core/sdk.js +74 -2
  182. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  183. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
  184. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  185. package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
  186. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  187. package/packages/pi-coding-agent/dist/core/skill-tool.test.js +22 -0
  188. package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -1
  189. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +6 -7
  190. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  191. package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -3
  192. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  193. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  194. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  195. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +3 -0
  196. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  197. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  198. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +13 -5
  199. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  200. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
  201. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  202. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  203. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
  204. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  205. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
  206. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
  207. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
  208. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
  209. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
  210. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
  211. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
  212. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
  213. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +25 -0
  214. package/packages/pi-coding-agent/src/core/agent-session.ts +48 -7
  215. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +3 -1
  216. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
  217. package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
  218. package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
  219. package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
  220. package/packages/pi-coding-agent/src/core/extensions/loader.ts +10 -0
  221. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +5 -3
  222. package/packages/pi-coding-agent/src/core/extensions/runner.ts +8 -5
  223. package/packages/pi-coding-agent/src/core/extensions/types.ts +42 -1
  224. package/packages/pi-coding-agent/src/core/hooks-runner.test.ts +2 -0
  225. package/packages/pi-coding-agent/src/core/sdk-tool-filter.test.ts +60 -0
  226. package/packages/pi-coding-agent/src/core/sdk.ts +85 -3
  227. package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
  228. package/packages/pi-coding-agent/src/core/skill-tool.test.ts +28 -0
  229. package/packages/pi-coding-agent/src/core/system-prompt.ts +8 -10
  230. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +4 -0
  231. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
  232. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +22 -7
  233. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
  234. package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
  235. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
  236. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  237. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  238. package/packages/pi-tui/dist/tui.js +18 -8
  239. package/packages/pi-tui/dist/tui.js.map +1 -1
  240. package/packages/pi-tui/src/tui.ts +20 -8
  241. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  242. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -2
  243. package/src/resources/extensions/gsd/auto/loop.ts +50 -8
  244. package/src/resources/extensions/gsd/auto/phases.ts +251 -172
  245. package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
  246. package/src/resources/extensions/gsd/auto/run-unit.ts +25 -30
  247. package/src/resources/extensions/gsd/auto/session.ts +8 -0
  248. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +8 -34
  249. package/src/resources/extensions/gsd/auto-dispatch.ts +16 -0
  250. package/src/resources/extensions/gsd/auto-post-unit.ts +18 -4
  251. package/src/resources/extensions/gsd/auto-prompts.ts +112 -15
  252. package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
  253. package/src/resources/extensions/gsd/auto-start.ts +230 -9
  254. package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
  255. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
  256. package/src/resources/extensions/gsd/auto-worktree.ts +123 -0
  257. package/src/resources/extensions/gsd/auto.ts +98 -21
  258. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +49 -1
  259. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +50 -36
  260. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +16 -5
  261. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +34 -19
  262. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +8 -1
  263. package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +10 -3
  264. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +9 -2
  265. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +305 -28
  266. package/src/resources/extensions/gsd/bootstrap/system-context.ts +90 -22
  267. package/src/resources/extensions/gsd/clean-root-preflight.ts +32 -7
  268. package/src/resources/extensions/gsd/commands-handlers.ts +34 -15
  269. package/src/resources/extensions/gsd/context-budget.ts +44 -2
  270. package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
  271. package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
  272. package/src/resources/extensions/gsd/db-base-schema.ts +4 -2
  273. package/src/resources/extensions/gsd/db-migration-steps.ts +8 -0
  274. package/src/resources/extensions/gsd/ecosystem/gsd-extension-api.ts +3 -0
  275. package/src/resources/extensions/gsd/git-service.ts +46 -8
  276. package/src/resources/extensions/gsd/gsd-db.ts +50 -13
  277. package/src/resources/extensions/gsd/guided-flow.ts +123 -45
  278. package/src/resources/extensions/gsd/memory-store.ts +77 -12
  279. package/src/resources/extensions/gsd/migrate/command.ts +47 -1
  280. package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
  281. package/src/resources/extensions/gsd/native-git-bridge.ts +39 -6
  282. package/src/resources/extensions/gsd/orphan-stash-audit.ts +117 -0
  283. package/src/resources/extensions/gsd/parallel-orchestrator.ts +13 -3
  284. package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
  285. package/src/resources/extensions/gsd/pre-execution-checks.ts +23 -0
  286. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  287. package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
  288. package/src/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
  289. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  290. package/src/resources/extensions/gsd/prompts/execute-task.md +4 -2
  291. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  292. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  293. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
  294. package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
  295. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  296. package/src/resources/extensions/gsd/quick.ts +37 -2
  297. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +2 -2
  298. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +417 -10
  299. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
  300. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
  301. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +168 -6
  302. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +15 -6
  303. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +14 -1
  304. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +31 -0
  305. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +3 -2
  306. package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
  307. package/src/resources/extensions/gsd/tests/context-store.test.ts +7 -1
  308. package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
  309. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
  310. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +5 -1
  311. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
  312. package/src/resources/extensions/gsd/tests/exec-history.test.ts +15 -0
  313. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +65 -0
  314. package/src/resources/extensions/gsd/tests/execute-task-rendering.test.ts +5 -2
  315. package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +219 -0
  316. package/src/resources/extensions/gsd/tests/finalize-survivor-branch.test.ts +132 -0
  317. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +54 -0
  318. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +6 -3
  319. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +239 -1
  320. package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +32 -0
  321. package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -0
  322. package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
  323. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -0
  324. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
  325. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
  326. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +242 -0
  327. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +34 -2
  328. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -0
  329. package/src/resources/extensions/gsd/tests/orphan-merge-bootstrap.test.ts +133 -0
  330. package/src/resources/extensions/gsd/tests/orphan-stash-audit.test.ts +201 -0
  331. package/src/resources/extensions/gsd/tests/parallel-orchestrator-fast-forward.test.ts +113 -0
  332. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
  333. package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
  334. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +45 -5
  335. package/src/resources/extensions/gsd/tests/prompt-duplication-cuts.test.ts +230 -0
  336. package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
  337. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
  338. package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +3 -3
  339. package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
  340. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +38 -17
  341. package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
  342. package/src/resources/extensions/gsd/tests/select-resumable-milestone.test.ts +96 -0
  343. package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +106 -0
  344. package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
  345. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +20 -0
  346. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
  347. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +55 -0
  348. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
  349. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
  350. package/src/resources/extensions/gsd/tests/system-context-memory.test.ts +112 -0
  351. package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +7 -9
  352. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +291 -0
  353. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
  354. package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
  355. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +37 -0
  356. package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +5 -4
  357. package/src/resources/extensions/gsd/tests/workflow-protocol-excerpt.test.ts +99 -0
  358. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
  359. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +1 -0
  360. package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +22 -19
  361. package/src/resources/extensions/gsd/tests/worktree-project-root-degrade.test.ts +66 -0
  362. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +167 -4
  363. package/src/resources/extensions/gsd/tools/context-mode-tool-result.ts +25 -0
  364. package/src/resources/extensions/gsd/tools/exec-search-tool.ts +7 -7
  365. package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -23
  366. package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
  367. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
  368. package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
  369. package/src/resources/extensions/gsd/tools/resume-tool.ts +7 -7
  370. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
  371. package/src/resources/extensions/gsd/unit-context-composer.ts +19 -4
  372. package/src/resources/extensions/gsd/unit-runtime.ts +25 -0
  373. package/src/resources/extensions/gsd/workflow-protocol.ts +160 -0
  374. package/src/resources/extensions/gsd/worktree-resolver.ts +85 -19
  375. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +0 -97
  376. /package/dist/web/standalone/.next/static/{vaQ47E8aNlbbNEDoeME6G → O2WvnXROue6l7nAp_4dhC}/_buildManifest.js +0 -0
  377. /package/dist/web/standalone/.next/static/{vaQ47E8aNlbbNEDoeME6G → O2WvnXROue6l7nAp_4dhC}/_ssgManifest.js +0 -0
@@ -0,0 +1,313 @@
1
+ // gsd-2 / dispatch rule coverage canary test
2
+ //
3
+ // Iterates DISPATCH_RULES in order against representative GSDState stubs and
4
+ // asserts that the first matching rule has the expected name and unitType
5
+ // (mirroring auto-dispatch's first-match-wins semantics). The goal is a
6
+ // canary: if a future PR adds a new rule in the wrong position and steals
7
+ // a match from an existing one, this test fails.
8
+
9
+ import test from "node:test";
10
+ import assert from "node:assert/strict";
11
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { tmpdir } from "node:os";
14
+
15
+ import { DISPATCH_RULES } from "../auto-dispatch.ts";
16
+ import type { DispatchContext, DispatchAction } from "../auto-dispatch.ts";
17
+ import type { GSDState } from "../types.ts";
18
+
19
+ // ─── State helpers ────────────────────────────────────────────────────────
20
+
21
+ function makeState(overrides: Partial<GSDState> = {}): GSDState {
22
+ return {
23
+ activeMilestone: { id: "M001", title: "Test Milestone" },
24
+ activeSlice: null,
25
+ activeTask: null,
26
+ phase: "pre-planning",
27
+ recentDecisions: [],
28
+ blockers: [],
29
+ nextAction: "",
30
+ registry: [],
31
+ ...overrides,
32
+ };
33
+ }
34
+
35
+ function makeCtx(basePath: string, state: GSDState, mid = "M001"): DispatchContext {
36
+ return {
37
+ basePath,
38
+ mid,
39
+ midTitle: "Test Milestone",
40
+ state,
41
+ prefs: undefined,
42
+ };
43
+ }
44
+
45
+ // ─── Disk scaffold helpers ────────────────────────────────────────────────
46
+
47
+ function writeMilestoneFile(basePath: string, mid: string, suffix: string, content = "stub\n"): void {
48
+ const dir = join(basePath, ".gsd", "milestones", mid);
49
+ mkdirSync(dir, { recursive: true });
50
+ writeFileSync(join(dir, `${mid}-${suffix}.md`), content);
51
+ }
52
+
53
+ function writeSliceFile(
54
+ basePath: string,
55
+ mid: string,
56
+ sid: string,
57
+ suffix: string,
58
+ content = "stub\n",
59
+ ): void {
60
+ const dir = join(basePath, ".gsd", "milestones", mid, "slices", sid);
61
+ mkdirSync(dir, { recursive: true });
62
+ writeFileSync(join(dir, `${sid}-${suffix}.md`), content);
63
+ }
64
+
65
+ function writeTaskPlan(basePath: string, mid: string, sid: string, tid: string): void {
66
+ const dir = join(basePath, ".gsd", "milestones", mid, "slices", sid, "tasks");
67
+ mkdirSync(dir, { recursive: true });
68
+ writeFileSync(join(dir, `${tid}-PLAN.md`), `# ${tid}\n\n## Steps\n- [ ] Step\n`);
69
+ }
70
+
71
+ // ─── Rule evaluation ──────────────────────────────────────────────────────
72
+
73
+ interface MatchEntry {
74
+ ruleName: string;
75
+ result: DispatchAction;
76
+ }
77
+
78
+ // First-match-wins semantics: walks DISPATCH_RULES in order and stops at the
79
+ // first non-null result. This mirrors the production resolver and is the
80
+ // canary against rule reordering or shadowing.
81
+ async function findFirstMatch(ctx: DispatchContext): Promise<MatchEntry | null> {
82
+ for (const rule of DISPATCH_RULES) {
83
+ const result = await rule.match(ctx);
84
+ if (result) return { ruleName: rule.name, result };
85
+ }
86
+ return null;
87
+ }
88
+
89
+ function assertMatch(
90
+ match: MatchEntry | null,
91
+ expected: { ruleName: string; action: DispatchAction["action"]; unitType?: string },
92
+ scenario: string,
93
+ ): void {
94
+ assert.ok(match, `${scenario}: no rule matched`);
95
+ assert.equal(match.ruleName, expected.ruleName, `${scenario}: matched rule mismatch`);
96
+ assert.equal(match.result.action, expected.action, `${scenario}: action mismatch`);
97
+ if (expected.action === "dispatch" && expected.unitType) {
98
+ assert.ok(
99
+ match.result.action === "dispatch" && match.result.unitType === expected.unitType,
100
+ `${scenario}: unitType mismatch (got ${
101
+ match.result.action === "dispatch" ? match.result.unitType : match.result.action
102
+ })`,
103
+ );
104
+ }
105
+ }
106
+
107
+ // ─── Tests ────────────────────────────────────────────────────────────────
108
+
109
+ test("dispatch-rule-coverage: escalating-task → stop (info)", async (t) => {
110
+ const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-esc-"));
111
+ t.after(() => rmSync(tmp, { recursive: true, force: true }));
112
+
113
+ const ctx = makeCtx(
114
+ tmp,
115
+ makeState({
116
+ phase: "escalating-task",
117
+ activeSlice: { id: "S01", title: "Slice" },
118
+ nextAction: "Resolve escalation X",
119
+ }),
120
+ );
121
+ const match = await findFirstMatch(ctx);
122
+ assertMatch(
123
+ match,
124
+ { ruleName: "escalating-task → pause-for-escalation", action: "stop" },
125
+ "escalating-task",
126
+ );
127
+ });
128
+
129
+ test("dispatch-rule-coverage: pre-planning, no CONTEXT → discuss-milestone", async (t) => {
130
+ const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-disc-"));
131
+ t.after(() => rmSync(tmp, { recursive: true, force: true }));
132
+
133
+ // Bare milestone dir, no CONTEXT/RESEARCH/ROADMAP files.
134
+ mkdirSync(join(tmp, ".gsd", "milestones", "M001"), { recursive: true });
135
+
136
+ const ctx = makeCtx(tmp, makeState({ phase: "pre-planning" }));
137
+ const match = await findFirstMatch(ctx);
138
+ assertMatch(
139
+ match,
140
+ {
141
+ ruleName: "pre-planning (no context) → discuss-milestone",
142
+ action: "dispatch",
143
+ unitType: "discuss-milestone",
144
+ },
145
+ "pre-planning no context",
146
+ );
147
+ });
148
+
149
+ test("dispatch-rule-coverage: pre-planning, has CONTEXT, no RESEARCH → research-milestone", async (t) => {
150
+ const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-res-"));
151
+ t.after(() => rmSync(tmp, { recursive: true, force: true }));
152
+
153
+ writeMilestoneFile(tmp, "M001", "CONTEXT", "# Context\n");
154
+
155
+ const ctx = makeCtx(tmp, makeState({ phase: "pre-planning" }));
156
+ const match = await findFirstMatch(ctx);
157
+ assertMatch(
158
+ match,
159
+ {
160
+ ruleName: "pre-planning (no research) → research-milestone",
161
+ action: "dispatch",
162
+ unitType: "research-milestone",
163
+ },
164
+ "pre-planning no research",
165
+ );
166
+ });
167
+
168
+ test("dispatch-rule-coverage: pre-planning, has CONTEXT + RESEARCH → plan-milestone", async (t) => {
169
+ const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-plan-m-"));
170
+ t.after(() => rmSync(tmp, { recursive: true, force: true }));
171
+
172
+ writeMilestoneFile(tmp, "M001", "CONTEXT", "# Context\n");
173
+ writeMilestoneFile(tmp, "M001", "RESEARCH", "# Research\n");
174
+
175
+ const ctx = makeCtx(tmp, makeState({ phase: "pre-planning" }));
176
+ const match = await findFirstMatch(ctx);
177
+ assertMatch(
178
+ match,
179
+ {
180
+ ruleName: "pre-planning (has research) → plan-milestone",
181
+ action: "dispatch",
182
+ unitType: "plan-milestone",
183
+ },
184
+ "pre-planning has research",
185
+ );
186
+ });
187
+
188
+ test("dispatch-rule-coverage: planning with active slice and skip_research → plan-slice", async (t) => {
189
+ const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-plan-s-"));
190
+ t.after(() => rmSync(tmp, { recursive: true, force: true }));
191
+
192
+ writeMilestoneFile(tmp, "M001", "CONTEXT", "# Context\n");
193
+ writeMilestoneFile(tmp, "M001", "ROADMAP", "# Roadmap\n");
194
+
195
+ const state = makeState({
196
+ phase: "planning",
197
+ activeSlice: { id: "S01", title: "First Slice" },
198
+ });
199
+ const ctx: DispatchContext = {
200
+ basePath: tmp,
201
+ mid: "M001",
202
+ midTitle: "Test Milestone",
203
+ state,
204
+ // Skip slice research so the parallel/single research rules fall through.
205
+ prefs: { phases: { skip_slice_research: true } } as DispatchContext["prefs"],
206
+ };
207
+ const match = await findFirstMatch(ctx);
208
+ assertMatch(
209
+ match,
210
+ {
211
+ ruleName: "planning → plan-slice",
212
+ action: "dispatch",
213
+ unitType: "plan-slice",
214
+ },
215
+ "planning → plan-slice",
216
+ );
217
+ });
218
+
219
+ test("dispatch-rule-coverage: executing with task plan present → execute-task", async (t) => {
220
+ const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-exec-"));
221
+ t.after(() => rmSync(tmp, { recursive: true, force: true }));
222
+
223
+ writeMilestoneFile(tmp, "M001", "CONTEXT", "# Context\n");
224
+ writeSliceFile(tmp, "M001", "S01", "PLAN", "# Plan\n");
225
+ writeTaskPlan(tmp, "M001", "S01", "T01");
226
+
227
+ const state = makeState({
228
+ phase: "executing",
229
+ activeSlice: { id: "S01", title: "First Slice" },
230
+ activeTask: { id: "T01", title: "First Task" },
231
+ });
232
+ // Disable reactive dispatch so the parallel batching rule falls through.
233
+ const ctx: DispatchContext = {
234
+ basePath: tmp,
235
+ mid: "M001",
236
+ midTitle: "Test Milestone",
237
+ state,
238
+ prefs: { reactive_execution: { enabled: false } } as DispatchContext["prefs"],
239
+ };
240
+ const match = await findFirstMatch(ctx);
241
+ assertMatch(
242
+ match,
243
+ {
244
+ ruleName: "executing → execute-task",
245
+ action: "dispatch",
246
+ unitType: "execute-task",
247
+ },
248
+ "executing → execute-task",
249
+ );
250
+ });
251
+
252
+ test("dispatch-rule-coverage: summarizing → complete-slice", async (t) => {
253
+ const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-sum-"));
254
+ t.after(() => rmSync(tmp, { recursive: true, force: true }));
255
+
256
+ // Rule "execution-entry phase (no context)" fires for summarizing if CONTEXT
257
+ // is missing — write it so the summarizing rule wins.
258
+ writeMilestoneFile(tmp, "M001", "CONTEXT", "# Context\n");
259
+
260
+ const ctx = makeCtx(
261
+ tmp,
262
+ makeState({
263
+ phase: "summarizing",
264
+ activeSlice: { id: "S01", title: "First Slice" },
265
+ }),
266
+ );
267
+ const match = await findFirstMatch(ctx);
268
+ assertMatch(
269
+ match,
270
+ {
271
+ ruleName: "summarizing → complete-slice",
272
+ action: "dispatch",
273
+ unitType: "complete-slice",
274
+ },
275
+ "summarizing → complete-slice",
276
+ );
277
+ });
278
+
279
+ test("dispatch-rule-coverage: complete phase → stop", async (t) => {
280
+ const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-done-"));
281
+ t.after(() => rmSync(tmp, { recursive: true, force: true }));
282
+
283
+ const ctx = makeCtx(
284
+ tmp,
285
+ makeState({
286
+ phase: "complete",
287
+ activeMilestone: null,
288
+ lastCompletedMilestone: { id: "M001", title: "Test Milestone" },
289
+ }),
290
+ );
291
+ const match = await findFirstMatch(ctx);
292
+ assertMatch(
293
+ match,
294
+ { ruleName: "complete → stop", action: "stop" },
295
+ "complete → stop",
296
+ );
297
+ });
298
+
299
+ // ─── Ordering canary: every scenario above resolves to exactly one rule ────
300
+
301
+ test("dispatch-rule-coverage: rule registry has the expected size", () => {
302
+ // Sanity check that complements the per-state assertions: if someone adds a
303
+ // new rule, this number changes — prompting them to add a state stub above.
304
+ // Exact count is a brittle but useful canary; update when adding rules
305
+ // intentionally.
306
+ assert.equal(
307
+ DISPATCH_RULES.length,
308
+ 29,
309
+ `DISPATCH_RULES length changed (got ${DISPATCH_RULES.length}). ` +
310
+ "If you added a rule, add a state stub to dispatch-rule-coverage.test.ts " +
311
+ "and update this expected count.",
312
+ );
313
+ });
@@ -108,6 +108,21 @@ test('executeExecSearch: returns helpful empty-state message when no matches', (
108
108
  }
109
109
  });
110
110
 
111
+ test('executeExecSearch: returns disabled error when context_mode.enabled=false', () => {
112
+ const base = freshBase();
113
+ try {
114
+ writeRun(base, 'should-not-surface', { stdout: 'hidden\n' });
115
+ const result = executeExecSearch(
116
+ { query: 'hidden' },
117
+ { baseDir: base, preferences: { context_mode: { enabled: false } } },
118
+ );
119
+ assert.equal(result.isError, true);
120
+ assert.equal((result.details as { error?: string }).error, 'context_mode_disabled');
121
+ } finally {
122
+ cleanup(base);
123
+ }
124
+ });
125
+
111
126
  test('executeExecSearch: includes stdout_path and preview in details', () => {
112
127
  const base = freshBase();
113
128
  try {
@@ -7,6 +7,7 @@ import { join } from 'node:path';
7
7
  import { EXEC_DEFAULTS, runExecSandbox, type ExecSandboxOptions } from '../exec-sandbox.ts';
8
8
  import { buildExecOptions, executeGsdExec } from '../tools/exec-tool.ts';
9
9
  import { isContextModeEnabled } from '../preferences-types.ts';
10
+ import { validatePreferences } from '../preferences-validation.ts';
10
11
 
11
12
  function freshBase(): string {
12
13
  return mkdtempSync(join(tmpdir(), 'gsd-exec-test-'));
@@ -174,6 +175,52 @@ test('executeGsdExec: runs when enabled explicitly set to true', async () => {
174
175
  }
175
176
  });
176
177
 
178
+ test('executeGsdExec: forwards custom exec_env_allowlist from preferences', async () => {
179
+ const base = freshBase();
180
+ try {
181
+ const result = await executeGsdExec(
182
+ {
183
+ runtime: 'bash',
184
+ script: 'printf "allowed=%s blocked=%s\\n" "$GSD_ALLOWED" "$GSD_BLOCKED"',
185
+ },
186
+ {
187
+ baseDir: base,
188
+ preferences: {
189
+ context_mode: {
190
+ enabled: true,
191
+ exec_env_allowlist: ['GSD_ALLOWED'],
192
+ },
193
+ },
194
+ env: {
195
+ PATH: '/usr/bin:/bin',
196
+ HOME: '/tmp',
197
+ GSD_ALLOWED: 'yes',
198
+ GSD_BLOCKED: 'no',
199
+ },
200
+ },
201
+ );
202
+ assert.ok(!result.isError);
203
+ assert.match(result.content[0].text, /allowed=yes blocked=/);
204
+ assert.doesNotMatch(result.content[0].text, /blocked=no/);
205
+ } finally {
206
+ cleanup(base);
207
+ }
208
+ });
209
+
210
+ test('executeGsdExec: enforces per-call timeout override end-to-end', async () => {
211
+ const base = freshBase();
212
+ try {
213
+ const result = await executeGsdExec(
214
+ { runtime: 'bash', script: 'sleep 2', timeout_ms: 1 },
215
+ { baseDir: base, preferences: { context_mode: { enabled: true, exec_timeout_ms: 10_000 } } },
216
+ );
217
+ assert.equal(result.details.timed_out, true);
218
+ assert.equal(result.isError, true);
219
+ } finally {
220
+ cleanup(base);
221
+ }
222
+ });
223
+
177
224
  test('executeGsdExec: rejects empty script', async () => {
178
225
  const base = freshBase();
179
226
  try {
@@ -188,6 +235,24 @@ test('executeGsdExec: rejects empty script', async () => {
188
235
  }
189
236
  });
190
237
 
238
+ test('validatePreferences: rejects invalid context_mode preference values', () => {
239
+ const result = validatePreferences({
240
+ context_mode: {
241
+ enabled: 'false',
242
+ exec_timeout_ms: 999,
243
+ exec_stdout_cap_bytes: 1,
244
+ exec_digest_chars: -1,
245
+ exec_env_allowlist: ['GOOD_NAME', 'bad-name'],
246
+ },
247
+ } as any);
248
+ assert.ok(result.errors.length > 0);
249
+ assert.ok(result.errors.includes('context_mode.enabled must be a boolean'));
250
+ assert.ok(result.errors.includes('context_mode.exec_timeout_ms must be a number between 1000 and 600000'));
251
+ assert.ok(result.errors.includes('context_mode.exec_stdout_cap_bytes must be a number between 4096 and 16777216'));
252
+ assert.ok(result.errors.includes('context_mode.exec_digest_chars must be a number between 0 and 4000'));
253
+ assert.ok(result.errors.includes('context_mode.exec_env_allowlist must be an array of valid env var names'));
254
+ });
255
+
191
256
  test('isContextModeEnabled: defaults to true; only explicit false disables', () => {
192
257
  assert.equal(isContextModeEnabled(undefined), true, 'undefined prefs → on');
193
258
  assert.equal(isContextModeEnabled(null), true, 'null prefs → on');
@@ -39,6 +39,7 @@ test("execute-task prompt renders compact execution and completion gates", async
39
39
  taskPlanPath: ".gsd/milestones/M001/slices/S01/tasks/T01-PLAN.md",
40
40
  priorTaskLines: "- None",
41
41
  skillActivation: "Load relevant skills.",
42
+ inlinedTemplates: "### Output Template: Task Summary\nSource: `templates/task-summary.md`",
42
43
  templatesDir: join(fixtureRoot, "templates"),
43
44
  taskSummaryTemplatePath: "C:\\Users\\Test\\.gsd\\agent\\extensions\\gsd\\templates\\task-summary.md",
44
45
  verificationBudget: "~10K chars",
@@ -46,12 +47,14 @@ test("execute-task prompt renders compact execution and completion gates", async
46
47
  });
47
48
 
48
49
  assert.match(prompt, /You execute\./);
49
- assert.match(prompt, /Call `memory_query`/);
50
+ assert.match(prompt, /Call `memory_query`.*only when no injected memory block exists/s);
50
51
  assert.match(prompt, /Before any `Write` that creates an artifact or output file/);
51
52
  assert.match(prompt, /Build real behavior/);
52
53
  assert.match(prompt, /Background process rule/);
53
54
  assert.match(prompt, /blocker_discovered: true/);
54
- assert.match(prompt, /C:\\Users\\Test\\.gsd\\agent\\extensions\\gsd\\templates\\task-summary\.md/);
55
+ assert.match(prompt, /Use the inlined Task Summary template below/);
56
+ assert.match(prompt, /Read `C:\\Users\\Test\\.gsd\\agent\\extensions\\gsd\\templates\\task-summary\.md` only if the inlined template is absent or visibly truncated/);
57
+ assert.match(prompt, /### Output Template: Task Summary/);
55
58
  assert.doesNotMatch(prompt, /\{\{templatesDir\}\}\/task-summary\.md/);
56
59
  assert.match(prompt, /Call `gsd_task_complete`/);
57
60
  assert.match(prompt, /Do not run git commands/);
@@ -0,0 +1,219 @@
1
+ // GSD-2 + src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts
2
+ // Regression: when createAutoWorktree reuses an existing milestone branch,
3
+ // it must be fast-forwarded onto integration so the next milestone forks
4
+ // from up-to-date code (#5538-followup).
5
+
6
+ import { describe, test, beforeEach, afterEach } from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import { execFileSync } from "node:child_process";
9
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import { basename, join } from "node:path";
12
+
13
+ import {
14
+ fastForwardReusedMilestoneBranchIfSafe,
15
+ _isBranchCheckedOutElsewhere,
16
+ } from "../auto-worktree.js";
17
+
18
+ const NO_PROMPT_ENV = {
19
+ ...process.env,
20
+ GIT_TERMINAL_PROMPT: "0",
21
+ GIT_AUTHOR_NAME: "test",
22
+ GIT_AUTHOR_EMAIL: "test@example.com",
23
+ GIT_COMMITTER_NAME: "test",
24
+ GIT_COMMITTER_EMAIL: "test@example.com",
25
+ };
26
+
27
+ function git(cwd: string, ...args: string[]): string {
28
+ return execFileSync("git", args, {
29
+ cwd,
30
+ stdio: ["ignore", "pipe", "pipe"],
31
+ encoding: "utf-8",
32
+ env: NO_PROMPT_ENV,
33
+ });
34
+ }
35
+
36
+ function rev(cwd: string, ref: string): string {
37
+ return git(cwd, "rev-parse", ref).trim();
38
+ }
39
+
40
+ describe("fastForwardReusedMilestoneBranchIfSafe", () => {
41
+ let repo: string;
42
+
43
+ beforeEach(() => {
44
+ repo = mkdtempSync(join(tmpdir(), "ff-reused-branch-"));
45
+ git(repo, "init", "-q", "-b", "main");
46
+ git(repo, "config", "user.email", "test@example.com");
47
+ git(repo, "config", "user.name", "test");
48
+ writeFileSync(join(repo, "seed.txt"), "seed\n");
49
+ git(repo, "add", "seed.txt");
50
+ git(repo, "commit", "-q", "-m", "initial");
51
+ });
52
+
53
+ afterEach(() => {
54
+ rmSync(repo, { recursive: true, force: true });
55
+ });
56
+
57
+ test("fast-forwards a milestone branch that is strictly behind integration (regression: stale base)", () => {
58
+ // Create milestone/M001 from main's initial commit, then advance main.
59
+ git(repo, "branch", "milestone/M001");
60
+ const m001Initial = rev(repo, "milestone/M001");
61
+
62
+ writeFileSync(join(repo, "seed.txt"), "advanced\n");
63
+ git(repo, "add", "seed.txt");
64
+ git(repo, "commit", "-q", "-m", "main moved forward");
65
+ const mainTip = rev(repo, "main");
66
+
67
+ assert.notEqual(m001Initial, mainTip, "main must be ahead before the test");
68
+
69
+ fastForwardReusedMilestoneBranchIfSafe(repo, "M001", "milestone/M001");
70
+
71
+ assert.equal(
72
+ rev(repo, "milestone/M001"),
73
+ mainTip,
74
+ "milestone/M001 must be fast-forwarded to main's tip",
75
+ );
76
+ });
77
+
78
+ test("does not touch a milestone branch that has its own commits ahead", () => {
79
+ // Branch from main, add a unique commit, then advance main.
80
+ git(repo, "checkout", "-q", "-b", "milestone/M001");
81
+ writeFileSync(join(repo, "milestone-only.txt"), "milestone work\n");
82
+ git(repo, "add", "milestone-only.txt");
83
+ git(repo, "commit", "-q", "-m", "M001 work");
84
+ const milestoneTip = rev(repo, "milestone/M001");
85
+
86
+ git(repo, "checkout", "-q", "main");
87
+ writeFileSync(join(repo, "seed.txt"), "advanced\n");
88
+ git(repo, "add", "seed.txt");
89
+ git(repo, "commit", "-q", "-m", "main moved forward");
90
+
91
+ fastForwardReusedMilestoneBranchIfSafe(repo, "M001", "milestone/M001");
92
+
93
+ assert.equal(
94
+ rev(repo, "milestone/M001"),
95
+ milestoneTip,
96
+ "diverged milestone branch must NOT be touched (would lose work)",
97
+ );
98
+ });
99
+
100
+ test("is a no-op when milestone branch is already up-to-date with main", () => {
101
+ git(repo, "branch", "milestone/M001");
102
+ const before = rev(repo, "milestone/M001");
103
+
104
+ fastForwardReusedMilestoneBranchIfSafe(repo, "M001", "milestone/M001");
105
+
106
+ assert.equal(rev(repo, "milestone/M001"), before, "ref must not move");
107
+ });
108
+
109
+ test("does nothing when the milestone branch does not exist", () => {
110
+ // Should silently return — no error, no side effects.
111
+ assert.doesNotThrow(() =>
112
+ fastForwardReusedMilestoneBranchIfSafe(repo, "M999", "milestone/M999"),
113
+ );
114
+ });
115
+
116
+ test("does nothing in a non-git directory", () => {
117
+ const nonRepo = mkdtempSync(join(tmpdir(), "ff-not-a-repo-"));
118
+ try {
119
+ assert.doesNotThrow(() =>
120
+ fastForwardReusedMilestoneBranchIfSafe(nonRepo, "M001", "milestone/M001"),
121
+ );
122
+ } finally {
123
+ rmSync(nonRepo, { recursive: true, force: true });
124
+ }
125
+ });
126
+
127
+ test("skips fast-forward when branch is checked out in another worktree (peer-review regression)", () => {
128
+ // Codex peer review caught: `nativeUpdateRef` succeeds even when the
129
+ // branch is checked out in a linked worktree, leaving that worktree's
130
+ // HEAD inconsistent with its index/work tree. The fix calls
131
+ // `nativeWorktreeList` first and skips the FF if any worktree owns the
132
+ // target branch. This test sets up the exact scenario.
133
+ git(repo, "branch", "milestone/M001");
134
+ const m001Initial = rev(repo, "milestone/M001");
135
+
136
+ // Add a linked worktree that checks out milestone/M001.
137
+ const wtPath = join(repo, "..", `${basename(repo)}-wt`);
138
+ git(repo, "worktree", "add", wtPath, "milestone/M001");
139
+
140
+ // Advance main so a fast-forward would otherwise apply.
141
+ writeFileSync(join(repo, "seed.txt"), "advanced\n");
142
+ git(repo, "add", "seed.txt");
143
+ git(repo, "commit", "-q", "-m", "main moved forward");
144
+
145
+ try {
146
+ fastForwardReusedMilestoneBranchIfSafe(repo, "M001", "milestone/M001");
147
+
148
+ assert.equal(
149
+ rev(repo, "milestone/M001"),
150
+ m001Initial,
151
+ "milestone/M001 must NOT move while a linked worktree has it checked out",
152
+ );
153
+ } finally {
154
+ git(repo, "worktree", "remove", "--force", wtPath);
155
+ rmSync(wtPath, { recursive: true, force: true });
156
+ }
157
+ });
158
+ });
159
+
160
+ describe("_isBranchCheckedOutElsewhere", () => {
161
+ let repo: string;
162
+
163
+ beforeEach(() => {
164
+ repo = mkdtempSync(join(tmpdir(), "is-checked-out-"));
165
+ git(repo, "init", "-q", "-b", "main");
166
+ git(repo, "config", "user.email", "test@example.com");
167
+ git(repo, "config", "user.name", "test");
168
+ writeFileSync(join(repo, "seed.txt"), "seed\n");
169
+ git(repo, "add", "seed.txt");
170
+ git(repo, "commit", "-q", "-m", "initial");
171
+ });
172
+
173
+ afterEach(() => {
174
+ rmSync(repo, { recursive: true, force: true });
175
+ });
176
+
177
+ test("returns true when branch is checked out in a linked worktree", () => {
178
+ git(repo, "branch", "milestone/M001");
179
+ const wtPath = join(repo, "..", `${basename(repo)}-wt`);
180
+ git(repo, "worktree", "add", wtPath, "milestone/M001");
181
+ try {
182
+ assert.equal(_isBranchCheckedOutElsewhere(repo, "milestone/M001"), true);
183
+ } finally {
184
+ git(repo, "worktree", "remove", "--force", wtPath);
185
+ rmSync(wtPath, { recursive: true, force: true });
186
+ }
187
+ });
188
+
189
+ test("returns true when branch is checked out in the main worktree itself", () => {
190
+ // The default checkout. `git worktree list --porcelain` reports the
191
+ // primary worktree too, so a branch checked out there counts as
192
+ // "checked out elsewhere" relative to a fresh ref update intent.
193
+ git(repo, "checkout", "-q", "-b", "milestone/M002");
194
+ assert.equal(_isBranchCheckedOutElsewhere(repo, "milestone/M002"), true);
195
+ });
196
+
197
+ test("returns false when branch exists but is not checked out anywhere", () => {
198
+ git(repo, "branch", "milestone/M003");
199
+ assert.equal(_isBranchCheckedOutElsewhere(repo, "milestone/M003"), false);
200
+ });
201
+
202
+ test("returns false for an unknown branch in a clean repo", () => {
203
+ assert.equal(_isBranchCheckedOutElsewhere(repo, "milestone/M999"), false);
204
+ });
205
+
206
+ test("returns false on a non-git directory (empty worktree list)", () => {
207
+ // nativeWorktreeList does not throw on a non-repo — it returns []. The
208
+ // parent function `fastForwardReusedMilestoneBranchIfSafe` never reaches
209
+ // this code path on a non-repo because `nativeBranchExists` short-circuits
210
+ // earlier. Documenting actual behavior so future readers don't expect a
211
+ // fail-safe `true` here.
212
+ const nonRepo = mkdtempSync(join(tmpdir(), "is-checked-out-not-repo-"));
213
+ try {
214
+ assert.equal(_isBranchCheckedOutElsewhere(nonRepo, "milestone/M001"), false);
215
+ } finally {
216
+ rmSync(nonRepo, { recursive: true, force: true });
217
+ }
218
+ });
219
+ });