gsd-pi 2.76.0-dev.fe143342a → 2.77.0-dev.1d17f366c

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 (597) hide show
  1. package/README.md +17 -35
  2. package/dist/claude-cli-check.js +32 -3
  3. package/dist/cli-web-branch.d.ts +1 -0
  4. package/dist/cli-web-branch.js +3 -0
  5. package/dist/cli.js +38 -2
  6. package/dist/extension-discovery.d.ts +6 -0
  7. package/dist/extension-discovery.js +37 -0
  8. package/dist/extension-registry.d.ts +3 -0
  9. package/dist/extension-sort.d.ts +18 -0
  10. package/dist/extension-sort.js +114 -0
  11. package/dist/extension-validator.d.ts +47 -0
  12. package/dist/extension-validator.js +127 -0
  13. package/dist/loader.js +35 -7
  14. package/dist/onboarding.js +45 -0
  15. package/dist/provider-migrations.d.ts +18 -0
  16. package/dist/provider-migrations.js +14 -0
  17. package/dist/resources/extensions/claude-code-cli/readiness.js +4 -3
  18. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +78 -59
  19. package/dist/resources/extensions/cmux/index.js +20 -0
  20. package/dist/resources/extensions/github-sync/templates.js +103 -0
  21. package/dist/resources/extensions/google-search/extension-manifest.json +5 -4
  22. package/dist/resources/extensions/google-search/index.js +3 -375
  23. package/dist/resources/extensions/gsd/abandon-detect.js +44 -0
  24. package/dist/resources/extensions/gsd/auto/loop.js +90 -2
  25. package/dist/resources/extensions/gsd/auto/phases.js +95 -21
  26. package/dist/resources/extensions/gsd/auto/resolve.js +24 -0
  27. package/dist/resources/extensions/gsd/auto/run-unit.js +48 -4
  28. package/dist/resources/extensions/gsd/auto/session.js +18 -1
  29. package/dist/resources/extensions/gsd/auto/turn-epoch.js +95 -0
  30. package/dist/resources/extensions/gsd/auto-dispatch.js +115 -17
  31. package/dist/resources/extensions/gsd/auto-loop.js +1 -1
  32. package/dist/resources/extensions/gsd/auto-model-selection.js +1 -1
  33. package/dist/resources/extensions/gsd/auto-post-unit.js +90 -2
  34. package/dist/resources/extensions/gsd/auto-prompts.js +14 -0
  35. package/dist/resources/extensions/gsd/auto-recovery.js +46 -1
  36. package/dist/resources/extensions/gsd/auto-start.js +45 -39
  37. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +11 -5
  38. package/dist/resources/extensions/gsd/auto-unit-closeout.js +11 -2
  39. package/dist/resources/extensions/gsd/auto-worktree.js +109 -61
  40. package/dist/resources/extensions/gsd/auto.js +97 -31
  41. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +27 -1
  42. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +4 -2
  43. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +11 -0
  44. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -6
  45. package/dist/resources/extensions/gsd/bootstrap/system-context.js +11 -6
  46. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +34 -2
  47. package/dist/resources/extensions/gsd/clean-root-preflight.js +93 -0
  48. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +31 -4
  49. package/dist/resources/extensions/gsd/commands-cmux.js +9 -6
  50. package/dist/resources/extensions/gsd/commands-extensions.js +634 -43
  51. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +968 -23
  52. package/dist/resources/extensions/gsd/dispatch-guard.js +29 -3
  53. package/dist/resources/extensions/gsd/file-lock.js +49 -9
  54. package/dist/resources/extensions/gsd/git-service.js +1 -0
  55. package/dist/resources/extensions/gsd/gitignore.js +2 -0
  56. package/dist/resources/extensions/gsd/gsd-db.js +90 -30
  57. package/dist/resources/extensions/gsd/guided-flow-queue.js +4 -1
  58. package/dist/resources/extensions/gsd/guided-flow.js +212 -9
  59. package/dist/resources/extensions/gsd/health-widget.js +4 -1
  60. package/dist/resources/extensions/gsd/journal.js +17 -2
  61. package/dist/resources/extensions/gsd/key-manager.js +22 -0
  62. package/dist/resources/extensions/gsd/milestone-actions.js +15 -0
  63. package/dist/resources/extensions/gsd/milestone-summary-classifier.js +37 -0
  64. package/dist/resources/extensions/gsd/model-router.js +36 -3
  65. package/dist/resources/extensions/gsd/notifications.js +30 -16
  66. package/dist/resources/extensions/gsd/pre-execution-checks.js +31 -6
  67. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +29 -2
  68. package/dist/resources/extensions/gsd/prompts/discuss.md +29 -2
  69. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
  70. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  71. package/dist/resources/extensions/gsd/prompts/system.md +1 -0
  72. package/dist/resources/extensions/gsd/reports.js +5 -4
  73. package/dist/resources/extensions/gsd/safety/evidence-collector.js +96 -0
  74. package/dist/resources/extensions/gsd/safety/file-change-validator.js +12 -4
  75. package/dist/resources/extensions/gsd/safety/safety-harness.js +5 -1
  76. package/dist/resources/extensions/gsd/state-transition-matrix.js +118 -0
  77. package/dist/resources/extensions/gsd/state.js +25 -25
  78. package/dist/resources/extensions/gsd/token-counter.js +22 -5
  79. package/dist/resources/extensions/gsd/tools/complete-milestone.js +16 -10
  80. package/dist/resources/extensions/gsd/tools/complete-slice.js +21 -0
  81. package/dist/resources/extensions/gsd/tools/complete-task.js +31 -0
  82. package/dist/resources/extensions/gsd/uok/audit.js +18 -2
  83. package/dist/resources/extensions/gsd/uok/dispatch-envelope.js +33 -0
  84. package/dist/resources/extensions/gsd/uok/execution-graph.js +10 -0
  85. package/dist/resources/extensions/gsd/uok/gitops.js +2 -1
  86. package/dist/resources/extensions/gsd/uok/loop-adapter.js +37 -10
  87. package/dist/resources/extensions/gsd/uok/parity-report.js +58 -0
  88. package/dist/resources/extensions/gsd/uok/plan-v2.js +30 -7
  89. package/dist/resources/extensions/gsd/uok/writer.js +82 -0
  90. package/dist/resources/extensions/gsd/workflow-logger.js +10 -2
  91. package/dist/resources/extensions/gsd/worktree-manager.js +1 -0
  92. package/dist/resources/extensions/gsd/worktree-resolver.js +50 -10
  93. package/dist/resources/extensions/mcp-client/auth.js +10 -1
  94. package/dist/resources/extensions/mcp-client/index.js +118 -9
  95. package/dist/resources/extensions/shared/cmux-events.js +12 -0
  96. package/dist/resources/extensions/shared/rtk-session-stats.js +1 -2
  97. package/dist/resources/skills/create-skill/SKILL.md +2 -2
  98. package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +4 -4
  99. package/dist/resources/skills/create-skill/workflows/audit-skill.md +4 -4
  100. package/dist/resources/skills/create-skill/workflows/create-new-skill.md +5 -5
  101. package/dist/resources/skills/verify-before-complete/SKILL.md +2 -1
  102. package/dist/resources/skills/write-docs/SKILL.md +2 -1
  103. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  104. package/dist/web/standalone/.next/BUILD_ID +1 -1
  105. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  106. package/dist/web/standalone/.next/build-manifest.json +3 -3
  107. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  108. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  109. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  110. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  115. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  117. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  118. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  119. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  120. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  121. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  122. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  123. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  124. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  125. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  127. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  129. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  131. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  133. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  135. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  137. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  141. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  153. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  155. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  157. package/dist/web/standalone/.next/server/app/api/notifications/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  159. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  161. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  163. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  165. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  167. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  169. package/dist/web/standalone/.next/server/app/api/session/events/route.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  171. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  173. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  175. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  177. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  179. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  181. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  183. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  185. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  187. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  189. package/dist/web/standalone/.next/server/app/index.html +1 -1
  190. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  191. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  192. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  193. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  194. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  195. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  196. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  197. package/dist/web/standalone/.next/server/chunks/1926.js +1 -0
  198. package/dist/web/standalone/.next/server/chunks/6897.js +2 -2
  199. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  200. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  201. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  202. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  203. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  204. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  205. package/dist/web/standalone/.next/static/chunks/2826.e9f5195e91f9cad2.js +11 -0
  206. package/dist/web/standalone/.next/static/chunks/{webpack-5fc74f13a25fa1bb.js → webpack-2e68521d7c82f7c2.js} +1 -1
  207. package/dist/welcome-screen.js +6 -1
  208. package/dist/wizard.js +2 -0
  209. package/package.json +16 -14
  210. package/packages/daemon/package.json +2 -2
  211. package/packages/mcp-server/README.md +3 -3
  212. package/packages/mcp-server/dist/env-writer.d.ts +1 -0
  213. package/packages/mcp-server/dist/env-writer.d.ts.map +1 -1
  214. package/packages/mcp-server/dist/env-writer.js +74 -6
  215. package/packages/mcp-server/dist/env-writer.js.map +1 -1
  216. package/packages/mcp-server/dist/remote-questions.d.ts +45 -0
  217. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -0
  218. package/packages/mcp-server/dist/remote-questions.js +732 -0
  219. package/packages/mcp-server/dist/remote-questions.js.map +1 -0
  220. package/packages/mcp-server/dist/server.d.ts +7 -0
  221. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  222. package/packages/mcp-server/dist/server.js +95 -10
  223. package/packages/mcp-server/dist/server.js.map +1 -1
  224. package/packages/mcp-server/dist/session-manager.d.ts +14 -0
  225. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -1
  226. package/packages/mcp-server/dist/session-manager.js +49 -1
  227. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  228. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  229. package/packages/mcp-server/dist/workflow-tools.js +15 -6
  230. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  231. package/packages/mcp-server/package.json +9 -3
  232. package/packages/mcp-server/src/env-writer.test.ts +79 -1
  233. package/packages/mcp-server/src/env-writer.ts +76 -6
  234. package/packages/mcp-server/src/mcp-server.test.ts +67 -0
  235. package/packages/mcp-server/src/readers/readers.test.ts +5 -1
  236. package/packages/mcp-server/src/remote-questions.test.ts +294 -0
  237. package/packages/mcp-server/src/remote-questions.ts +916 -0
  238. package/packages/mcp-server/src/server.ts +118 -16
  239. package/packages/mcp-server/src/session-manager.ts +43 -1
  240. package/packages/mcp-server/src/workflow-tools.test.ts +44 -0
  241. package/packages/mcp-server/src/workflow-tools.ts +19 -6
  242. package/packages/mcp-server/tsconfig.test.json +19 -0
  243. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  244. package/packages/native/package.json +6 -1
  245. package/packages/native/src/__tests__/clipboard.test.mjs +69 -23
  246. package/packages/native/tsconfig.tsbuildinfo +1 -1
  247. package/packages/pi-agent-core/package.json +6 -1
  248. package/packages/pi-agent-core/src/agent-loop.test.ts +220 -15
  249. package/packages/pi-ai/dist/models/custom.d.ts +38 -0
  250. package/packages/pi-ai/dist/models/custom.d.ts.map +1 -1
  251. package/packages/pi-ai/dist/models/custom.js +41 -0
  252. package/packages/pi-ai/dist/models/custom.js.map +1 -1
  253. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +1 -1
  254. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -1
  255. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  256. package/packages/pi-ai/dist/providers/anthropic-shared.js +27 -4
  257. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  258. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  259. package/packages/pi-ai/dist/providers/anthropic.js +8 -3
  260. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  261. package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts +2 -0
  262. package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts.map +1 -0
  263. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js +80 -0
  264. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js.map +1 -0
  265. package/packages/pi-ai/dist/providers/simple-options.d.ts +10 -0
  266. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  267. package/packages/pi-ai/dist/providers/simple-options.js +16 -1
  268. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  269. package/packages/pi-ai/package.json +6 -1
  270. package/packages/pi-ai/src/models/custom.ts +42 -0
  271. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +1 -1
  272. package/packages/pi-ai/src/providers/anthropic-shared.ts +26 -5
  273. package/packages/pi-ai/src/providers/anthropic.ts +9 -3
  274. package/packages/pi-ai/src/providers/minimax-tool-name.test.ts +98 -0
  275. package/packages/pi-ai/src/providers/simple-options.ts +17 -1
  276. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  277. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +3 -2
  278. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  279. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +2 -0
  280. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  281. package/packages/pi-coding-agent/dist/core/agent-session.js +7 -0
  282. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  283. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +25 -0
  284. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  285. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +105 -6
  286. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  287. package/packages/pi-coding-agent/dist/core/compaction/compaction.test.js +230 -28
  288. package/packages/pi-coding-agent/dist/core/compaction/compaction.test.js.map +1 -1
  289. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts +30 -2
  290. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts.map +1 -1
  291. package/packages/pi-coding-agent/dist/core/compaction/utils.js +113 -12
  292. package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
  293. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts +1 -0
  294. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
  295. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +29 -18
  296. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
  297. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.d.ts +2 -0
  298. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.d.ts.map +1 -0
  299. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.js +130 -0
  300. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.js.map +1 -0
  301. package/packages/pi-coding-agent/dist/core/compaction-utils.test.js +56 -1
  302. package/packages/pi-coding-agent/dist/core/compaction-utils.test.js.map +1 -1
  303. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +8 -15
  304. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -1
  305. package/packages/pi-coding-agent/dist/core/extensions/extension-discovery.d.ts +25 -0
  306. package/packages/pi-coding-agent/dist/core/extensions/extension-discovery.d.ts.map +1 -0
  307. package/packages/pi-coding-agent/dist/core/extensions/extension-discovery.js +109 -0
  308. package/packages/pi-coding-agent/dist/core/extensions/extension-discovery.js.map +1 -0
  309. package/packages/pi-coding-agent/dist/core/extensions/extension-registry.d.ts +67 -0
  310. package/packages/pi-coding-agent/dist/core/extensions/extension-registry.d.ts.map +1 -0
  311. package/packages/pi-coding-agent/dist/core/extensions/extension-registry.js +167 -0
  312. package/packages/pi-coding-agent/dist/core/extensions/extension-registry.js.map +1 -0
  313. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +3 -2
  314. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  315. package/packages/pi-coding-agent/dist/core/extensions/loader.js +24 -8
  316. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  317. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -0
  318. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  319. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  320. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +14 -0
  321. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  322. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  323. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +11 -0
  324. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
  325. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +2 -2
  326. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  327. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts +2 -0
  328. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts.map +1 -0
  329. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js +203 -0
  330. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js.map +1 -0
  331. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  332. package/packages/pi-coding-agent/dist/core/model-registry.js +14 -0
  333. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  334. package/packages/pi-coding-agent/dist/core/resource-loader.js +1 -1
  335. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  336. package/packages/pi-coding-agent/dist/core/sdk.d.ts +1 -0
  337. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  338. package/packages/pi-coding-agent/dist/core/sdk.js +4 -1
  339. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  340. package/packages/pi-coding-agent/dist/core/sdk.test.js +19 -1
  341. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -1
  342. package/packages/pi-coding-agent/dist/core/session-manager.js +1 -1
  343. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  344. package/packages/pi-coding-agent/dist/core/session-manager.test.js +21 -1
  345. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  346. package/packages/pi-coding-agent/dist/core/system-prompt.js +3 -3
  347. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  348. package/packages/pi-coding-agent/dist/core/tools/path-utils.test.js +2 -1
  349. package/packages/pi-coding-agent/dist/core/tools/path-utils.test.js.map +1 -1
  350. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js +15 -6
  351. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js.map +1 -1
  352. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  353. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +14 -5
  354. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  355. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +7 -1
  356. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  357. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +31 -9
  358. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  359. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +14 -0
  360. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  361. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  362. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +13 -1
  363. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  364. package/packages/pi-coding-agent/package.json +6 -1
  365. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +3 -2
  366. package/packages/pi-coding-agent/src/core/agent-session.ts +11 -0
  367. package/packages/pi-coding-agent/src/core/compaction/compaction.test.ts +368 -28
  368. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +122 -6
  369. package/packages/pi-coding-agent/src/core/compaction/utils.ts +111 -13
  370. package/packages/pi-coding-agent/src/core/compaction-orchestrator.test.ts +154 -0
  371. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +32 -18
  372. package/packages/pi-coding-agent/src/core/compaction-utils.test.ts +68 -1
  373. package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +9 -18
  374. package/packages/pi-coding-agent/src/core/extensions/extension-discovery.ts +119 -0
  375. package/packages/pi-coding-agent/src/core/extensions/extension-registry.ts +222 -0
  376. package/packages/pi-coding-agent/src/core/extensions/loader.ts +24 -11
  377. package/packages/pi-coding-agent/src/core/extensions/runner.ts +2 -0
  378. package/packages/pi-coding-agent/src/core/extensions/types.ts +15 -0
  379. package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +13 -0
  380. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +2 -2
  381. package/packages/pi-coding-agent/src/core/model-registry-custom-caps.test.ts +245 -0
  382. package/packages/pi-coding-agent/src/core/model-registry.ts +16 -0
  383. package/packages/pi-coding-agent/src/core/resource-loader.ts +1 -1
  384. package/packages/pi-coding-agent/src/core/sdk.test.ts +25 -1
  385. package/packages/pi-coding-agent/src/core/sdk.ts +10 -3
  386. package/packages/pi-coding-agent/src/core/session-manager.test.ts +30 -1
  387. package/packages/pi-coding-agent/src/core/session-manager.ts +1 -1
  388. package/packages/pi-coding-agent/src/core/system-prompt.ts +3 -3
  389. package/packages/pi-coding-agent/src/core/tools/path-utils.test.ts +2 -1
  390. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +17 -7
  391. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +14 -5
  392. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +45 -11
  393. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +14 -0
  394. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +13 -1
  395. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  396. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +12 -5
  397. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  398. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +21 -0
  399. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
  400. package/packages/pi-tui/dist/stdin-buffer.d.ts +7 -0
  401. package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
  402. package/packages/pi-tui/dist/stdin-buffer.js +20 -0
  403. package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
  404. package/packages/pi-tui/package.json +6 -1
  405. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +20 -5
  406. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +27 -0
  407. package/packages/pi-tui/src/stdin-buffer.ts +26 -0
  408. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  409. package/packages/rpc-client/package.json +6 -1
  410. package/pkg/package.json +1 -1
  411. package/scripts/install.js +512 -0
  412. package/scripts/lib/workspace-manifest.cjs +86 -0
  413. package/scripts/link-workspace-packages.cjs +5 -17
  414. package/scripts/postinstall.js +9 -178
  415. package/src/resources/extensions/claude-code-cli/readiness.ts +4 -3
  416. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +91 -63
  417. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +114 -12
  418. package/src/resources/extensions/cmux/index.ts +35 -10
  419. package/src/resources/extensions/github-sync/templates.ts +151 -0
  420. package/src/resources/extensions/github-sync/tests/templates.test.ts +59 -0
  421. package/src/resources/extensions/google-search/extension-manifest.json +5 -4
  422. package/src/resources/extensions/google-search/index.ts +9 -470
  423. package/src/resources/extensions/gsd/abandon-detect.ts +62 -0
  424. package/src/resources/extensions/gsd/auto/loop-deps.ts +14 -1
  425. package/src/resources/extensions/gsd/auto/loop.ts +104 -2
  426. package/src/resources/extensions/gsd/auto/phases.ts +123 -21
  427. package/src/resources/extensions/gsd/auto/resolve.ts +29 -0
  428. package/src/resources/extensions/gsd/auto/run-unit.ts +56 -4
  429. package/src/resources/extensions/gsd/auto/session.ts +28 -1
  430. package/src/resources/extensions/gsd/auto/turn-epoch.ts +108 -0
  431. package/src/resources/extensions/gsd/auto/types.ts +1 -1
  432. package/src/resources/extensions/gsd/auto-dispatch.ts +117 -16
  433. package/src/resources/extensions/gsd/auto-loop.ts +1 -1
  434. package/src/resources/extensions/gsd/auto-model-selection.ts +1 -1
  435. package/src/resources/extensions/gsd/auto-post-unit.ts +92 -3
  436. package/src/resources/extensions/gsd/auto-prompts.ts +28 -1
  437. package/src/resources/extensions/gsd/auto-recovery.ts +40 -1
  438. package/src/resources/extensions/gsd/auto-start.ts +48 -52
  439. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -5
  440. package/src/resources/extensions/gsd/auto-unit-closeout.ts +14 -3
  441. package/src/resources/extensions/gsd/auto-worktree.ts +122 -68
  442. package/src/resources/extensions/gsd/auto.ts +105 -35
  443. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -1
  444. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +6 -2
  445. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +11 -0
  446. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +18 -6
  447. package/src/resources/extensions/gsd/bootstrap/system-context.ts +13 -9
  448. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +35 -2
  449. package/src/resources/extensions/gsd/clean-root-preflight.ts +111 -0
  450. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +27 -8
  451. package/src/resources/extensions/gsd/commands-cmux.ts +10 -6
  452. package/src/resources/extensions/gsd/commands-extensions.ts +747 -41
  453. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +898 -32
  454. package/src/resources/extensions/gsd/dispatch-guard.ts +26 -2
  455. package/src/resources/extensions/gsd/file-lock.ts +84 -11
  456. package/src/resources/extensions/gsd/git-service.ts +1 -0
  457. package/src/resources/extensions/gsd/gitignore.ts +2 -1
  458. package/src/resources/extensions/gsd/gsd-db.ts +92 -32
  459. package/src/resources/extensions/gsd/guided-flow-queue.ts +4 -1
  460. package/src/resources/extensions/gsd/guided-flow.ts +259 -10
  461. package/src/resources/extensions/gsd/health-widget.ts +3 -1
  462. package/src/resources/extensions/gsd/journal.ts +29 -3
  463. package/src/resources/extensions/gsd/key-manager.ts +22 -0
  464. package/src/resources/extensions/gsd/milestone-actions.ts +18 -0
  465. package/src/resources/extensions/gsd/milestone-summary-classifier.ts +42 -0
  466. package/src/resources/extensions/gsd/model-router.ts +42 -1
  467. package/src/resources/extensions/gsd/notifications.ts +27 -15
  468. package/src/resources/extensions/gsd/pre-execution-checks.ts +33 -7
  469. package/src/resources/extensions/gsd/preferences-types.ts +8 -0
  470. package/src/resources/extensions/gsd/prompts/discuss-headless.md +29 -2
  471. package/src/resources/extensions/gsd/prompts/discuss.md +29 -2
  472. package/src/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
  473. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  474. package/src/resources/extensions/gsd/prompts/system.md +1 -0
  475. package/src/resources/extensions/gsd/reports.ts +5 -4
  476. package/src/resources/extensions/gsd/safety/evidence-collector.ts +119 -0
  477. package/src/resources/extensions/gsd/safety/file-change-validator.ts +16 -3
  478. package/src/resources/extensions/gsd/safety/safety-harness.ts +9 -0
  479. package/src/resources/extensions/gsd/state-transition-matrix.ts +152 -0
  480. package/src/resources/extensions/gsd/state.ts +35 -30
  481. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +238 -4
  482. package/src/resources/extensions/gsd/tests/auto-mode-guards.test.ts +79 -0
  483. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +12 -0
  484. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +122 -0
  485. package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +141 -0
  486. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +63 -0
  487. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +23 -0
  488. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +186 -0
  489. package/src/resources/extensions/gsd/tests/cmux.test.ts +5 -9
  490. package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +15 -0
  491. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +61 -1
  492. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  493. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  494. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +161 -0
  495. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +1 -0
  496. package/src/resources/extensions/gsd/tests/derive-state.test.ts +1 -2
  497. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +14 -9
  498. package/src/resources/extensions/gsd/tests/dispatch-guard-summary-db-mismatch.test.ts +77 -0
  499. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +25 -0
  500. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +14 -0
  501. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +31 -0
  502. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +1 -1
  503. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +1 -1
  504. package/src/resources/extensions/gsd/tests/escalation.test.ts +1 -1
  505. package/src/resources/extensions/gsd/tests/exec-history.test.ts +113 -0
  506. package/src/resources/extensions/gsd/tests/execution-entry-missing-context-4671.test.ts +173 -0
  507. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +38 -0
  508. package/src/resources/extensions/gsd/tests/file-lock.test.ts +86 -12
  509. package/src/resources/extensions/gsd/tests/google-search-stub.test.ts +131 -0
  510. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +296 -1
  511. package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +117 -0
  512. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +30 -0
  513. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +4 -2
  514. package/src/resources/extensions/gsd/tests/integration/gitignore-tracked-gsd.test.ts +1 -0
  515. package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +30 -0
  516. package/src/resources/extensions/gsd/tests/integration/worktree-e2e.test.ts +11 -0
  517. package/src/resources/extensions/gsd/tests/issue-4540-regressions.test.ts +288 -0
  518. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +66 -0
  519. package/src/resources/extensions/gsd/tests/key-manager.test.ts +2 -0
  520. package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +76 -0
  521. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  522. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +12 -0
  523. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  524. package/src/resources/extensions/gsd/tests/milestone-status-authoritative.test.ts +3 -3
  525. package/src/resources/extensions/gsd/tests/milestone-summary-classifier.test.ts +30 -0
  526. package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +4 -2
  527. package/src/resources/extensions/gsd/tests/parallel-commit-scope.test.ts +5 -0
  528. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +19 -0
  529. package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +150 -0
  530. package/src/resources/extensions/gsd/tests/plan-gate-failed-doctor-heal-hint.test.ts +37 -0
  531. package/src/resources/extensions/gsd/tests/pre-exec-gate-loop.test.ts +272 -0
  532. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +337 -0
  533. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +44 -0
  534. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +39 -25
  535. package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +181 -0
  536. package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +13 -7
  537. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +388 -0
  538. package/src/resources/extensions/gsd/tests/require-slice-discussion-dispatch.test.ts +170 -0
  539. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +9 -3
  540. package/src/resources/extensions/gsd/tests/resume-dispatch-worktree.test.ts +230 -0
  541. package/src/resources/extensions/gsd/tests/rewrite-docs-abandon-detect.test.ts +195 -0
  542. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +205 -0
  543. package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +413 -0
  544. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +32 -40
  545. package/src/resources/extensions/gsd/tests/stale-dirlistcache-4648.test.ts +112 -0
  546. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +56 -0
  547. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +24 -0
  548. package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +44 -0
  549. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +2 -2
  550. package/src/resources/extensions/gsd/tests/token-counter.test.ts +105 -1
  551. package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +107 -0
  552. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +50 -2
  553. package/src/resources/extensions/gsd/tests/turn-epoch.test.ts +162 -0
  554. package/src/resources/extensions/gsd/tests/uok-contracts.test.ts +51 -0
  555. package/src/resources/extensions/gsd/tests/uok-execution-graph.test.ts +16 -0
  556. package/src/resources/extensions/gsd/tests/uok-loop-adapter-writer.test.ts +65 -0
  557. package/src/resources/extensions/gsd/tests/uok-parity-report.test.ts +42 -0
  558. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +42 -2
  559. package/src/resources/extensions/gsd/tests/uok-writer.test.ts +75 -0
  560. package/src/resources/extensions/gsd/tests/validate-extension-package.test.ts +168 -0
  561. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +138 -5
  562. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +25 -2
  563. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +65 -2
  564. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +35 -0
  565. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +6 -1
  566. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +78 -5
  567. package/src/resources/extensions/gsd/tests/write-gate.test.ts +64 -0
  568. package/src/resources/extensions/gsd/token-counter.ts +22 -5
  569. package/src/resources/extensions/gsd/tools/complete-milestone.ts +15 -9
  570. package/src/resources/extensions/gsd/tools/complete-slice.ts +38 -0
  571. package/src/resources/extensions/gsd/tools/complete-task.ts +49 -0
  572. package/src/resources/extensions/gsd/uok/audit.ts +20 -2
  573. package/src/resources/extensions/gsd/uok/contracts.ts +65 -0
  574. package/src/resources/extensions/gsd/uok/dispatch-envelope.ts +56 -0
  575. package/src/resources/extensions/gsd/uok/execution-graph.ts +22 -0
  576. package/src/resources/extensions/gsd/uok/gitops.ts +6 -1
  577. package/src/resources/extensions/gsd/uok/loop-adapter.ts +45 -10
  578. package/src/resources/extensions/gsd/uok/parity-report.ts +84 -0
  579. package/src/resources/extensions/gsd/uok/plan-v2.ts +39 -8
  580. package/src/resources/extensions/gsd/uok/writer.ts +113 -0
  581. package/src/resources/extensions/gsd/workflow-logger.ts +23 -3
  582. package/src/resources/extensions/gsd/worktree-manager.ts +1 -0
  583. package/src/resources/extensions/gsd/worktree-resolver.ts +54 -9
  584. package/src/resources/extensions/mcp-client/auth.ts +12 -1
  585. package/src/resources/extensions/mcp-client/index.ts +129 -10
  586. package/src/resources/extensions/shared/cmux-events.ts +59 -0
  587. package/src/resources/extensions/shared/rtk-session-stats.ts +1 -2
  588. package/src/resources/skills/create-skill/SKILL.md +2 -2
  589. package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +4 -4
  590. package/src/resources/skills/create-skill/workflows/audit-skill.md +4 -4
  591. package/src/resources/skills/create-skill/workflows/create-new-skill.md +5 -5
  592. package/src/resources/skills/verify-before-complete/SKILL.md +2 -1
  593. package/src/resources/skills/write-docs/SKILL.md +2 -1
  594. package/dist/web/standalone/.next/server/chunks/7461.js +0 -1
  595. package/dist/web/standalone/.next/static/chunks/2826.e59e8578e2e28639.js +0 -9
  596. /package/dist/web/standalone/.next/static/{n21VtX2hZlkpdEUO_nU4z → vidAVJkURvTJ0_V2-64ro}/_buildManifest.js +0 -0
  597. /package/dist/web/standalone/.next/static/{n21VtX2hZlkpdEUO_nU4z → vidAVJkURvTJ0_V2-64ro}/_ssgManifest.js +0 -0
