gsd-pi 2.41.0 → 2.42.0-dev.1df898f

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 (494) hide show
  1. package/README.md +92 -29
  2. package/dist/cli-web-branch.d.ts +6 -0
  3. package/dist/cli-web-branch.js +17 -0
  4. package/dist/cli.js +18 -3
  5. package/dist/loader.js +3 -1
  6. package/dist/onboarding.js +2 -1
  7. package/dist/resource-loader.js +39 -6
  8. package/dist/resources/extensions/async-jobs/async-bash-tool.js +52 -4
  9. package/dist/resources/extensions/async-jobs/await-tool.js +5 -0
  10. package/dist/resources/extensions/async-jobs/index.js +2 -0
  11. package/dist/resources/extensions/gsd/auto/loop.js +89 -1
  12. package/dist/resources/extensions/gsd/auto/phases.js +29 -13
  13. package/dist/resources/extensions/gsd/auto/session.js +6 -0
  14. package/dist/resources/extensions/gsd/auto-dashboard.js +8 -2
  15. package/dist/resources/extensions/gsd/auto-dispatch.js +19 -2
  16. package/dist/resources/extensions/gsd/auto-post-unit.js +7 -0
  17. package/dist/resources/extensions/gsd/auto-prompts.js +3 -16
  18. package/dist/resources/extensions/gsd/auto-recovery.js +12 -4
  19. package/dist/resources/extensions/gsd/auto-start.js +16 -14
  20. package/dist/resources/extensions/gsd/auto-worktree.js +147 -13
  21. package/dist/resources/extensions/gsd/auto.js +64 -2
  22. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +199 -164
  23. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +62 -0
  24. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +25 -3
  26. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +7 -2
  27. package/dist/resources/extensions/gsd/commands/catalog.js +40 -1
  28. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
  29. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  30. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +146 -0
  31. package/dist/resources/extensions/gsd/context-injector.js +74 -0
  32. package/dist/resources/extensions/gsd/context-store.js +4 -3
  33. package/dist/resources/extensions/gsd/custom-execution-policy.js +47 -0
  34. package/dist/resources/extensions/gsd/custom-verification.js +145 -0
  35. package/dist/resources/extensions/gsd/custom-workflow-engine.js +164 -0
  36. package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -0
  37. package/dist/resources/extensions/gsd/db-writer.js +5 -2
  38. package/dist/resources/extensions/gsd/definition-loader.js +352 -0
  39. package/dist/resources/extensions/gsd/detection.js +20 -1
  40. package/dist/resources/extensions/gsd/dev-execution-policy.js +24 -0
  41. package/dist/resources/extensions/gsd/dev-workflow-engine.js +82 -0
  42. package/dist/resources/extensions/gsd/doctor-checks.js +31 -1
  43. package/dist/resources/extensions/gsd/doctor-providers.js +10 -0
  44. package/dist/resources/extensions/gsd/doctor.js +11 -1
  45. package/dist/resources/extensions/gsd/engine-resolver.js +40 -0
  46. package/dist/resources/extensions/gsd/engine-types.js +8 -0
  47. package/dist/resources/extensions/gsd/execution-policy.js +8 -0
  48. package/dist/resources/extensions/gsd/exit-command.js +12 -2
  49. package/dist/resources/extensions/gsd/export.js +9 -13
  50. package/dist/resources/extensions/gsd/extension-manifest.json +2 -2
  51. package/dist/resources/extensions/gsd/files.js +28 -11
  52. package/dist/resources/extensions/gsd/forensics.js +94 -3
  53. package/dist/resources/extensions/gsd/git-constants.js +1 -0
  54. package/dist/resources/extensions/gsd/git-service.js +6 -2
  55. package/dist/resources/extensions/gsd/graph.js +225 -0
  56. package/dist/resources/extensions/gsd/gsd-db.js +25 -8
  57. package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
  58. package/dist/resources/extensions/gsd/guided-flow.js +7 -3
  59. package/dist/resources/extensions/gsd/journal.js +85 -0
  60. package/dist/resources/extensions/gsd/md-importer.js +5 -0
  61. package/dist/resources/extensions/gsd/milestone-ids.js +1 -1
  62. package/dist/resources/extensions/gsd/native-git-bridge.js +3 -2
  63. package/dist/resources/extensions/gsd/post-unit-hooks.js +24 -412
  64. package/dist/resources/extensions/gsd/preferences-types.js +2 -0
  65. package/dist/resources/extensions/gsd/preferences.js +60 -8
  66. package/dist/resources/extensions/gsd/prompt-loader.js +34 -4
  67. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +11 -10
  68. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
  69. package/dist/resources/extensions/gsd/prompts/discuss.md +1 -1
  70. package/dist/resources/extensions/gsd/prompts/forensics.md +12 -5
  71. package/dist/resources/extensions/gsd/prompts/queue.md +1 -1
  72. package/dist/resources/extensions/gsd/repo-identity.js +92 -7
  73. package/dist/resources/extensions/gsd/rule-registry.js +489 -0
  74. package/dist/resources/extensions/gsd/rule-types.js +6 -0
  75. package/dist/resources/extensions/gsd/run-manager.js +134 -0
  76. package/dist/resources/extensions/gsd/service-tier.js +147 -0
  77. package/dist/resources/extensions/gsd/session-lock.js +2 -2
  78. package/dist/resources/extensions/gsd/structured-data-formatter.js +2 -1
  79. package/dist/resources/extensions/gsd/templates/decisions.md +2 -2
  80. package/dist/resources/extensions/gsd/workflow-engine.js +7 -0
  81. package/dist/resources/extensions/gsd/workflow-templates.js +13 -1
  82. package/dist/resources/extensions/gsd/worktree-manager.js +20 -6
  83. package/dist/resources/extensions/gsd/worktree-resolver.js +21 -4
  84. package/dist/resources/extensions/gsd/worktree.js +2 -2
  85. package/dist/resources/extensions/mcp-client/index.js +2 -1
  86. package/dist/resources/extensions/search-the-web/tool-search.js +3 -3
  87. package/dist/resources/extensions/subagent/index.js +7 -3
  88. package/dist/resources/extensions/voice/index.js +4 -4
  89. package/dist/resources/skills/create-workflow/SKILL.md +103 -0
  90. package/dist/resources/skills/create-workflow/references/feature-patterns.md +128 -0
  91. package/dist/resources/skills/create-workflow/references/verification-policies.md +76 -0
  92. package/dist/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
  93. package/dist/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
  94. package/dist/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
  95. package/dist/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
  96. package/dist/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
  97. package/dist/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
  98. package/dist/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
  99. package/dist/web/standalone/.next/BUILD_ID +1 -1
  100. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  101. package/dist/web/standalone/.next/build-manifest.json +4 -4
  102. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  103. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  104. package/dist/web/standalone/.next/required-server-files.json +3 -3
  105. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  106. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  108. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  115. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  116. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +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 +3 -3
  119. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  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 +3 -3
  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_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  132. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
  170. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  176. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  185. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  187. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  189. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  190. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  191. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  192. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  193. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  194. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  195. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  196. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  197. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  198. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  199. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  200. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  201. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  202. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  203. package/dist/web/standalone/.next/server/app/index.html +1 -1
  204. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  205. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  206. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  207. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  208. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  209. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  210. package/dist/web/standalone/.next/server/app/page.js +2 -2
  211. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  212. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  213. package/dist/web/standalone/.next/server/chunks/229.js +3 -3
  214. package/dist/web/standalone/.next/server/chunks/471.js +3 -3
  215. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  216. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  217. package/dist/web/standalone/.next/server/middleware.js +2 -2
  218. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  219. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  220. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  221. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  222. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  223. package/dist/web/standalone/.next/static/chunks/4024.c195dc1fdd2adbea.js +9 -0
  224. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-f2a7482d42a5614b.js +1 -0
  225. package/dist/web/standalone/.next/static/chunks/app/layout-a16c7a7ecdf0c2cf.js +1 -0
  226. package/dist/web/standalone/.next/static/chunks/app/page-b9367c5ae13b99c6.js +1 -0
  227. package/dist/web/standalone/.next/static/chunks/{main-app-2f2ee7b85712c2bd.js → main-app-fdab67f7802d7832.js} +1 -1
  228. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  229. package/dist/web/standalone/.next/static/chunks/{webpack-9afaaebf6042a1d7.js → webpack-fa307370fcf9fb2c.js} +1 -1
  230. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  231. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  232. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  233. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  234. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  235. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  236. package/dist/web/standalone/server.js +1 -1
  237. package/dist/web-mode.d.ts +4 -0
  238. package/dist/web-mode.js +69 -11
  239. package/package.json +1 -1
  240. package/packages/native/src/__tests__/text.test.mjs +33 -0
  241. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  242. package/packages/pi-agent-core/dist/agent.js +2 -0
  243. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  244. package/packages/pi-agent-core/dist/types.d.ts +6 -0
  245. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  246. package/packages/pi-agent-core/dist/types.js.map +1 -1
  247. package/packages/pi-agent-core/src/agent.test.ts +53 -0
  248. package/packages/pi-agent-core/src/agent.ts +3 -0
  249. package/packages/pi-agent-core/src/types.ts +6 -0
  250. package/packages/pi-agent-core/tsconfig.json +1 -1
  251. package/packages/pi-ai/dist/models.d.ts +5 -3
  252. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  253. package/packages/pi-ai/dist/models.generated.d.ts +801 -1468
  254. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  255. package/packages/pi-ai/dist/models.generated.js +1135 -1588
  256. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  257. package/packages/pi-ai/dist/models.js.map +1 -1
  258. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  259. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +60 -2
  260. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  261. package/packages/pi-ai/scripts/generate-models.ts +1543 -0
  262. package/packages/pi-ai/src/models.generated.ts +1140 -1593
  263. package/packages/pi-ai/src/models.ts +7 -4
  264. package/packages/pi-ai/src/utils/oauth/github-copilot.ts +74 -2
  265. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  266. package/packages/pi-coding-agent/dist/core/agent-session.js +8 -1
  267. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  268. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +7 -0
  269. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  270. package/packages/pi-coding-agent/dist/core/auth-storage.js +29 -2
  271. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  272. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +60 -0
  273. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  274. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +3 -1
  275. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -1
  276. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  277. package/packages/pi-coding-agent/dist/core/extensions/loader.js +18 -0
  278. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  279. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  280. package/packages/pi-coding-agent/dist/core/lsp/client.js +23 -0
  281. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  282. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  283. package/packages/pi-coding-agent/dist/core/model-registry.js +2 -0
  284. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  285. package/packages/pi-coding-agent/dist/core/package-manager.d.ts +6 -0
  286. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  287. package/packages/pi-coding-agent/dist/core/package-manager.js +63 -11
  288. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  289. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +9 -0
  290. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  291. package/packages/pi-coding-agent/dist/core/resource-loader.js +20 -6
  292. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  293. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  294. package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -5
  295. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  296. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  297. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js +3 -0
  298. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js.map +1 -1
  299. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  300. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +9 -6
  301. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  302. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  303. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +10 -7
  304. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  305. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  306. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +34 -10
  307. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  308. package/packages/pi-coding-agent/package.json +1 -1
  309. package/packages/pi-coding-agent/src/core/agent-session.ts +7 -1
  310. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +68 -0
  311. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -2
  312. package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +4 -2
  313. package/packages/pi-coding-agent/src/core/extensions/loader.ts +18 -0
  314. package/packages/pi-coding-agent/src/core/lsp/client.ts +29 -0
  315. package/packages/pi-coding-agent/src/core/model-registry.ts +3 -0
  316. package/packages/pi-coding-agent/src/core/package-manager.ts +99 -58
  317. package/packages/pi-coding-agent/src/core/resource-loader.ts +24 -6
  318. package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -5
  319. package/packages/pi-coding-agent/src/modes/interactive/components/extension-editor.ts +3 -0
  320. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +10 -6
  321. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +11 -7
  322. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +36 -11
  323. package/pkg/package.json +1 -1
  324. package/src/resources/extensions/async-jobs/async-bash-timeout.test.ts +122 -0
  325. package/src/resources/extensions/async-jobs/async-bash-tool.ts +40 -4
  326. package/src/resources/extensions/async-jobs/await-tool.test.ts +47 -0
  327. package/src/resources/extensions/async-jobs/await-tool.ts +5 -0
  328. package/src/resources/extensions/async-jobs/index.ts +1 -0
  329. package/src/resources/extensions/async-jobs/job-manager.ts +2 -0
  330. package/src/resources/extensions/gsd/auto/loop-deps.ts +5 -2
  331. package/src/resources/extensions/gsd/auto/loop.ts +101 -1
  332. package/src/resources/extensions/gsd/auto/phases.ts +31 -13
  333. package/src/resources/extensions/gsd/auto/session.ts +6 -0
  334. package/src/resources/extensions/gsd/auto/types.ts +4 -0
  335. package/src/resources/extensions/gsd/auto-dashboard.ts +9 -2
  336. package/src/resources/extensions/gsd/auto-dispatch.ts +25 -5
  337. package/src/resources/extensions/gsd/auto-post-unit.ts +8 -0
  338. package/src/resources/extensions/gsd/auto-prompts.ts +2 -18
  339. package/src/resources/extensions/gsd/auto-recovery.ts +12 -4
  340. package/src/resources/extensions/gsd/auto-start.ts +15 -13
  341. package/src/resources/extensions/gsd/auto-worktree.ts +162 -18
  342. package/src/resources/extensions/gsd/auto.ts +71 -2
  343. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +209 -162
  344. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +62 -0
  345. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  346. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +25 -4
  347. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +9 -2
  348. package/src/resources/extensions/gsd/commands/catalog.ts +40 -1
  349. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
  350. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  351. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +164 -0
  352. package/src/resources/extensions/gsd/context-injector.ts +100 -0
  353. package/src/resources/extensions/gsd/context-store.ts +4 -3
  354. package/src/resources/extensions/gsd/custom-execution-policy.ts +73 -0
  355. package/src/resources/extensions/gsd/custom-verification.ts +180 -0
  356. package/src/resources/extensions/gsd/custom-workflow-engine.ts +216 -0
  357. package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -0
  358. package/src/resources/extensions/gsd/db-writer.ts +6 -2
  359. package/src/resources/extensions/gsd/definition-loader.ts +462 -0
  360. package/src/resources/extensions/gsd/detection.ts +20 -1
  361. package/src/resources/extensions/gsd/dev-execution-policy.ts +51 -0
  362. package/src/resources/extensions/gsd/dev-workflow-engine.ts +110 -0
  363. package/src/resources/extensions/gsd/doctor-checks.ts +32 -1
  364. package/src/resources/extensions/gsd/doctor-providers.ts +13 -0
  365. package/src/resources/extensions/gsd/doctor-types.ts +1 -0
  366. package/src/resources/extensions/gsd/doctor.ts +12 -1
  367. package/src/resources/extensions/gsd/engine-resolver.ts +57 -0
  368. package/src/resources/extensions/gsd/engine-types.ts +71 -0
  369. package/src/resources/extensions/gsd/execution-policy.ts +43 -0
  370. package/src/resources/extensions/gsd/exit-command.ts +14 -2
  371. package/src/resources/extensions/gsd/export.ts +8 -15
  372. package/src/resources/extensions/gsd/extension-manifest.json +2 -2
  373. package/src/resources/extensions/gsd/files.ts +29 -12
  374. package/src/resources/extensions/gsd/forensics.ts +101 -3
  375. package/src/resources/extensions/gsd/git-constants.ts +1 -0
  376. package/src/resources/extensions/gsd/git-service.ts +5 -5
  377. package/src/resources/extensions/gsd/gitignore.ts +1 -1
  378. package/src/resources/extensions/gsd/graph.ts +312 -0
  379. package/src/resources/extensions/gsd/gsd-db.ts +37 -8
  380. package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
  381. package/src/resources/extensions/gsd/guided-flow.ts +7 -3
  382. package/src/resources/extensions/gsd/journal.ts +134 -0
  383. package/src/resources/extensions/gsd/md-importer.ts +6 -0
  384. package/src/resources/extensions/gsd/milestone-ids.ts +1 -1
  385. package/src/resources/extensions/gsd/native-git-bridge.ts +3 -2
  386. package/src/resources/extensions/gsd/post-unit-hooks.ts +24 -462
  387. package/src/resources/extensions/gsd/preferences-types.ts +6 -0
  388. package/src/resources/extensions/gsd/preferences.ts +63 -6
  389. package/src/resources/extensions/gsd/prompt-loader.ts +35 -4
  390. package/src/resources/extensions/gsd/prompts/complete-milestone.md +11 -10
  391. package/src/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
  392. package/src/resources/extensions/gsd/prompts/discuss.md +1 -1
  393. package/src/resources/extensions/gsd/prompts/forensics.md +12 -5
  394. package/src/resources/extensions/gsd/prompts/queue.md +1 -1
  395. package/src/resources/extensions/gsd/repo-identity.ts +95 -7
  396. package/src/resources/extensions/gsd/rule-registry.ts +599 -0
  397. package/src/resources/extensions/gsd/rule-types.ts +68 -0
  398. package/src/resources/extensions/gsd/run-manager.ts +180 -0
  399. package/src/resources/extensions/gsd/service-tier.ts +184 -0
  400. package/src/resources/extensions/gsd/session-lock.ts +2 -2
  401. package/src/resources/extensions/gsd/structured-data-formatter.ts +3 -1
  402. package/src/resources/extensions/gsd/templates/decisions.md +2 -2
  403. package/src/resources/extensions/gsd/tests/activity-log.test.ts +31 -69
  404. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +103 -120
  405. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +85 -0
  406. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -2
  407. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +202 -0
  408. package/src/resources/extensions/gsd/tests/bundled-workflow-defs.test.ts +180 -0
  409. package/src/resources/extensions/gsd/tests/captures.test.ts +12 -1
  410. package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +283 -0
  411. package/src/resources/extensions/gsd/tests/context-injector.test.ts +313 -0
  412. package/src/resources/extensions/gsd/tests/context-store.test.ts +10 -5
  413. package/src/resources/extensions/gsd/tests/continue-here.test.ts +20 -20
  414. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +540 -0
  415. package/src/resources/extensions/gsd/tests/custom-verification.test.ts +382 -0
  416. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +339 -0
  417. package/src/resources/extensions/gsd/tests/dashboard-custom-engine.test.ts +87 -0
  418. package/src/resources/extensions/gsd/tests/db-writer.test.ts +10 -0
  419. package/src/resources/extensions/gsd/tests/definition-loader.test.ts +778 -0
  420. package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +318 -0
  421. package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +15 -10
  422. package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +5 -4
  423. package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +167 -0
  424. package/src/resources/extensions/gsd/tests/doctor-task-done-missing-summary-slice-loop.test.ts +174 -0
  425. package/src/resources/extensions/gsd/tests/e2e-workflow-pipeline-integration.test.ts +476 -0
  426. package/src/resources/extensions/gsd/tests/engine-interfaces-contract.test.ts +271 -0
  427. package/src/resources/extensions/gsd/tests/exit-command.test.ts +55 -0
  428. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +48 -0
  429. package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +43 -0
  430. package/src/resources/extensions/gsd/tests/git-locale.test.ts +133 -0
  431. package/src/resources/extensions/gsd/tests/git-service.test.ts +44 -0
  432. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +599 -0
  433. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +8 -1
  434. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +7 -7
  435. package/src/resources/extensions/gsd/tests/iterate-engine-integration.test.ts +429 -0
  436. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +513 -0
  437. package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +147 -0
  438. package/src/resources/extensions/gsd/tests/journal.test.ts +341 -0
  439. package/src/resources/extensions/gsd/tests/manifest-status.test.ts +73 -82
  440. package/src/resources/extensions/gsd/tests/md-importer.test.ts +31 -1
  441. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  442. package/src/resources/extensions/gsd/tests/milestone-id-reservation.test.ts +1 -1
  443. package/src/resources/extensions/gsd/tests/parsers.test.ts +110 -0
  444. package/src/resources/extensions/gsd/tests/preferences.test.ts +47 -25
  445. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +3 -1
  446. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +61 -1
  447. package/src/resources/extensions/gsd/tests/routing-history.test.ts +11 -22
  448. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +413 -0
  449. package/src/resources/extensions/gsd/tests/run-manager.test.ts +229 -0
  450. package/src/resources/extensions/gsd/tests/service-tier.test.ts +127 -0
  451. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +56 -3
  452. package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +2 -2
  453. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +102 -0
  454. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +4 -3
  455. package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +151 -0
  456. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +45 -0
  457. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +117 -0
  458. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -1
  459. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +156 -263
  460. package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +99 -0
  461. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -0
  462. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +4 -0
  463. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +135 -0
  464. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +203 -106
  465. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +79 -5
  466. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +140 -0
  467. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +74 -0
  468. package/src/resources/extensions/gsd/types.ts +3 -0
  469. package/src/resources/extensions/gsd/workflow-engine.ts +38 -0
  470. package/src/resources/extensions/gsd/workflow-templates.ts +12 -1
  471. package/src/resources/extensions/gsd/worktree-manager.ts +21 -6
  472. package/src/resources/extensions/gsd/worktree-resolver.ts +32 -12
  473. package/src/resources/extensions/gsd/worktree.ts +2 -2
  474. package/src/resources/extensions/mcp-client/index.ts +5 -1
  475. package/src/resources/extensions/search-the-web/tool-search.ts +3 -3
  476. package/src/resources/extensions/subagent/index.ts +7 -3
  477. package/src/resources/extensions/voice/index.ts +4 -4
  478. package/src/resources/skills/create-workflow/SKILL.md +103 -0
  479. package/src/resources/skills/create-workflow/references/feature-patterns.md +128 -0
  480. package/src/resources/skills/create-workflow/references/verification-policies.md +76 -0
  481. package/src/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
  482. package/src/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
  483. package/src/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
  484. package/src/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
  485. package/src/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
  486. package/src/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
  487. package/src/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
  488. package/dist/web/standalone/.next/static/chunks/4024.279c423e4661ece1.js +0 -9
  489. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-e07acdb7dd069836.js +0 -1
  490. package/dist/web/standalone/.next/static/chunks/app/layout-745c6ed5fea5fb06.js +0 -1
  491. package/dist/web/standalone/.next/static/chunks/app/page-801b53eff6e83579.js +0 -1
  492. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-e6255954dccfcf0a.js +0 -1
  493. /package/dist/web/standalone/.next/static/{Ute3pMouVczQyT15qrBBO → qw8qDHXOTLUXBq1vEknSz}/_buildManifest.js +0 -0
  494. /package/dist/web/standalone/.next/static/{Ute3pMouVczQyT15qrBBO → qw8qDHXOTLUXBq1vEknSz}/_ssgManifest.js +0 -0