@@ -378,3 +378,59 @@ test("#2505: back-to-back merges preserve queued CONTEXT files", () => {
378
378
  rmSync(repo, { recursive: true, force: true });
379
379
  }
380
380
  });
381
+
382
+ // #4573: When `.gsd` is a gitignored symlink (ADR-002 layout) and the project
383
+ // `.gitignore` contains `.gsd`, `git stash push --include-untracked -- <pathspec>`
384
+ // fatals with "The following paths are ignored by one of your .gitignore files".
385
+ // The prior tests used a symlinked `.gsd` but no `.gitignore`, so this failure
386
+ // mode was invisible to CI. Fixture must include BOTH the symlink AND the
387
+ // ignore rule to reproduce the bug on pre-fix code.
388
+ test("#4573: gitignored .gsd symlink does not break pre-merge stash", () => {
389
+ const repo = realpathSync(mkdtempSync(join(tmpdir(), "wt-4573-ignored-symlink-")));
390
+ const stateDir = realpathSync(mkdtempSync(join(tmpdir(), "wt-4573-state-")));
391
+ try {
392
+ run("git init", repo);
393
+ run("git config user.email test@test.com", repo);
394
+ run("git config user.name Test", repo);
395
+ writeFileSync(join(repo, "README.md"), "# test\n");
396
+ // Matches what BASELINE_PATTERNS in gitignore.ts writes for real projects.
397
+ writeFileSync(join(repo, ".gitignore"), ".gsd\n.gsd-id\n");
398
+ symlinkSync(stateDir, join(repo, ".gsd"));
399
+ run("git add README.md .gitignore", repo);
400
+ run("git commit -m init", repo);
401
+ run("git branch -M main", repo);
402
+
403
+ const wtPath = createAutoWorktree(repo, "M001");
404
+ const worktreeName = wtPath.replaceAll("\\", "/").split("/").pop() || "M001";
405
+ const sliceBranch = `slice/${worktreeName}/S01`;
406
+ run(`git checkout -b "${sliceBranch}"`, wtPath);
407
+ writeFileSync(join(wtPath, "app.ts"), "export const app = true;\n");
408
+ run("git add app.ts", wtPath);
409
+ run('git commit -m "add feature"', wtPath);
410
+ run("git checkout milestone/M001", wtPath);
411
+ run(`git merge --no-ff "${sliceBranch}" -m "merge S01"`, wtPath);
412
+
413
+ // Dirty a tracked file so the pre-merge stash branch actually runs.
414
+ writeFileSync(join(repo, "README.md"), "# test\n\nDirty.\n");
415
+
416
+ const result = mergeMilestoneToMain(
417
+ repo,
418
+ "M001",
419
+ makeRoadmap("M001", "Feature", [{ id: "S01", title: "Feature" }]),
420
+ );
421
+
422
+ assert.ok(
423
+ result.commitMessage.includes("GSD-Milestone: M001"),
424
+ "merge must succeed despite gitignored .gsd symlink",
425
+ );
426
+ assert.ok(existsSync(join(repo, "app.ts")), "milestone code merged to main");
427
+ assert.equal(
428
+ lstatSync(join(repo, ".gsd")).isSymbolicLink(),
429
+ true,
430
+ ".gsd symlink remains in place",
431
+ );
432
+ } finally {
433
+ rmSync(repo, { recursive: true, force: true });
434
+ rmSync(stateDir, { recursive: true, force: true });
435
+ }
436
+ });
@@ -13,6 +13,7 @@ import {
13
13
  isValidationTerminal,
14
14
  isGhostMilestone,
15
15
  invalidateStateCache,
16
+ getActiveMilestoneId,
16
17
  } from "../state.ts";
17
18
  import {
18
19
  openDatabase,
@@ -667,6 +668,29 @@ describe("state-machine-full-walkthrough", () => {
667
668
  assert.notEqual(state.phase, "completing-milestone", "should be complete, not completing");
668
669
  assert.equal(state.phase, "complete");
669
670
  });
671
+
672
+ test("failure-path milestone SUMMARY is not terminal completion", async () => {
673
+ const base = createFixtureBase();
674
+ writeRoadmap(base, "M001", doneSliceRoadmap());
675
+ writeMilestoneValidation(base, "M001", "pass");
676
+ const dir = join(base, ".gsd", "milestones", "M001");
677
+ writeFileSync(join(dir, "M001-SUMMARY.md"), [
678
+ "---",
679
+ "status: failed",
680
+ "---",
681
+ "",
682
+ "# BLOCKER",
683
+ "",
684
+ "auto-mode recovery failed; milestone is not complete.",
685
+ ].join("\n"));
686
+ invalidateStateCache();
687
+
688
+ const state = await deriveState(base);
689
+
690
+ assert.equal(state.phase, "completing-milestone");
691
+ assert.equal(state.registry[0]?.status, "active");
692
+ assert.equal(await getActiveMilestoneId(base), "M001");
693
+ });
670
694
  });