@@ -0,0 +1,413 @@
1
+ // GSD Extension — Rule Registry Tests
2
+ //
3
+ // Tests the RuleRegistry class, UnifiedRule types, singleton accessors,
4
+ // and evaluation methods using mock rules.
5
+
6
+ import { test, describe, beforeEach } from "node:test";
7
+ import { createTestContext } from "./test-helpers.ts";
8
+ import {
9
+ RuleRegistry,
10
+ getRegistry,
11
+ setRegistry,
12
+ initRegistry,
13
+ resetRegistry,
14
+ convertDispatchRules,
15
+ getOrCreateRegistry,
16
+ } from "../rule-registry.ts";
17
+ import type { UnifiedRule } from "../rule-types.ts";
18
+ import type { DispatchAction, DispatchContext } from "../auto-dispatch.ts";
19
+ import { DISPATCH_RULES, getDispatchRuleNames } from "../auto-dispatch.ts";
20
+ import type { GSDState } from "../types.ts";
21
+
22
+ // ─── Mock Rule Factories ──────────────────────────────────────────────────
23
+
24
+ function mockDispatchRule(name: string, matchPhase: string): UnifiedRule {
25
+ return {
26
+ name,
27
+ when: "dispatch",
28
+ evaluation: "first-match",
29
+ where: async (ctx: DispatchContext): Promise<DispatchAction | null> => {
30
+ if (ctx.state.phase === matchPhase) {
31
+ return {
32
+ action: "dispatch",
33
+ unitType: `test-${matchPhase}`,
34
+ unitId: "test-id",
35
+ prompt: `Prompt for ${matchPhase}`,
36
+ };
37
+ }
38
+ return null;
39
+ },
40
+ then: () => {},
41
+ description: `Mock rule for ${matchPhase}`,
42
+ };
43
+ }
44
+
45
+ function makeContext(phase: string): DispatchContext {
46
+ return {
47
+ basePath: "/tmp/test",
48
+ mid: "M001",
49
+ midTitle: "Test Milestone",
50
+ state: {
51
+ phase: phase as any,
52
+ activeMilestone: { id: "M001", title: "Test" },
53
+ activeSlice: null,
54
+ activeTask: null,
55
+ recentDecisions: [],
56
+ blockers: [],
57
+ nextAction: "",
58
+ registry: [],
59
+ },
60
+ prefs: undefined,
61
+ };
62
+ }
63
+
64
+ // ─── Tests ────────────────────────────────────────────────────────────────
65
+
66
+ describe("RuleRegistry", () => {
67
+ const { assertEq, assertTrue } = createTestContext();
68
+
69
+ beforeEach(() => {
70
+ resetRegistry();
71
+ });
72
+
73
+ test("construct with dispatch rules, listRules returns them", () => {
74
+ const rules: UnifiedRule[] = [
75
+ mockDispatchRule("rule-a", "planning"),
76
+ mockDispatchRule("rule-b", "executing"),
77
+ mockDispatchRule("rule-c", "complete"),
78
+ ];
79
+ const registry = new RuleRegistry(rules);
80
+ const listed = registry.listRules();
81
+
82
+ // At minimum, dispatch rules are returned (hook rules depend on prefs)
83
+ const dispatchRules = listed.filter(r => r.when === "dispatch");
84
+ assertEq(dispatchRules.length, 3, "listRules returns 3 dispatch rules");
85
+ assertEq(dispatchRules[0].name, "rule-a", "first rule name is rule-a");
86
+ assertEq(dispatchRules[1].name, "rule-b", "second rule name is rule-b");
87
+ assertEq(dispatchRules[2].name, "rule-c", "third rule name is rule-c");
88
+ });
89
+
90
+ test("listRules returns correct fields on each rule", () => {
91
+ const rules: UnifiedRule[] = [
92
+ mockDispatchRule("check-fields", "planning"),
93
+ ];
94
+ const registry = new RuleRegistry(rules);
95
+ const listed = registry.listRules();
96
+ const rule = listed.find(r => r.name === "check-fields")!;
97
+
98
+ assertTrue(rule !== undefined, "rule found by name");
99
+ assertEq(rule.when, "dispatch", "when field is dispatch");
100
+ assertEq(rule.evaluation, "first-match", "evaluation is first-match");
101
+ assertTrue(typeof rule.where === "function", "where is a function");
102
+ assertTrue(typeof rule.then === "function", "then is a function");
103
+ assertEq(rule.description, "Mock rule for planning", "description is set");
104
+ });
105
+
106
+ test("evaluateDispatch returns first matching rule", async () => {
107
+ const rules: UnifiedRule[] = [
108
+ mockDispatchRule("rule-planning", "planning"),
109
+ mockDispatchRule("rule-executing", "executing"),
110
+ mockDispatchRule("rule-complete", "complete"),
111
+ ];
112
+ const registry = new RuleRegistry(rules);
113
+ const ctx = makeContext("executing");
114
+ const result = await registry.evaluateDispatch(ctx);
115
+
116
+ assertEq(result.action, "dispatch", "result is a dispatch action");
117
+ if (result.action === "dispatch") {
118
+ assertEq(result.unitType, "test-executing", "matched the executing rule");
119
+ assertEq(result.prompt, "Prompt for executing", "prompt from matched rule");
120
+ }
121
+ });
122
+
123
+ test("evaluateDispatch returns stop when no rule matches", async () => {
124
+ const rules: UnifiedRule[] = [
125
+ mockDispatchRule("only-planning", "planning"),
126
+ ];
127
+ const registry = new RuleRegistry(rules);
128
+ const ctx = makeContext("blocked");
129
+ const result = await registry.evaluateDispatch(ctx);
130
+
131
+ assertEq(result.action, "stop", "result is a stop action");
132
+ if (result.action === "stop") {
133
+ assertTrue(result.reason.includes("blocked"), "stop reason mentions phase");
134
+ }
135
+ });
136
+
137
+ test("evaluateDispatch works with async where predicate", async () => {
138
+ const asyncRule: UnifiedRule = {
139
+ name: "async-rule",
140
+ when: "dispatch",
141
+ evaluation: "first-match",
142
+ where: async (ctx: DispatchContext): Promise<DispatchAction | null> => {
143
+ // Simulate async work
144
+ await new Promise(resolve => setTimeout(resolve, 1));
145
+ if (ctx.state.phase === "planning") {
146
+ return {
147
+ action: "dispatch",
148
+ unitType: "async-test",
149
+ unitId: "async-id",
150
+ prompt: "Async prompt",
151
+ };
152
+ }
153
+ return null;
154
+ },
155
+ then: () => {},
156
+ };
157
+
158
+ const registry = new RuleRegistry([asyncRule]);
159
+ const ctx = makeContext("planning");
160
+ const result = await registry.evaluateDispatch(ctx);
161
+
162
+ assertEq(result.action, "dispatch", "async dispatch resolved");
163
+ if (result.action === "dispatch") {
164
+ assertEq(result.unitType, "async-test", "async rule matched");
165
+ }
166
+ });
167
+
168
+ test("resetState clears all mutable state", () => {
169
+ const registry = new RuleRegistry([]);
170
+
171
+ // Set up some state
172
+ registry.activeHook = {
173
+ hookName: "test-hook",
174
+ triggerUnitType: "execute-task",
175
+ triggerUnitId: "M001/S01/T01",
176
+ cycle: 2,
177
+ pendingRetry: false,
178
+ };
179
+ registry.hookQueue.push({
180
+ config: { name: "q", after: [], prompt: "p" },
181
+ triggerUnitType: "execute-task",
182
+ triggerUnitId: "M001/S01/T02",
183
+ });
184
+ registry.cycleCounts.set("test/key", 3);
185
+ registry.retryPending = true;
186
+ registry.retryTrigger = { unitType: "execute-task", unitId: "M001/S01/T01", retryArtifact: "RETRY" };
187
+
188
+ // Reset
189
+ registry.resetState();
190
+
191
+ assertEq(registry.getActiveHook(), null, "activeHook cleared");
192
+ assertEq(registry.hookQueue.length, 0, "hookQueue cleared");
193
+ assertEq(registry.cycleCounts.size, 0, "cycleCounts cleared");
194
+ assertEq(registry.isRetryPending(), false, "retryPending cleared");
195
+ assertEq(registry.consumeRetryTrigger(), null, "retryTrigger cleared");
196
+ });
197
+
198
+ test("singleton getRegistry throws when not initialized", () => {
199
+ let threw = false;
200
+ try {
201
+ getRegistry();
202
+ } catch (e: any) {
203
+ threw = true;
204
+ assertTrue(e.message.includes("not initialized"), "error mentions not initialized");
205
+ }
206
+ assertTrue(threw, "getRegistry threw");
207
+ });
208
+
209
+ test("setRegistry / getRegistry round-trips", () => {
210
+ const registry = new RuleRegistry([mockDispatchRule("singleton-test", "planning")]);
211
+ setRegistry(registry);
212
+
213
+ const retrieved = getRegistry();
214
+ assertEq(retrieved, registry, "getRegistry returns the same instance");
215
+
216
+ const listed = retrieved.listRules().filter(r => r.when === "dispatch");
217
+ assertEq(listed.length, 1, "singleton has 1 dispatch rule");
218
+ assertEq(listed[0].name, "singleton-test", "rule name matches");
219
+ });
220
+
221
+ test("initRegistry creates and sets singleton", () => {
222
+ const rules = [mockDispatchRule("init-test", "executing")];
223
+ const registry = initRegistry(rules);
224
+
225
+ assertEq(getRegistry(), registry, "initRegistry sets the singleton");
226
+ const listed = getRegistry().listRules().filter(r => r.when === "dispatch");
227
+ assertEq(listed.length, 1, "singleton has the rule");
228
+ });
229
+
230
+ test("evaluateDispatch respects rule order (first match wins)", async () => {
231
+ // Both rules match "planning" but rule-first should win
232
+ const ruleFirst: UnifiedRule = {
233
+ name: "rule-first",
234
+ when: "dispatch",
235
+ evaluation: "first-match",
236
+ where: async (ctx: DispatchContext) => {
237
+ if (ctx.state.phase === "planning") {
238
+ return { action: "dispatch" as const, unitType: "first-wins", unitId: "id", prompt: "first" };
239
+ }
240
+ return null;
241
+ },
242
+ then: () => {},
243
+ };
244
+ const ruleSecond: UnifiedRule = {
245
+ name: "rule-second",
246
+ when: "dispatch",
247
+ evaluation: "first-match",
248
+ where: async (ctx: DispatchContext) => {
249
+ if (ctx.state.phase === "planning") {
250
+ return { action: "dispatch" as const, unitType: "second-loses", unitId: "id", prompt: "second" };
251
+ }
252
+ return null;
253
+ },
254
+ then: () => {},
255
+ };
256
+
257
+ const registry = new RuleRegistry([ruleFirst, ruleSecond]);
258
+ const ctx = makeContext("planning");
259
+ const result = await registry.evaluateDispatch(ctx);
260
+
261
+ assertEq(result.action, "dispatch", "dispatch action returned");
262
+ if (result.action === "dispatch") {
263
+ assertEq(result.unitType, "first-wins", "first rule won over second");
264
+ }
265
+ });
266
+
267
+ // ── Dispatch rule conversion tests ─────────────────────────────────
268
+
269
+ test("convertDispatchRules produces correct count of UnifiedRule objects", () => {
270
+ const converted = convertDispatchRules(DISPATCH_RULES);
271
+ assertEq(converted.length, DISPATCH_RULES.length, `convertDispatchRules produces ${DISPATCH_RULES.length} rules`);
272
+ });
273
+
274
+ test("each converted rule has correct when, evaluation, and original name", () => {
275
+ const converted = convertDispatchRules(DISPATCH_RULES);
276
+ for (let i = 0; i < converted.length; i++) {
277
+ const rule = converted[i];
278
+ assertEq(rule.when, "dispatch", `rule ${i} has when:"dispatch"`);
279
+ assertEq(rule.evaluation, "first-match", `rule ${i} has evaluation:"first-match"`);
280
+ assertEq(rule.name, DISPATCH_RULES[i].name, `rule ${i} preserves name "${DISPATCH_RULES[i].name}"`);
281
+ assertTrue(typeof rule.where === "function", `rule ${i} has a where function`);
282
+ assertTrue(typeof rule.then === "function", `rule ${i} has a then function`);
283
+ }
284
+ });
285
+
286
+ test("listRules after construction with real dispatch rules returns correct count", () => {
287
+ const converted = convertDispatchRules(DISPATCH_RULES);
288
+ const registry = new RuleRegistry(converted);
289
+ const listed = registry.listRules().filter(r => r.when === "dispatch");
290
+ assertEq(listed.length, DISPATCH_RULES.length, `listRules returns ${DISPATCH_RULES.length} dispatch rules`);
291
+ });
292
+
293
+ test("rule names from listRules match getDispatchRuleNames in exact order", () => {
294
+ const converted = convertDispatchRules(DISPATCH_RULES);
295
+ const registry = new RuleRegistry(converted);
296
+ const listedNames = registry.listRules()
297
+ .filter(r => r.when === "dispatch")
298
+ .map(r => r.name);
299
+ const originalNames = getDispatchRuleNames();
300
+
301
+ assertEq(listedNames.length, originalNames.length, "same number of names");
302
+ for (let i = 0; i < originalNames.length; i++) {
303
+ assertEq(listedNames[i], originalNames[i], `name at index ${i} matches: "${originalNames[i]}"`);
304
+ }
305
+ });
306
+
307
+ // ── getOrCreateRegistry (lazy init for facades) ────────────────────
308
+
309
+ test("getOrCreateRegistry lazily creates a registry with empty dispatch rules", () => {
310
+ // After resetRegistry(), getRegistry() would throw. getOrCreateRegistry() should not.
311
+ const registry = getOrCreateRegistry();
312
+ assertTrue(registry instanceof RuleRegistry, "returns a RuleRegistry instance");
313
+ const dispatchRules = registry.listRules().filter(r => r.when === "dispatch");
314
+ assertEq(dispatchRules.length, 0, "lazily-created registry has 0 dispatch rules");
315
+ });
316
+
317
+ test("getOrCreateRegistry returns existing registry when initialized", () => {
318
+ const rules = [mockDispatchRule("explicit-init", "planning")];
319
+ const explicit = initRegistry(rules);
320
+ const lazy = getOrCreateRegistry();
321
+ assertEq(lazy, explicit, "getOrCreateRegistry returns the same singleton as initRegistry");
322
+ const dispatchRules = lazy.listRules().filter(r => r.when === "dispatch");
323
+ assertEq(dispatchRules.length, 1, "singleton has the explicitly initialized dispatch rule");
324
+ });
325
+
326
+ // ── Hook-derived rules in listRules ────────────────────────────────
327
+
328
+ test("listRules returns only dispatch rules when no hooks are configured", () => {
329
+ const converted = convertDispatchRules(DISPATCH_RULES);
330
+ const registry = new RuleRegistry(converted);
331
+ const allRules = registry.listRules();
332
+ const postUnitRules = allRules.filter(r => r.when === "post-unit");
333
+ const preDispatchRules = allRules.filter(r => r.when === "pre-dispatch");
334
+
335
+ // No preferences file = no hooks
336
+ assertEq(postUnitRules.length, 0, "no post-unit rules when no hooks configured");
337
+ assertEq(preDispatchRules.length, 0, "no pre-dispatch rules when no hooks configured");
338
+ assertEq(allRules.length, DISPATCH_RULES.length, "total rules equals dispatch rules only");
339
+ });
340
+
341
+ test("listRules dispatch rules appear first, hooks after", () => {
342
+ const converted = convertDispatchRules(DISPATCH_RULES);
343
+ const registry = new RuleRegistry(converted);
344
+ const allRules = registry.listRules();
345
+
346
+ // Verify dispatch rules come first (indices 0..N-1)
347
+ for (let i = 0; i < converted.length; i++) {
348
+ assertEq(allRules[i].when, "dispatch", `rule at index ${i} is a dispatch rule`);
349
+ assertEq(allRules[i].name, converted[i].name, `dispatch rule at index ${i} has correct name`);
350
+ }
351
+ });
352
+
353
+ // ── Facade delegation (post-unit-hooks.ts imports work through registry) ──
354
+
355
+ test("evaluatePostUnit returns null for hook-on-hook prevention", () => {
356
+ const registry = new RuleRegistry([]);
357
+ const result = registry.evaluatePostUnit("hook/code-review", "M001/S01/T01", "/tmp/test");
358
+ assertEq(result, null, "hook units don't trigger other hooks");
359
+ });
360
+
361
+ test("evaluatePostUnit returns null for triage-captures", () => {
362
+ const registry = new RuleRegistry([]);
363
+ const result = registry.evaluatePostUnit("triage-captures", "M001/S01/T01", "/tmp/test");
364
+ assertEq(result, null, "triage-captures skipped");
365
+ });
366
+
367
+ test("evaluatePostUnit returns null for quick-task", () => {
368
+ const registry = new RuleRegistry([]);
369
+ const result = registry.evaluatePostUnit("quick-task", "M001/S01/T01", "/tmp/test");
370
+ assertEq(result, null, "quick-task skipped");
371
+ });
372
+
373
+ test("evaluatePreDispatch bypasses hook units", () => {
374
+ const registry = new RuleRegistry([]);
375
+ const result = registry.evaluatePreDispatch("hook/review", "M001/S01/T01", "prompt", "/tmp/test");
376
+ assertEq(result.action, "proceed", "hook units always proceed");
377
+ assertEq(result.prompt, "prompt", "prompt unchanged");
378
+ assertEq(result.firedHooks.length, 0, "no hooks fired");
379
+ });
380
+
381
+ test("evaluatePreDispatch proceeds with empty hooks", () => {
382
+ const registry = new RuleRegistry([]);
383
+ const result = registry.evaluatePreDispatch("execute-task", "M001/S01/T01", "original prompt", "/tmp/test");
384
+ assertEq(result.action, "proceed", "proceeds when no hooks");
385
+ assertEq(result.prompt, "original prompt", "prompt unchanged");
386
+ });
387
+
388
+ // ── matchedRule provenance (S02 journal support) ───────────────────
389
+
390
+ test("evaluateDispatch result includes matchedRule on dispatch match", async () => {
391
+ const rules: UnifiedRule[] = [
392
+ mockDispatchRule("my-planning-rule", "planning"),
393
+ ];
394
+ const registry = new RuleRegistry(rules);
395
+ const ctx = makeContext("planning");
396
+ const result = await registry.evaluateDispatch(ctx);
397
+
398
+ assertEq(result.action, "dispatch", "result is a dispatch action");
399
+ assertEq(result.matchedRule, "my-planning-rule", "matchedRule is the rule name");
400
+ });
401
+
402
+ test("evaluateDispatch result includes matchedRule '<no-match>' on fallback stop", async () => {
403
+ const rules: UnifiedRule[] = [
404
+ mockDispatchRule("only-planning", "planning"),
405
+ ];
406
+ const registry = new RuleRegistry(rules);
407
+ const ctx = makeContext("some-unknown-phase");
408
+ const result = await registry.evaluateDispatch(ctx);
409
+
410
+ assertEq(result.action, "stop", "result is a stop action");
411
+ assertEq(result.matchedRule, "<no-match>", "matchedRule is '<no-match>' on fallback");
412
+ });
413
+ });
@@ -0,0 +1,229 @@
1
+ /**
2
+ * run-manager.test.ts — Tests for run directory creation and listing.
3
+ *
4
+ * Uses real temp directories with actual definition YAML files and
5
+ * GRAPH.yaml persistence — no mocks.
6
+ */
7
+
8
+ import { describe, it, afterEach } from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import {
11
+ mkdtempSync,
12
+ rmSync,
13
+ mkdirSync,
14
+ writeFileSync,
15
+ readFileSync,
16
+ existsSync,
17
+ readdirSync,
18
+ } from "node:fs";
19
+ import { join } from "node:path";
20
+ import { tmpdir } from "node:os";
21
+ import { parse } from "yaml";
22
+
23
+ import { createRun, listRuns } from "../run-manager.ts";
24
+
25
+ // ─── Helpers ─────────────────────────────────────────────────────────────
26
+
27
+ const tmpDirs: string[] = [];
28
+
29
+ function makeTmpBase(): string {
30
+ const dir = mkdtempSync(join(tmpdir(), "run-mgr-test-"));
31
+ tmpDirs.push(dir);
32
+ return dir;
33
+ }
34
+
35
+ afterEach(() => {
36
+ for (const d of tmpDirs) {
37
+ try { rmSync(d, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* Windows EPERM */ }
38
+ }
39
+ tmpDirs.length = 0;
40
+ });
41
+
42
+ /** Write a minimal valid workflow definition YAML to the expected location. */
43
+ function writeDefinition(
44
+ basePath: string,
45
+ name: string,
46
+ content: string,
47
+ ): void {
48
+ const defsDir = join(basePath, ".gsd", "workflow-defs");
49
+ mkdirSync(defsDir, { recursive: true });
50
+ writeFileSync(join(defsDir, `${name}.yaml`), content, "utf-8");
51
+ }
52
+
53
+ const SIMPLE_DEF = `
54
+ version: 1
55
+ name: test-workflow
56
+ description: A test workflow
57
+ steps:
58
+ - id: step-1
59
+ name: First Step
60
+ prompt: Do step 1
61
+ requires: []
62
+ produces: []
63
+ - id: step-2
64
+ name: Second Step
65
+ prompt: Do step 2
66
+ requires:
67
+ - step-1
68
+ produces: []
69
+ `;
70
+
71
+ const PARAMETERIZED_DEF = `
72
+ version: 1
73
+ name: param-workflow
74
+ description: A parameterized workflow
75
+ params:
76
+ target: default-target
77
+ steps:
78
+ - id: step-1
79
+ name: Build
80
+ prompt: "Build {{target}}"
81
+ requires: []
82
+ produces: []
83
+ `;
84
+
85
+ // ─── createRun ───────────────────────────────────────────────────────────
86
+
87
+ describe("createRun", () => {
88
+ it("creates directory structure with DEFINITION.yaml and GRAPH.yaml", () => {
89
+ const base = makeTmpBase();
90
+ writeDefinition(base, "test-workflow", SIMPLE_DEF);
91
+
92
+ const runDir = createRun(base, "test-workflow");
93
+
94
+ // Run directory exists
95
+ assert.ok(existsSync(runDir), "run directory should exist");
96
+
97
+ // DEFINITION.yaml exists and contains the definition
98
+ const defPath = join(runDir, "DEFINITION.yaml");
99
+ assert.ok(existsSync(defPath), "DEFINITION.yaml should exist");
100
+ const defContent = parse(readFileSync(defPath, "utf-8"));
101
+ assert.equal(defContent.name, "test-workflow");
102
+ assert.equal(defContent.steps.length, 2);
103
+
104
+ // GRAPH.yaml exists with all steps pending
105
+ const graphPath = join(runDir, "GRAPH.yaml");
106
+ assert.ok(existsSync(graphPath), "GRAPH.yaml should exist");
107
+ const graphContent = parse(readFileSync(graphPath, "utf-8"));
108
+ assert.equal(graphContent.steps.length, 2);
109
+ assert.equal(graphContent.steps[0].status, "pending");
110
+ assert.equal(graphContent.steps[1].status, "pending");
111
+ assert.equal(graphContent.metadata.name, "test-workflow");
112
+
113
+ // No PARAMS.json without overrides
114
+ assert.ok(!existsSync(join(runDir, "PARAMS.json")), "PARAMS.json should not exist without overrides");
115
+
116
+ // Run directory path matches convention
117
+ assert.ok(runDir.includes(join(".gsd", "workflow-runs", "test-workflow")), "path should follow convention");
118
+ });
119
+
120
+ it("writes PARAMS.json and substituted prompts when overrides provided", () => {
121
+ const base = makeTmpBase();
122
+ writeDefinition(base, "param-workflow", PARAMETERIZED_DEF);
123
+
124
+ const runDir = createRun(base, "param-workflow", { target: "my-app" });
125
+
126
+ // PARAMS.json exists with overrides
127
+ const paramsPath = join(runDir, "PARAMS.json");
128
+ assert.ok(existsSync(paramsPath), "PARAMS.json should exist");
129
+ const params = JSON.parse(readFileSync(paramsPath, "utf-8"));
130
+ assert.deepStrictEqual(params, { target: "my-app" });
131
+
132
+ // DEFINITION.yaml has substituted prompts
133
+ const defPath = join(runDir, "DEFINITION.yaml");
134
+ const defContent = parse(readFileSync(defPath, "utf-8"));
135
+ assert.equal(defContent.steps[0].prompt, "Build my-app");
136
+
137
+ // GRAPH.yaml also has substituted prompts
138
+ const graphPath = join(runDir, "GRAPH.yaml");
139
+ const graphContent = parse(readFileSync(graphPath, "utf-8"));
140
+ assert.equal(graphContent.steps[0].prompt, "Build my-app");
141
+ });
142
+
143
+ it("throws for unknown definition", () => {
144
+ const base = makeTmpBase();
145
+ // Don't write any definition file
146
+
147
+ assert.throws(
148
+ () => createRun(base, "nonexistent"),
149
+ (err: Error) => err.message.includes("not found"),
150
+ );
151
+ });
152
+
153
+ it("uses filesystem-safe timestamp directory names", () => {
154
+ const base = makeTmpBase();
155
+ writeDefinition(base, "test-workflow", SIMPLE_DEF);
156
+
157
+ const runDir = createRun(base, "test-workflow");
158
+
159
+ // Extract the timestamp directory name (use path.sep for cross-platform)
160
+ const timestamp = runDir.split(/[/\\]/).pop()!;
161
+
162
+ // Should not contain colons (filesystem-unsafe on Windows)
163
+ assert.ok(!timestamp.includes(":"), `timestamp should not contain colons: ${timestamp}`);
164
+ // Should match YYYY-MM-DDTHH-MM-SS pattern
165
+ assert.match(timestamp, /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}$/);
166
+ });
167
+ });
168
+
169
+ // ─── listRuns ────────────────────────────────────────────────────────────
170
+
171
+ describe("listRuns", () => {
172
+ it("returns empty array when no runs exist", () => {
173
+ const base = makeTmpBase();
174
+ const runs = listRuns(base);
175
+ assert.deepStrictEqual(runs, []);
176
+ });
177
+
178
+ it("returns correct metadata for existing runs", () => {
179
+ const base = makeTmpBase();
180
+ writeDefinition(base, "test-workflow", SIMPLE_DEF);
181
+
182
+ // Create a run
183
+ const runDir = createRun(base, "test-workflow");
184
+
185
+ const runs = listRuns(base);
186
+ assert.equal(runs.length, 1);
187
+ assert.equal(runs[0].name, "test-workflow");
188
+ assert.equal(runs[0].runDir, runDir);
189
+ assert.equal(runs[0].steps.total, 2);
190
+ assert.equal(runs[0].steps.completed, 0);
191
+ assert.equal(runs[0].steps.pending, 2);
192
+ assert.equal(runs[0].steps.active, 0);
193
+ assert.equal(runs[0].status, "pending");
194
+ });
195
+
196
+ it("filters by definition name", () => {
197
+ const base = makeTmpBase();
198
+ writeDefinition(base, "test-workflow", SIMPLE_DEF);
199
+ writeDefinition(base, "param-workflow", PARAMETERIZED_DEF);
200
+
201
+ createRun(base, "test-workflow");
202
+ createRun(base, "param-workflow", { target: "app" });
203
+
204
+ const allRuns = listRuns(base);
205
+ assert.equal(allRuns.length, 2);
206
+
207
+ const filtered = listRuns(base, "test-workflow");
208
+ assert.equal(filtered.length, 1);
209
+ assert.equal(filtered[0].name, "test-workflow");
210
+ });
211
+
212
+ it("returns newest-first within same definition", () => {
213
+ const base = makeTmpBase();
214
+ writeDefinition(base, "test-workflow", SIMPLE_DEF);
215
+
216
+ const run1 = createRun(base, "test-workflow");
217
+ // Ensure different timestamp by creating run dir manually with earlier timestamp
218
+ const earlyDir = join(base, ".gsd", "workflow-runs", "test-workflow", "2020-01-01T00-00-00");
219
+ mkdirSync(earlyDir, { recursive: true });
220
+ // Copy GRAPH.yaml to make it a valid run
221
+ const graphContent = readFileSync(join(run1, "GRAPH.yaml"), "utf-8");
222
+ writeFileSync(join(earlyDir, "GRAPH.yaml"), graphContent, "utf-8");
223
+
224
+ const runs = listRuns(base, "test-workflow");
225
+ assert.equal(runs.length, 2);
226
+ // First should be the newer one (the one we just created)
227
+ assert.ok(runs[0].timestamp > runs[1].timestamp, "should be sorted newest-first");
228
+ });
229
+ });