671
695
 
672
696
  // ═══════════════════════════════════════════════════════════════════════════
@@ -0,0 +1,44 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import {
5
+ STATE_TRANSITION_MATRIX,
6
+ findTransition,
7
+ validateTransitionMatrix,
8
+ } from "../state-transition-matrix.ts";
9
+
10
+ test("state transition matrix covers required swarm hardening events", () => {
11
+ const result = validateTransitionMatrix([
12
+ "context-ready",
13
+ "research-ready",
14
+ "plan-ready",
15
+ "task-dispatched",
16
+ "slice-complete",
17
+ "validation-pass",
18
+ "recovery-plan-ready",
19
+ "closeout-complete",
20
+ ]);
21
+
22
+ assert.equal(result.ok, true);
23
+ assert.deepEqual(result.missingEvents, []);
24
+ assert.deepEqual(result.duplicateKeys, []);
25
+ });
26
+
27
+ test("state transition matrix fails closed for recovery and closeout guards", () => {
28
+ const recovery = findTransition("blocked", "recovery-plan-ready");
29
+ assert.equal(recovery?.to, "executing");
30
+ assert.equal(recovery?.onFail, "blocked");
31
+ assert.equal(recovery?.reasonCode, "recovery");
32
+
33
+ const closeout = findTransition("completing-milestone", "closeout-complete");
34
+ assert.equal(closeout?.to, "complete");
35
+ assert.equal(closeout?.onFail, "blocked");
36
+ });
37
+
38
+ test("state transition matrix entries all have guard and reason codes", () => {
39
+ assert.ok(STATE_TRANSITION_MATRIX.length >= 8);
40
+ for (const entry of STATE_TRANSITION_MATRIX) {
41
+ assert.ok(entry.guard.length > 0, `${entry.event} must document its guard`);
42
+ assert.ok(entry.reasonCode.length > 0, `${entry.event} must include reason code`);
43
+ }
44
+ });
@@ -142,9 +142,9 @@ test("Rule 3: A-A-A-A triggers Rule 2 not Rule 3", () => {
142
142
 
143
143
  test("Rule 4: same ENOENT path in two entries triggers stuck", () => {
144
144
  const result = detectStuck([
145
- { key: "A", error: "ENOENT: no such file or directory, access '/home/user/.gsd/agent/skills/debug-like-expert/SKILL.md'" },
145
+ { key: "A", error: "ENOENT: no such file or directory, access '/home/user/.agents/skills/debug-like-expert/SKILL.md'" },
146
146
  { key: "B" },
147
- { key: "A", error: "ENOENT: no such file or directory, access '/home/user/.gsd/agent/skills/debug-like-expert/SKILL.md'" },
147
+ { key: "A", error: "ENOENT: no such file or directory, access '/home/user/.agents/skills/debug-like-expert/SKILL.md'" },
148
148
  ]);
149
149
  assert.notEqual(result, null);
150
150
  assert.equal(result!.stuck, true);
@@ -102,7 +102,8 @@ describe("token-counter: estimateTokensForProvider", () => {
102
102
 
103
103
  describe("token-counter: backward compatibility", () => {
104
104
  it("countTokensSync returns heuristic estimate when tiktoken is not loaded", () => {
105
- // Without tiktoken loaded, countTokensSync falls back to ceil(len/4)
105
+ // Without tiktoken loaded, countTokensSync falls back to estimateTokensForProvider.
106
+ // With no provider (defaults to "unknown", ratio 4.0): ceil(100/4) = 25.
106
107
  const text = "A".repeat(100);
107
108
  const result = countTokensSync(text);
108
109
  // Either tiktoken is loaded (exact count) or heuristic (ceil(100/4) = 25)
@@ -127,3 +128,106 @@ describe("token-counter: backward compatibility", () => {
127
128
  assert.equal(result, 0);
128
129
  });
129
130
  });
131
+
132
+ // ─── provider-aware fallback (issue #4529) ───────────────────────────────────
133
+ // Regression tests: countTokens/countTokensSync must use provider-specific
134
+ // ratios for their heuristic fallback, not a hardcoded GPT-4o / 4 divisor.
135
+
136
+ describe("token-counter: provider-aware heuristic fallback", () => {
137
+ // These tests exercise the heuristic path (no tiktoken or before init).
138
+ // We call estimateTokensForProvider directly to validate expected values,
139
+ // then verify countTokens/countTokensSync return the same values when
140
+ // tiktoken is unavailable.
141
+
142
+ it("countTokensSync uses anthropic ratio (3.5) when provider is 'anthropic'", () => {
143
+ const text = "A".repeat(350);
144
+ // anthropic: ceil(350 / 3.5) = 100
145
+ // openai/unknown: ceil(350 / 4.0) = 88
146
+ // These are different — the provider must matter.
147
+ const anthropicEstimate = estimateTokensForProvider(text, "anthropic");
148
+ const unknownEstimate = estimateTokensForProvider(text, "unknown");
149
+ assert.equal(anthropicEstimate, 100, "anthropic ratio should give 100 tokens for 350 chars");
150
+ assert.equal(unknownEstimate, 88, "unknown ratio should give 88 tokens for 350 chars");
151
+ assert.notEqual(
152
+ anthropicEstimate,
153
+ unknownEstimate,
154
+ "anthropic and unknown estimates must differ — if they are equal the provider is being ignored",
155
+ );
156
+
157
+ // Actually call countTokensSync with the anthropic provider.
158
+ // When tiktoken is not loaded, this must return the provider-aware estimate (100).
159
+ // When tiktoken is loaded, it returns the tiktoken count (which is also > 0 and
160
+ // will be in the range [88, 120] for 350 "A" characters with cl100k_base).
161
+ const syncResult = countTokensSync(text, "anthropic");
162
+ assert.ok(typeof syncResult === "number", "countTokensSync must return a number");
163
+ assert.ok(syncResult > 0, "countTokensSync must return a positive count");
164
+ // If tiktoken is unavailable the result must exactly match the anthropic heuristic.
165
+ // If tiktoken is available we cannot assert the exact value, but we know it will
166
+ // not equal the unknown-provider heuristic (88) for 350 identical characters.
167
+ const tiktokenAvailable = syncResult !== anthropicEstimate;
168
+ if (!tiktokenAvailable) {
169
+ assert.equal(
170
+ syncResult,
171
+ anthropicEstimate,
172
+ `countTokensSync with 'anthropic' provider must return ${anthropicEstimate} (not the unknown-provider value ${unknownEstimate}) when tiktoken is unavailable`,
173
+ );
174
+ }
175
+ });
176
+
177
+ it("countTokens uses anthropic ratio when provider='anthropic' and tiktoken unavailable", async () => {
178
+ const text = "A".repeat(350);
179
+ // anthropic heuristic: ceil(350 / 3.5) = 100
180
+ // unknown/hardcoded-4 heuristic: ceil(350 / 4.0) = 88
181
+ const anthropicEstimate = estimateTokensForProvider(text, "anthropic"); // 100
182
+ const unknownEstimate = estimateTokensForProvider(text, "unknown"); // 88
183
+ const hardcodedFallback = Math.ceil(text.length / 4); // 88
184
+
185
+ // The anthropic heuristic must produce more tokens than the old /4 fallback.
186
+ assert.equal(anthropicEstimate, 100, "anthropic heuristic should give 100 tokens for 350 chars");
187
+ assert.ok(
188
+ anthropicEstimate > hardcodedFallback,
189
+ `anthropic estimate (${anthropicEstimate}) must exceed the hardcoded /4 fallback (${hardcodedFallback})`,
190
+ );
191
+ assert.notEqual(
192
+ anthropicEstimate,
193
+ unknownEstimate,
194
+ "anthropic and unknown estimates must differ — provider is being ignored if equal",
195
+ );
196
+
197
+ // Call countTokens with the anthropic provider and verify the result.
198
+ const result = await countTokens(text, "anthropic");
199
+ assert.ok(typeof result === "number", "should return a number");
200
+ assert.ok(result > 0, "should return a positive token count");
201
+
202
+ // When tiktoken is unavailable: result must equal the anthropic heuristic (100),
203
+ // NOT the unknown-provider heuristic (88). This is the core regression guard for #4529.
204
+ // When tiktoken IS available: result is the tiktoken count, which will differ from
205
+ // the heuristic — but it must never equal the wrong (unknown) heuristic for this text.
206
+ if (result === anthropicEstimate || result === unknownEstimate) {
207
+ // We are on the heuristic path — assert the correct provider ratio was used.
208
+ assert.equal(
209
+ result,
210
+ anthropicEstimate,
211
+ `countTokens with 'anthropic' provider returned ${result} but expected ${anthropicEstimate} (anthropic heuristic) not ${unknownEstimate} (unknown/hardcoded heuristic)`,
212
+ );
213
+ } else {
214
+ // tiktoken is active — result is an exact BPE count.
215
+ // For 350 identical "A" characters cl100k_base produces a count in [80, 130].
216
+ assert.ok(
217
+ result >= 80 && result <= 130,
218
+ `tiktoken count ${result} for 350 "A" chars is outside expected range [80, 130]`,
219
+ );
220
+ }
221
+ });
222
+
223
+ it("countTokens with provider='anthropic' yields more tokens than provider='openai' (heuristic)", () => {
224
+ const text = "A".repeat(400);
225
+ // anthropic: ceil(400/3.5) = 115, openai: ceil(400/4.0) = 100
226
+ const anthropic = estimateTokensForProvider(text, "anthropic");
227
+ const openai = estimateTokensForProvider(text, "openai");
228
+ assert.ok(
229
+ anthropic > openai,
230
+ `anthropic estimate (${anthropic}) must exceed openai estimate (${openai}) for same text`,
231
+ );
232
+ });
233
+ });
@@ -14,6 +14,7 @@ import {
14
14
  isToolCompatibleWithProvider,
15
15
  filterToolsForProvider,
16
16
  adjustToolSet,
17
+ GROQ_MAX_TOOLS,
17
18
  } from "../model-router.js";
18
19
 
19
20
  import {
@@ -197,3 +198,109 @@ describe("adjustToolSet", () => {
197
198
  assert.deepEqual(removedTools, ["mcp_complex"]);
198
199
  });
199
200
  });
201
+
202
+ // ─── GROQ_MAX_TOOLS constant ─────────────────────────────────────────────────
203
+
204
+ describe("GROQ_MAX_TOOLS", () => {
205
+ test("equals 128", () => {
206
+ assert.equal(GROQ_MAX_TOOLS, 128);
207
+ });
208
+ });
209
+
210
+ // ─── Groq tool-count cap (#4376) ────────────────────────────────────────────
211
+
212
+ describe("filterToolsForProvider — Groq 128-tool cap", () => {
213
+ beforeEach(() => {
214
+ resetToolCompatibilityRegistry();
215
+ });
216
+
217
+ test("does not cap when provider is not groq", () => {
218
+ const toolNames = Array.from({ length: 200 }, (_, i) => `tool_${i}`);
219
+ const { compatible, filtered } = filterToolsForProvider(toolNames, "openai-completions");
220
+ assert.equal(compatible.length, 200);
221
+ assert.equal(filtered.length, 0);
222
+ });
223
+
224
+ test("does not cap when <= 128 tools with groq provider", () => {
225
+ const toolNames = Array.from({ length: 128 }, (_, i) => `tool_${i}`);
226
+ const { compatible, filtered } = filterToolsForProvider(toolNames, "openai-completions", "groq");
227
+ assert.equal(compatible.length, 128);
228
+ assert.equal(filtered.length, 0);
229
+ });
230
+
231
+ test("caps to 128 when >128 tools with groq provider", () => {
232
+ const toolNames = Array.from({ length: 200 }, (_, i) => `tool_${i}`);
233
+ const { compatible, filtered } = filterToolsForProvider(toolNames, "openai-completions", "groq");
234
+ assert.equal(compatible.length, 128);
235
+ assert.equal(filtered.length, 72);
236
+ });
237
+
238
+ test("keeps the first 128 tools when capping", () => {
239
+ const toolNames = Array.from({ length: 200 }, (_, i) => `tool_${i}`);
240
+ const { compatible } = filterToolsForProvider(toolNames, "openai-completions", "groq");
241
+ assert.equal(compatible[0], "tool_0");
242
+ assert.equal(compatible[127], "tool_127");
243
+ });
244
+
245
+ test("trimmed tools appear in filtered list", () => {
246
+ const toolNames = Array.from({ length: 130 }, (_, i) => `tool_${i}`);
247
+ const { filtered } = filterToolsForProvider(toolNames, "openai-completions", "groq");
248
+ assert.equal(filtered.length, 2);
249
+ assert.equal(filtered[0], "tool_128");
250
+ assert.equal(filtered[1], "tool_129");
251
+ });
252
+
253
+ test("emits a warning when tools are trimmed", () => {
254
+ const warnings: string[] = [];
255
+ const original = console.warn;
256
+ console.warn = (...args: unknown[]) => { warnings.push(String(args[0])); };
257
+ try {
258
+ const toolNames = Array.from({ length: 129 }, (_, i) => `tool_${i}`);
259
+ filterToolsForProvider(toolNames, "openai-completions", "groq");
260
+ assert.equal(warnings.length, 1);
261
+ assert.ok(warnings[0].includes("128"), "warning mentions Groq limit");
262
+ } finally {
263
+ console.warn = original;
264
+ }
265
+ });
266
+
267
+ test("does not warn when tools are at the limit", () => {
268
+ const warnings: string[] = [];
269
+ const original = console.warn;
270
+ console.warn = (...args: unknown[]) => { warnings.push(String(args[0])); };
271
+ try {
272
+ const toolNames = Array.from({ length: 128 }, (_, i) => `tool_${i}`);
273
+ filterToolsForProvider(toolNames, "openai-completions", "groq");
274
+ assert.equal(warnings.length, 0);
275
+ } finally {
276
+ console.warn = original;
277
+ }
278
+ });
279
+ });
280
+
281
+ describe("adjustToolSet — Groq 128-tool cap", () => {
282
+ beforeEach(() => {
283
+ resetToolCompatibilityRegistry();
284
+ });
285
+
286
+ test("caps to 128 tools when provider is groq and >128 tools active", () => {
287
+ const toolNames = Array.from({ length: 150 }, (_, i) => `tool_${i}`);
288
+ const { toolNames: result, removedTools } = adjustToolSet(toolNames, "openai-completions", "groq");
289
+ assert.equal(result.length, 128);
290
+ assert.equal(removedTools.length, 22);
291
+ });
292
+
293
+ test("does not cap for non-groq providers even with >128 tools", () => {
294
+ const toolNames = Array.from({ length: 150 }, (_, i) => `tool_${i}`);
295
+ const { toolNames: result, removedTools } = adjustToolSet(toolNames, "openai-completions", "openai");
296
+ assert.equal(result.length, 150);
297
+ assert.equal(removedTools.length, 0);
298
+ });
299
+
300
+ test("does not cap when provider is omitted", () => {
301
+ const toolNames = Array.from({ length: 150 }, (_, i) => `tool_${i}`);
302
+ const { toolNames: result, removedTools } = adjustToolSet(toolNames, "openai-completions");
303
+ assert.equal(result.length, 150);
304
+ assert.equal(removedTools.length, 0);
305
+ });
306
+ });
@@ -258,6 +258,55 @@ test("resolution: markCaptureExecuted is idempotent", () => {
258
258
  }
259
259
  });
260
260
 
261
+ // ─── executeTriageResolutions + note execution (#3578) ──────────────────────
262
+
263
+ test("resolution: executeTriageResolutions stamps note captures as executed", () => {
264
+ const tmp = makeTempDir("res-exec-note");
265
+ try {
266
+ const id = appendCapture(tmp, "FYI the API changed");
267
+ markCaptureResolved(tmp, id, "note", "acknowledged", "informational");
268
+
269
+ const result = executeTriageResolutions(tmp, "M001", "S01");
270
+
271
+ // The note should now be marked as executed
272
+ const all = loadAllCaptures(tmp);
273
+ assert.strictEqual(all.length, 1);
274
+ assert.strictEqual(all[0].executed, true, "note capture should be marked as executed");
275
+
276
+ // It should appear in the actions log
277
+ assert.ok(
278
+ result.actions.some(a => a.includes(id) && a.includes("Note acknowledged")),
279
+ "actions should include a note-acknowledged entry",
280
+ );
281
+ } finally {
282
+ rmSync(tmp, { recursive: true, force: true });
283
+ }
284
+ });
285
+
286
+ test("resolution: executeTriageResolutions does not double-stamp already-executed notes", () => {
287
+ const tmp = makeTempDir("res-exec-note-idem");
288
+ try {
289
+ const id = appendCapture(tmp, "informational note");
290
+ markCaptureResolved(tmp, id, "note", "acknowledged", "info");
291
+
292
+ // First execution — stamps the note
293
+ executeTriageResolutions(tmp, "M001", "S01");
294
+
295
+ // Second execution — should be a no-op for the note
296
+ const result2 = executeTriageResolutions(tmp, "M001", "S01");
297
+
298
+ assert.strictEqual(result2.actions.length, 0, "second call should produce no actions");
299
+
300
+ // Verify the Executed field was not duplicated in the file
301
+ const filePath = join(tmp, ".gsd", "CAPTURES.md");
302
+ const content = readFileSync(filePath, "utf-8");
303
+ const executedMatches = content.match(/\*\*Executed:\*\*/g);
304
+ assert.strictEqual(executedMatches?.length, 1, "should have exactly one Executed field");
305
+ } finally {
306
+ rmSync(tmp, { recursive: true, force: true });
307
+ }
308
+ });
309
+
261
310
  // ─── loadActionableCaptures ──────────────────────────────────────────────────
262
311
 
263
312
  test("resolution: loadActionableCaptures returns only unexecuted actionable captures", () => {
@@ -387,8 +436,7 @@ test("resolution: executeTriageResolutions handles mixed classifications", () =>
387
436
  assert.strictEqual(result.injected, 1, "should inject 1 task");
388
437
  assert.strictEqual(result.replanned, 0);
389
438
  assert.strictEqual(result.quickTasks.length, 1, "should queue 1 quick-task");
390
- // inject + quick-task + note acknowledged = 3 actions (defer still excluded)
391
- assert.strictEqual(result.actions.length, 3, "should have 3 action entries (defer excluded, note now included)");
439
+ assert.strictEqual(result.actions.length, 3, "should have 3 action entries (inject + quick-task + note acknowledged; defer excluded)");
392
440
  } finally {
393
441
  rmSync(tmp, { recursive: true, force: true });
394
442
  }
@@ -0,0 +1,162 @@
1
+ // Project: gsd-pi — Tests for the auto/turn-epoch stale-write guard.
2
+
3
+ import test from "node:test";
4
+ import assert from "node:assert/strict";
5
+
6
+ import {
7
+ _resetTurnEpoch,
8
+ bumpTurnGeneration,
9
+ describeTurnEpoch,
10
+ getCurrentTurnGeneration,
11
+ isStaleWrite,
12
+ runWithTurnGeneration,
13
+ } from "../auto/turn-epoch.ts";
14
+
15
+ test("turn-epoch: generation starts at 0 and bumps monotonically", () => {
16
+ _resetTurnEpoch();
17
+ assert.equal(getCurrentTurnGeneration(), 0);
18
+ assert.equal(bumpTurnGeneration("test-a"), 1);
19
+ assert.equal(bumpTurnGeneration("test-b"), 2);
20
+ assert.equal(getCurrentTurnGeneration(), 2);
21
+ });
22
+
23
+ test("turn-epoch: isStaleWrite returns false when no turn context captured", () => {
24
+ _resetTurnEpoch();
25
+ bumpTurnGeneration("no-context");
26
+ // Called outside runWithTurnGeneration — safe default is false.
27
+ assert.equal(isStaleWrite("out-of-band"), false);
28
+ });
29
+
30
+ test("turn-epoch: isStaleWrite returns false inside a fresh turn", () => {
31
+ _resetTurnEpoch();
32
+ const captured = getCurrentTurnGeneration();
33
+ runWithTurnGeneration(captured, () => {
34
+ assert.equal(isStaleWrite("fresh"), false);
35
+ });
36
+ });
37
+
38
+ test("turn-epoch: isStaleWrite returns true after the epoch bumps mid-turn", () => {
39
+ _resetTurnEpoch();
40
+ const captured = getCurrentTurnGeneration();
41
+ runWithTurnGeneration(captured, () => {
42
+ bumpTurnGeneration("recovery-fires");
43
+ assert.equal(isStaleWrite("stale"), true);
44
+ });
45
+ });
46
+
47
+ test("turn-epoch: nested turns each see their own captured generation", () => {
48
+ _resetTurnEpoch();
49
+ const outerGen = getCurrentTurnGeneration();
50
+ runWithTurnGeneration(outerGen, () => {
51
+ assert.equal(isStaleWrite("outer-fresh"), false);
52
+ bumpTurnGeneration("bump-between");
53
+ const innerGen = getCurrentTurnGeneration();
54
+ runWithTurnGeneration(innerGen, () => {
55
+ // Inner context saw the bumped generation at capture time — fresh.
56
+ assert.equal(isStaleWrite("inner-fresh"), false);
57
+ });
58
+ // Back to outer context — still stale because outerGen < current.
59
+ assert.equal(isStaleWrite("outer-after-bump"), true);
60
+ });
61
+ });
62
+
63
+ test("turn-epoch: describeTurnEpoch surfaces captured vs current", () => {
64
+ _resetTurnEpoch();
65
+ bumpTurnGeneration("seed");
66
+ const captured = getCurrentTurnGeneration();
67
+ runWithTurnGeneration(captured, () => {
68
+ let snapshot = describeTurnEpoch();
69
+ assert.equal(snapshot.captured, captured);
70
+ assert.equal(snapshot.current, captured);
71
+ assert.equal(snapshot.stale, false);
72
+
73
+ bumpTurnGeneration("supersede");
74
+ snapshot = describeTurnEpoch();
75
+ assert.equal(snapshot.captured, captured);
76
+ assert.equal(snapshot.current, captured + 1);
77
+ assert.equal(snapshot.stale, true);
78
+ });
79
+
80
+ // Outside the turn — captured is null, stale is false.
81
+ const outside = describeTurnEpoch();
82
+ assert.equal(outside.captured, null);
83
+ assert.equal(outside.stale, false);
84
+ });
85
+
86
+ test("turn-epoch: AsyncLocalStorage propagates across awaits", async () => {
87
+ _resetTurnEpoch();
88
+ const captured = getCurrentTurnGeneration();
89
+ await runWithTurnGeneration(captured, async () => {
90
+ await Promise.resolve();
91
+ await new Promise((r) => setTimeout(r, 1));
92
+ bumpTurnGeneration("async-bump");
93
+ await Promise.resolve();
94
+ assert.equal(isStaleWrite("post-await"), true);
95
+ });
96
+ });
97
+
98
+ // ─── Source-level invariant checks for recoverTimedOutUnit ──────────────────
99
+ //
100
+ // The recoverTimedOutUnit function has two branch families:
101
+ // - ADVANCE branches: the unit is done, loop moves on — these MUST bump.
102
+ // - STEERING branches: the same LLM turn is kept alive with a steering
103
+ // message — these MUST NOT bump (otherwise the retry's legitimate writes
104
+ // get marked stale).
105
+ //
106
+ // The whole function must contain zero raw `resolveAgentEnd` calls with the
107
+ // "timeout-recovery" _synthetic marker — all advance paths go through
108
+ // bumpAndResolveSynthetic. And there must be no top-level bump call.
109
+
110
+ test("recoverTimedOutUnit: no raw `resolveAgentEnd({ _synthetic: \"timeout-recovery\" })` calls remain", async () => {
111
+ const fs = await import("node:fs");
112
+ const path = await import("node:path");
113
+ const url = await import("node:url");
114
+ const here = path.dirname(url.fileURLToPath(import.meta.url));
115
+ const src = fs.readFileSync(
116
+ path.join(here, "..", "auto-timeout-recovery.ts"),
117
+ "utf-8",
118
+ );
119
+ const rawSyntheticResolve =
120
+ /resolveAgentEnd\s*\(\s*\{\s*messages:\s*\[\s*\]\s*,\s*_synthetic:\s*["']timeout-recovery/;
121
+ assert.equal(
122
+ rawSyntheticResolve.test(src),
123
+ false,
124
+ "auto-timeout-recovery.ts must funnel advance paths through bumpAndResolveSynthetic — a raw resolveAgentEnd with _synthetic:\"timeout-recovery\" would leak orphan writes",
125
+ );
126
+ });
127
+
128
+ test("recoverTimedOutUnit: no top-level bumpTurnGeneration — steering branches must not supersede", async () => {
129
+ const fs = await import("node:fs");
130
+ const path = await import("node:path");
131
+ const url = await import("node:url");
132
+ const here = path.dirname(url.fileURLToPath(import.meta.url));
133
+ const src = fs.readFileSync(
134
+ path.join(here, "..", "auto-timeout-recovery.ts"),
135
+ "utf-8",
136
+ );
137
+ // The only bump surface allowed is via bumpAndResolveSynthetic (advance
138
+ // paths) — a direct bumpTurnGeneration call in this file would bump even
139
+ // when the function later decides to take a steering retry branch.
140
+ assert.equal(
141
+ /\bbumpTurnGeneration\s*\(/.test(src),
142
+ false,
143
+ "auto-timeout-recovery.ts must not call bumpTurnGeneration directly — use bumpAndResolveSynthetic so bump and supersede are atomic and tied to advance-only branches",
144
+ );
145
+ });
146
+
147
+ test("recoverTimedOutUnit: bumpAndResolveSynthetic appears exactly four times (one per advance branch)", async () => {
148
+ const fs = await import("node:fs");
149
+ const path = await import("node:path");
150
+ const url = await import("node:url");
151
+ const here = path.dirname(url.fileURLToPath(import.meta.url));
152
+ const src = fs.readFileSync(
153
+ path.join(here, "..", "auto-timeout-recovery.ts"),
154
+ "utf-8",
155
+ );
156
+ const matches = src.match(/bumpAndResolveSynthetic\s*\(/g) ?? [];
157
+ assert.equal(
158
+ matches.length,
159
+ 4,
160
+ `expected 4 advance-branch supersede sites (durableComplete, execute-task-exhausted, artifact-already-exists, non-execute-exhausted); found ${matches.length}`,
161
+ );
162
+ });