gsd-pi 2.41.0-dev.0acbce9 → 2.41.0-dev.5a170d0

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 (291) hide show
  1. package/README.md +1 -1
  2. package/dist/cli-web-branch.d.ts +6 -0
  3. package/dist/cli-web-branch.js +17 -0
  4. package/dist/onboarding.js +2 -1
  5. package/dist/resources/extensions/gsd/auto/loop.js +89 -1
  6. package/dist/resources/extensions/gsd/auto/phases.js +28 -10
  7. package/dist/resources/extensions/gsd/auto/session.js +6 -0
  8. package/dist/resources/extensions/gsd/auto-dashboard.js +8 -2
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +19 -2
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +7 -0
  11. package/dist/resources/extensions/gsd/auto-recovery.js +12 -4
  12. package/dist/resources/extensions/gsd/auto-start.js +8 -3
  13. package/dist/resources/extensions/gsd/auto-worktree.js +147 -13
  14. package/dist/resources/extensions/gsd/auto.js +64 -2
  15. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +199 -164
  16. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +62 -0
  17. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  18. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +16 -0
  19. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +7 -2
  20. package/dist/resources/extensions/gsd/commands/catalog.js +40 -1
  21. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
  22. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  23. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +146 -0
  24. package/dist/resources/extensions/gsd/context-injector.js +74 -0
  25. package/dist/resources/extensions/gsd/context-store.js +4 -3
  26. package/dist/resources/extensions/gsd/custom-execution-policy.js +47 -0
  27. package/dist/resources/extensions/gsd/custom-verification.js +145 -0
  28. package/dist/resources/extensions/gsd/custom-workflow-engine.js +164 -0
  29. package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -0
  30. package/dist/resources/extensions/gsd/db-writer.js +5 -2
  31. package/dist/resources/extensions/gsd/definition-loader.js +352 -0
  32. package/dist/resources/extensions/gsd/detection.js +1 -1
  33. package/dist/resources/extensions/gsd/dev-execution-policy.js +24 -0
  34. package/dist/resources/extensions/gsd/dev-workflow-engine.js +82 -0
  35. package/dist/resources/extensions/gsd/doctor.js +11 -1
  36. package/dist/resources/extensions/gsd/engine-resolver.js +40 -0
  37. package/dist/resources/extensions/gsd/engine-types.js +8 -0
  38. package/dist/resources/extensions/gsd/execution-policy.js +8 -0
  39. package/dist/resources/extensions/gsd/exit-command.js +12 -2
  40. package/dist/resources/extensions/gsd/export.js +9 -13
  41. package/dist/resources/extensions/gsd/extension-manifest.json +2 -2
  42. package/dist/resources/extensions/gsd/files.js +28 -11
  43. package/dist/resources/extensions/gsd/forensics.js +10 -3
  44. package/dist/resources/extensions/gsd/git-service.js +5 -1
  45. package/dist/resources/extensions/gsd/graph.js +225 -0
  46. package/dist/resources/extensions/gsd/gsd-db.js +25 -8
  47. package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
  48. package/dist/resources/extensions/gsd/guided-flow.js +7 -3
  49. package/dist/resources/extensions/gsd/journal.js +85 -0
  50. package/dist/resources/extensions/gsd/md-importer.js +5 -0
  51. package/dist/resources/extensions/gsd/milestone-ids.js +1 -1
  52. package/dist/resources/extensions/gsd/native-git-bridge.js +2 -2
  53. package/dist/resources/extensions/gsd/post-unit-hooks.js +24 -412
  54. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  55. package/dist/resources/extensions/gsd/preferences.js +1 -0
  56. package/dist/resources/extensions/gsd/prompt-loader.js +34 -4
  57. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +11 -10
  58. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
  59. package/dist/resources/extensions/gsd/prompts/discuss.md +1 -1
  60. package/dist/resources/extensions/gsd/prompts/queue.md +1 -1
  61. package/dist/resources/extensions/gsd/repo-identity.js +46 -2
  62. package/dist/resources/extensions/gsd/rule-registry.js +489 -0
  63. package/dist/resources/extensions/gsd/rule-types.js +6 -0
  64. package/dist/resources/extensions/gsd/run-manager.js +134 -0
  65. package/dist/resources/extensions/gsd/service-tier.js +138 -0
  66. package/dist/resources/extensions/gsd/structured-data-formatter.js +2 -1
  67. package/dist/resources/extensions/gsd/templates/decisions.md +2 -2
  68. package/dist/resources/extensions/gsd/workflow-engine.js +7 -0
  69. package/dist/resources/extensions/gsd/workflow-templates.js +13 -1
  70. package/dist/resources/extensions/gsd/worktree-manager.js +20 -6
  71. package/dist/resources/extensions/gsd/worktree-resolver.js +19 -2
  72. package/dist/resources/extensions/subagent/index.js +7 -3
  73. package/dist/resources/extensions/voice/index.js +4 -4
  74. package/dist/resources/skills/create-workflow/SKILL.md +103 -0
  75. package/dist/resources/skills/create-workflow/references/feature-patterns.md +128 -0
  76. package/dist/resources/skills/create-workflow/references/verification-policies.md +76 -0
  77. package/dist/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
  78. package/dist/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
  79. package/dist/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
  80. package/dist/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
  81. package/dist/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
  82. package/dist/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
  83. package/dist/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
  84. package/dist/web/standalone/.next/BUILD_ID +1 -1
  85. package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
  86. package/dist/web/standalone/.next/build-manifest.json +3 -3
  87. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  88. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  89. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  90. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/index.html +1 -1
  121. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  122. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  123. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  124. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  125. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  126. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  127. package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
  128. package/dist/web/standalone/.next/server/chunks/229.js +3 -3
  129. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  132. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  133. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  134. package/dist/web/standalone/.next/static/chunks/4024.c195dc1fdd2adbea.js +9 -0
  135. package/dist/web/standalone/.next/static/chunks/{webpack-9afaaebf6042a1d7.js → webpack-fa307370fcf9fb2c.js} +1 -1
  136. package/dist/web-mode.d.ts +2 -0
  137. package/dist/web-mode.js +29 -7
  138. package/package.json +1 -1
  139. package/packages/native/src/__tests__/text.test.mjs +33 -0
  140. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +3 -1
  141. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -1
  142. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +10 -7
  144. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  145. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  146. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -0
  147. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  148. package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +4 -2
  149. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +11 -7
  150. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +5 -0
  151. package/src/resources/extensions/gsd/auto/loop-deps.ts +5 -1
  152. package/src/resources/extensions/gsd/auto/loop.ts +101 -1
  153. package/src/resources/extensions/gsd/auto/phases.ts +30 -10
  154. package/src/resources/extensions/gsd/auto/session.ts +6 -0
  155. package/src/resources/extensions/gsd/auto/types.ts +4 -0
  156. package/src/resources/extensions/gsd/auto-dashboard.ts +9 -2
  157. package/src/resources/extensions/gsd/auto-dispatch.ts +25 -5
  158. package/src/resources/extensions/gsd/auto-post-unit.ts +8 -0
  159. package/src/resources/extensions/gsd/auto-recovery.ts +12 -4
  160. package/src/resources/extensions/gsd/auto-start.ts +8 -3
  161. package/src/resources/extensions/gsd/auto-worktree.ts +162 -18
  162. package/src/resources/extensions/gsd/auto.ts +71 -2
  163. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +209 -162
  164. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +62 -0
  165. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  166. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +13 -0
  167. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +9 -2
  168. package/src/resources/extensions/gsd/commands/catalog.ts +40 -1
  169. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
  170. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  171. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +164 -0
  172. package/src/resources/extensions/gsd/context-injector.ts +100 -0
  173. package/src/resources/extensions/gsd/context-store.ts +4 -3
  174. package/src/resources/extensions/gsd/custom-execution-policy.ts +73 -0
  175. package/src/resources/extensions/gsd/custom-verification.ts +180 -0
  176. package/src/resources/extensions/gsd/custom-workflow-engine.ts +216 -0
  177. package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -0
  178. package/src/resources/extensions/gsd/db-writer.ts +6 -2
  179. package/src/resources/extensions/gsd/definition-loader.ts +462 -0
  180. package/src/resources/extensions/gsd/detection.ts +1 -1
  181. package/src/resources/extensions/gsd/dev-execution-policy.ts +51 -0
  182. package/src/resources/extensions/gsd/dev-workflow-engine.ts +110 -0
  183. package/src/resources/extensions/gsd/doctor.ts +12 -1
  184. package/src/resources/extensions/gsd/engine-resolver.ts +57 -0
  185. package/src/resources/extensions/gsd/engine-types.ts +71 -0
  186. package/src/resources/extensions/gsd/execution-policy.ts +43 -0
  187. package/src/resources/extensions/gsd/exit-command.ts +14 -2
  188. package/src/resources/extensions/gsd/export.ts +8 -15
  189. package/src/resources/extensions/gsd/extension-manifest.json +2 -2
  190. package/src/resources/extensions/gsd/files.ts +29 -12
  191. package/src/resources/extensions/gsd/forensics.ts +9 -3
  192. package/src/resources/extensions/gsd/git-service.ts +5 -4
  193. package/src/resources/extensions/gsd/graph.ts +312 -0
  194. package/src/resources/extensions/gsd/gsd-db.ts +37 -8
  195. package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
  196. package/src/resources/extensions/gsd/guided-flow.ts +7 -3
  197. package/src/resources/extensions/gsd/journal.ts +134 -0
  198. package/src/resources/extensions/gsd/md-importer.ts +6 -0
  199. package/src/resources/extensions/gsd/milestone-ids.ts +1 -1
  200. package/src/resources/extensions/gsd/native-git-bridge.ts +2 -2
  201. package/src/resources/extensions/gsd/post-unit-hooks.ts +24 -462
  202. package/src/resources/extensions/gsd/preferences-types.ts +3 -0
  203. package/src/resources/extensions/gsd/preferences.ts +1 -0
  204. package/src/resources/extensions/gsd/prompt-loader.ts +35 -4
  205. package/src/resources/extensions/gsd/prompts/complete-milestone.md +11 -10
  206. package/src/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
  207. package/src/resources/extensions/gsd/prompts/discuss.md +1 -1
  208. package/src/resources/extensions/gsd/prompts/queue.md +1 -1
  209. package/src/resources/extensions/gsd/repo-identity.ts +47 -2
  210. package/src/resources/extensions/gsd/rule-registry.ts +599 -0
  211. package/src/resources/extensions/gsd/rule-types.ts +68 -0
  212. package/src/resources/extensions/gsd/run-manager.ts +180 -0
  213. package/src/resources/extensions/gsd/service-tier.ts +171 -0
  214. package/src/resources/extensions/gsd/structured-data-formatter.ts +3 -1
  215. package/src/resources/extensions/gsd/templates/decisions.md +2 -2
  216. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +103 -120
  217. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +85 -0
  218. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -2
  219. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +202 -0
  220. package/src/resources/extensions/gsd/tests/bundled-workflow-defs.test.ts +180 -0
  221. package/src/resources/extensions/gsd/tests/captures.test.ts +12 -1
  222. package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +283 -0
  223. package/src/resources/extensions/gsd/tests/context-injector.test.ts +313 -0
  224. package/src/resources/extensions/gsd/tests/context-store.test.ts +10 -5
  225. package/src/resources/extensions/gsd/tests/continue-here.test.ts +20 -20
  226. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +540 -0
  227. package/src/resources/extensions/gsd/tests/custom-verification.test.ts +382 -0
  228. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +339 -0
  229. package/src/resources/extensions/gsd/tests/dashboard-custom-engine.test.ts +87 -0
  230. package/src/resources/extensions/gsd/tests/db-writer.test.ts +10 -0
  231. package/src/resources/extensions/gsd/tests/definition-loader.test.ts +778 -0
  232. package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +318 -0
  233. package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +15 -10
  234. package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +5 -4
  235. package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +167 -0
  236. package/src/resources/extensions/gsd/tests/doctor-task-done-missing-summary-slice-loop.test.ts +174 -0
  237. package/src/resources/extensions/gsd/tests/e2e-workflow-pipeline-integration.test.ts +476 -0
  238. package/src/resources/extensions/gsd/tests/engine-interfaces-contract.test.ts +271 -0
  239. package/src/resources/extensions/gsd/tests/exit-command.test.ts +55 -0
  240. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +599 -0
  241. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +8 -1
  242. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +7 -7
  243. package/src/resources/extensions/gsd/tests/iterate-engine-integration.test.ts +429 -0
  244. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +513 -0
  245. package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +147 -0
  246. package/src/resources/extensions/gsd/tests/journal.test.ts +386 -0
  247. package/src/resources/extensions/gsd/tests/md-importer.test.ts +31 -1
  248. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  249. package/src/resources/extensions/gsd/tests/milestone-id-reservation.test.ts +1 -1
  250. package/src/resources/extensions/gsd/tests/parsers.test.ts +110 -0
  251. package/src/resources/extensions/gsd/tests/preferences.test.ts +47 -25
  252. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +3 -1
  253. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +61 -1
  254. package/src/resources/extensions/gsd/tests/routing-history.test.ts +11 -22
  255. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +413 -0
  256. package/src/resources/extensions/gsd/tests/run-manager.test.ts +229 -0
  257. package/src/resources/extensions/gsd/tests/service-tier.test.ts +98 -0
  258. package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +2 -2
  259. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +102 -0
  260. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +4 -3
  261. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +45 -0
  262. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +117 -0
  263. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -1
  264. package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +99 -0
  265. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -0
  266. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +4 -0
  267. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +178 -0
  268. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +195 -105
  269. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +78 -3
  270. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +140 -0
  271. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +74 -0
  272. package/src/resources/extensions/gsd/types.ts +3 -0
  273. package/src/resources/extensions/gsd/workflow-engine.ts +38 -0
  274. package/src/resources/extensions/gsd/workflow-templates.ts +12 -1
  275. package/src/resources/extensions/gsd/worktree-manager.ts +21 -6
  276. package/src/resources/extensions/gsd/worktree-resolver.ts +30 -9
  277. package/src/resources/extensions/subagent/index.ts +7 -3
  278. package/src/resources/extensions/voice/index.ts +4 -4
  279. package/src/resources/skills/create-workflow/SKILL.md +103 -0
  280. package/src/resources/skills/create-workflow/references/feature-patterns.md +128 -0
  281. package/src/resources/skills/create-workflow/references/verification-policies.md +76 -0
  282. package/src/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
  283. package/src/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
  284. package/src/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
  285. package/src/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
  286. package/src/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
  287. package/src/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
  288. package/src/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
  289. package/dist/web/standalone/.next/static/chunks/4024.279c423e4661ece1.js +0 -9
  290. /package/dist/web/standalone/.next/static/{SwbKZ7JPNFlEmU4f8pKEv → K7GYOOPvQWX6TKYEKhODM}/_buildManifest.js +0 -0
  291. /package/dist/web/standalone/.next/static/{SwbKZ7JPNFlEmU4f8pKEv → K7GYOOPvQWX6TKYEKhODM}/_ssgManifest.js +0 -0
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync, unlinkSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import { parse as parseYaml } from "yaml";
3
4
  import { handleQuick } from "../../quick.js";
4
5
  import { showDiscuss, showHeadlessMilestoneCreation, showQueue } from "../../guided-flow.js";
5
6
  import { handleStart, handleTemplates } from "../../commands-workflow-templates.js";
@@ -10,7 +11,152 @@ import { loadEffectiveGSDPreferences } from "../../preferences.js";
10
11
  import { nextMilestoneId } from "../../milestone-ids.js";
11
12
  import { findMilestoneIds } from "../../guided-flow.js";
12
13
  import { projectRoot } from "../context.js";
14
+ import { createRun, listRuns } from "../../run-manager.js";
15
+ import { setActiveEngineId, setActiveRunDir, startAuto, pauseAuto, isAutoActive, getActiveEngineId, } from "../../auto.js";
16
+ import { validateDefinition } from "../../definition-loader.js";
17
+ // ─── Custom Workflow Subcommands ─────────────────────────────────────────
18
+ const WORKFLOW_USAGE = [
19
+ "Usage: /gsd workflow <subcommand>",
20
+ "",
21
+ " new — Create a new workflow definition (via skill)",
22
+ " run <name> [k=v] — Create a run and start auto-mode",
23
+ " list [name] — List workflow runs (optionally filtered by name)",
24
+ " validate <name> — Validate a workflow definition YAML",
25
+ " pause — Pause custom workflow auto-mode",
26
+ " resume — Resume paused custom workflow auto-mode",
27
+ ].join("\n");
28
+ async function handleCustomWorkflow(sub, ctx, pi) {
29
+ // Bare `/gsd workflow` — show usage
30
+ if (!sub) {
31
+ ctx.ui.notify(WORKFLOW_USAGE, "info");
32
+ return true;
33
+ }
34
+ // ── new ──
35
+ if (sub === "new") {
36
+ ctx.ui.notify("Use the create-workflow skill: /skill create-workflow", "info");
37
+ return true;
38
+ }
39
+ // ── run <name> [param=value ...] ──
40
+ if (sub === "run" || sub.startsWith("run ")) {
41
+ const args = sub.slice("run".length).trim();
42
+ if (!args) {
43
+ ctx.ui.notify("Usage: /gsd workflow run <name> [param=value ...]", "warning");
44
+ return true;
45
+ }
46
+ const parts = args.split(/\s+/);
47
+ const defName = parts[0];
48
+ const overrides = {};
49
+ for (let i = 1; i < parts.length; i++) {
50
+ const eqIdx = parts[i].indexOf("=");
51
+ if (eqIdx > 0) {
52
+ overrides[parts[i].slice(0, eqIdx)] = parts[i].slice(eqIdx + 1);
53
+ }
54
+ }
55
+ try {
56
+ const base = projectRoot();
57
+ const runDir = createRun(base, defName, Object.keys(overrides).length > 0 ? overrides : undefined);
58
+ setActiveEngineId("custom");
59
+ setActiveRunDir(runDir);
60
+ ctx.ui.notify(`Created workflow run: ${defName}\nRun dir: ${runDir}`, "info");
61
+ await startAuto(ctx, pi, base, false);
62
+ }
63
+ catch (err) {
64
+ // Clean up engine state so a failed workflow run doesn't pollute the next /gsd auto
65
+ setActiveEngineId(null);
66
+ setActiveRunDir(null);
67
+ const msg = err instanceof Error ? err.message : String(err);
68
+ ctx.ui.notify(`Failed to run workflow "${defName}": ${msg}`, "error");
69
+ }
70
+ return true;
71
+ }
72
+ // ── list [name] ──
73
+ if (sub === "list" || sub.startsWith("list ")) {
74
+ const filterName = sub.slice("list".length).trim() || undefined;
75
+ const base = projectRoot();
76
+ const runs = listRuns(base, filterName);
77
+ if (runs.length === 0) {
78
+ ctx.ui.notify("No workflow runs found.", "info");
79
+ return true;
80
+ }
81
+ const lines = runs.map((r) => {
82
+ const stepInfo = `${r.steps.completed}/${r.steps.total} steps`;
83
+ return `• ${r.name} [${r.timestamp}] — ${r.status} (${stepInfo})`;
84
+ });
85
+ ctx.ui.notify(lines.join("\n"), "info");
86
+ return true;
87
+ }
88
+ // ── validate <name> ──
89
+ if (sub === "validate" || sub.startsWith("validate ")) {
90
+ const defName = sub.slice("validate".length).trim();
91
+ if (!defName) {
92
+ ctx.ui.notify("Usage: /gsd workflow validate <name>", "warning");
93
+ return true;
94
+ }
95
+ const base = projectRoot();
96
+ const defPath = join(base, ".gsd", "workflow-defs", `${defName}.yaml`);
97
+ if (!existsSync(defPath)) {
98
+ ctx.ui.notify(`Definition not found: ${defPath}`, "error");
99
+ return true;
100
+ }
101
+ try {
102
+ const raw = readFileSync(defPath, "utf-8");
103
+ const parsed = parseYaml(raw);
104
+ const result = validateDefinition(parsed);
105
+ if (result.valid) {
106
+ ctx.ui.notify(`✓ "${defName}" is a valid workflow definition.`, "info");
107
+ }
108
+ else {
109
+ ctx.ui.notify(`✗ "${defName}" has errors:\n - ${result.errors.join("\n - ")}`, "error");
110
+ }
111
+ }
112
+ catch (err) {
113
+ const msg = err instanceof Error ? err.message : String(err);
114
+ ctx.ui.notify(`Failed to validate "${defName}": ${msg}`, "error");
115
+ }
116
+ return true;
117
+ }
118
+ // ── pause ──
119
+ if (sub === "pause") {
120
+ const engineId = getActiveEngineId();
121
+ if (engineId === "dev" || engineId === null) {
122
+ ctx.ui.notify("No custom workflow is running. Use /gsd pause for dev workflow.", "warning");
123
+ return true;
124
+ }
125
+ if (!isAutoActive()) {
126
+ ctx.ui.notify("Auto-mode is not active.", "warning");
127
+ return true;
128
+ }
129
+ await pauseAuto(ctx, pi);
130
+ ctx.ui.notify("Custom workflow paused.", "info");
131
+ return true;
132
+ }
133
+ // ── resume ──
134
+ if (sub === "resume") {
135
+ const engineId = getActiveEngineId();
136
+ if (engineId === "dev" || engineId === null) {
137
+ ctx.ui.notify("No custom workflow to resume. Use /gsd auto for dev workflow.", "warning");
138
+ return true;
139
+ }
140
+ try {
141
+ await startAuto(ctx, pi, projectRoot(), false);
142
+ ctx.ui.notify("Custom workflow resumed.", "info");
143
+ }
144
+ catch (err) {
145
+ const msg = err instanceof Error ? err.message : String(err);
146
+ ctx.ui.notify(`Failed to resume workflow: ${msg}`, "error");
147
+ }
148
+ return true;
149
+ }
150
+ // Unknown subcommand — show usage
151
+ ctx.ui.notify(`Unknown workflow subcommand: "${sub}"\n\n${WORKFLOW_USAGE}`, "warning");
152
+ return true;
153
+ }
13
154
  export async function handleWorkflowCommand(trimmed, ctx, pi) {
155
+ // ── Custom workflow commands (`/gsd workflow ...`) ──
156
+ if (trimmed === "workflow" || trimmed.startsWith("workflow ")) {
157
+ const sub = trimmed.slice("workflow".length).trim();
158
+ return handleCustomWorkflow(sub, ctx, pi);
159
+ }
14
160
  if (trimmed === "queue") {
15
161
  await showQueue(ctx, pi, projectRoot());
16
162
  return true;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * context-injector.ts — Inject prior step artifacts as context into step prompts.
3
+ *
4
+ * Reads the frozen DEFINITION.yaml from a run directory, finds the current step's
5
+ * `contextFrom` references, locates each referenced step's `produces` artifacts
6
+ * on disk, reads their content (truncated to 10k chars), and prepends formatted
7
+ * context blocks to the step prompt.
8
+ *
9
+ * Observability:
10
+ * - Truncation is logged via console.warn when it occurs, preventing silent overflow.
11
+ * - Missing artifact files are skipped silently (the step may not have produced them yet).
12
+ * - Unknown step IDs in contextFrom produce a console.warn for diagnosis.
13
+ * - The frozen DEFINITION.yaml on disk is the single source of truth for contextFrom config.
14
+ */
15
+ import { readFileSync, existsSync } from "node:fs";
16
+ import { resolve, sep } from "node:path";
17
+ import { readFrozenDefinition } from "./custom-workflow-engine.js";
18
+ /** Maximum characters per artifact to prevent context window blowout. */
19
+ const MAX_CONTEXT_CHARS = 10_000;
20
+ /**
21
+ * Inject context from prior step artifacts into a step's prompt.
22
+ *
23
+ * Reads the frozen DEFINITION.yaml from `runDir`, finds the step matching
24
+ * `stepId`, and for each step ID in its `contextFrom` array, looks up that
25
+ * step's `produces` paths, reads them from disk (relative to `runDir`),
26
+ * truncates to MAX_CONTEXT_CHARS, and prepends as labeled context blocks.
27
+ *
28
+ * @param runDir — absolute path to the workflow run directory
29
+ * @param stepId — the step ID whose prompt to enrich
30
+ * @param prompt — the original step prompt
31
+ * @returns The prompt with context blocks prepended, or unchanged if no context applies
32
+ * @throws Error if DEFINITION.yaml is missing or unreadable
33
+ */
34
+ export function injectContext(runDir, stepId, prompt) {
35
+ const def = readFrozenDefinition(runDir);
36
+ const step = def.steps.find((s) => s.id === stepId);
37
+ if (!step || !step.contextFrom || step.contextFrom.length === 0) {
38
+ return prompt;
39
+ }
40
+ const contextBlocks = [];
41
+ for (const refStepId of step.contextFrom) {
42
+ const refStep = def.steps.find((s) => s.id === refStepId);
43
+ if (!refStep) {
44
+ console.warn(`context-injector: step "${stepId}" references unknown step "${refStepId}" in contextFrom — skipping`);
45
+ continue;
46
+ }
47
+ if (!refStep.produces || refStep.produces.length === 0) {
48
+ continue;
49
+ }
50
+ for (const relPath of refStep.produces) {
51
+ const absPath = resolve(runDir, relPath);
52
+ // Path traversal guard: ensure resolved path stays within runDir
53
+ if (!absPath.startsWith(resolve(runDir) + sep) && absPath !== resolve(runDir)) {
54
+ console.warn(`context-injector: artifact path "${relPath}" resolves outside runDir — skipping`);
55
+ continue;
56
+ }
57
+ if (!existsSync(absPath)) {
58
+ // Artifact not yet produced or optional — skip silently
59
+ continue;
60
+ }
61
+ let content = readFileSync(absPath, "utf-8");
62
+ if (content.length > MAX_CONTEXT_CHARS) {
63
+ console.warn(`context-injector: truncating artifact "${relPath}" from step "${refStepId}" ` +
64
+ `(${content.length} chars → ${MAX_CONTEXT_CHARS} chars)`);
65
+ content = content.slice(0, MAX_CONTEXT_CHARS) + "\n...[truncated]";
66
+ }
67
+ contextBlocks.push(`--- Context from step "${refStepId}" (file: ${relPath}) ---\n${content}\n---`);
68
+ }
69
+ }
70
+ if (contextBlocks.length === 0) {
71
+ return prompt;
72
+ }
73
+ return contextBlocks.join("\n\n") + "\n\n" + prompt;
74
+ }
@@ -39,6 +39,7 @@ export function queryDecisions(opts) {
39
39
  choice: row['choice'],
40
40
  rationale: row['rationale'],
41
41
  revisable: row['revisable'],
42
+ made_by: row['made_by'] ?? 'agent',
42
43
  superseded_by: null,
43
44
  }));
44
45
  }
@@ -99,9 +100,9 @@ export function queryRequirements(opts) {
99
100
  export function formatDecisionsForPrompt(decisions) {
100
101
  if (decisions.length === 0)
101
102
  return '';
102
- const header = '| # | When | Scope | Decision | Choice | Rationale | Revisable? |';
103
- const separator = '|---|------|-------|----------|--------|-----------|------------|';
104
- const rows = decisions.map(d => `| ${d.id} | ${d.when_context} | ${d.scope} | ${d.decision} | ${d.choice} | ${d.rationale} | ${d.revisable} |`);
103
+ const header = '| # | When | Scope | Decision | Choice | Rationale | Revisable? | Made By |';
104
+ const separator = '|---|------|-------|----------|--------|-----------|------------|---------|';
105
+ const rows = decisions.map(d => `| ${d.id} | ${d.when_context} | ${d.scope} | ${d.decision} | ${d.choice} | ${d.rationale} | ${d.revisable} | ${d.made_by ?? 'agent'} |`);
105
106
  return [header, separator, ...rows].join('\n');
106
107
  }
107
108
  /**
@@ -0,0 +1,47 @@
1
+ /**
2
+ * custom-execution-policy.ts — ExecutionPolicy for custom workflows.
3
+ *
4
+ * Delegates verification to the step-level verification module which reads
5
+ * the frozen DEFINITION.yaml and dispatches to the appropriate policy handler.
6
+ *
7
+ * Observability:
8
+ * - verify() returns the outcome from runCustomVerification() — four policies
9
+ * are supported: content-heuristic, shell-command, prompt-verify, human-review.
10
+ * - selectModel() returns null — defers to loop defaults.
11
+ * - recover() returns retry — simple default recovery strategy.
12
+ */
13
+ import { runCustomVerification } from "./custom-verification.js";
14
+ export class CustomExecutionPolicy {
15
+ runDir;
16
+ constructor(runDir) {
17
+ this.runDir = runDir;
18
+ }
19
+ /** No workspace preparation needed for custom workflows. */
20
+ async prepareWorkspace(_basePath, _milestoneId) {
21
+ // No-op — custom workflows don't need worktree setup
22
+ }
23
+ /** Defer model selection to loop defaults. */
24
+ async selectModel(_unitType, _unitId, _context) {
25
+ return null;
26
+ }
27
+ /**
28
+ * Verify step output by dispatching to the step's configured verification policy.
29
+ *
30
+ * Extracts the step ID from unitId (format: "<workflowName>/<stepId>")
31
+ * and calls runCustomVerification() which reads the frozen DEFINITION.yaml
32
+ * to determine which policy to apply.
33
+ */
34
+ async verify(_unitType, unitId, _context) {
35
+ const parts = unitId.split("/");
36
+ const stepId = parts[parts.length - 1];
37
+ return runCustomVerification(this.runDir, stepId);
38
+ }
39
+ /** Default recovery: retry the step. */
40
+ async recover(_unitType, _unitId, _context) {
41
+ return { outcome: "retry", reason: "Default retry" };
42
+ }
43
+ /** No-op closeout — no commits or artifact capture. */
44
+ async closeout(_unitType, _unitId, _context) {
45
+ return { committed: false, artifacts: [] };
46
+ }
47
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * custom-verification.ts — Step verification for custom workflows.
3
+ *
4
+ * Reads the frozen DEFINITION.yaml from a run directory, finds the step's
5
+ * `verify` policy, and dispatches to the appropriate handler. Four policies:
6
+ *
7
+ * - content-heuristic: file existence + optional minSize + optional pattern match
8
+ * - shell-command: spawnSync with 30s timeout, exit 0 → continue, else retry
9
+ * - prompt-verify: always "pause" (defers to agent)
10
+ * - human-review: always "pause" (waits for manual inspection)
11
+ * - (no policy): returns "continue" (passthrough)
12
+ *
13
+ * Observability:
14
+ * - Return value is the typed verification outcome ("continue" | "retry" | "pause").
15
+ * - shell-command captures stderr from spawnSync — callers can inspect on retry.
16
+ * - content-heuristic logs the specific failure (missing file, below minSize, pattern mismatch).
17
+ * - The frozen DEFINITION.yaml on disk is the single source of truth for step policies.
18
+ */
19
+ import { readFileSync, existsSync, statSync } from "node:fs";
20
+ import { resolve, sep } from "node:path";
21
+ import { spawnSync } from "node:child_process";
22
+ import { readFrozenDefinition } from "./custom-workflow-engine.js";
23
+ /**
24
+ * Run custom verification for a specific step in a workflow run.
25
+ *
26
+ * Reads the frozen DEFINITION.yaml from `runDir`, finds the step with the
27
+ * given `stepId`, and dispatches to the appropriate verification handler
28
+ * based on the step's `verify.policy` field.
29
+ *
30
+ * @param runDir — absolute path to the workflow run directory
31
+ * @param stepId — the step ID to verify (e.g. "step-1")
32
+ * @returns "continue" if verification passes, "retry" if it should retry, "pause" if it needs review
33
+ * @throws Error if DEFINITION.yaml is missing or unreadable
34
+ */
35
+ export function runCustomVerification(runDir, stepId) {
36
+ const def = readFrozenDefinition(runDir);
37
+ const step = def.steps.find((s) => s.id === stepId);
38
+ if (!step) {
39
+ // Step not found in definition — nothing to verify, continue
40
+ return "continue";
41
+ }
42
+ if (!step.verify) {
43
+ // No verification policy configured — passthrough
44
+ return "continue";
45
+ }
46
+ return dispatchPolicy(runDir, step, step.verify);
47
+ }
48
+ /**
49
+ * Dispatch to the correct policy handler.
50
+ */
51
+ function dispatchPolicy(runDir, step, verify) {
52
+ switch (verify.policy) {
53
+ case "content-heuristic":
54
+ return handleContentHeuristic(runDir, step, verify);
55
+ case "shell-command":
56
+ return handleShellCommand(runDir, verify);
57
+ case "prompt-verify":
58
+ return "pause";
59
+ case "human-review":
60
+ return "pause";
61
+ default:
62
+ // Unknown policy — safe default is pause
63
+ return "pause";
64
+ }
65
+ }
66
+ /**
67
+ * content-heuristic handler.
68
+ *
69
+ * For each path in the step's `produces` array:
70
+ * 1. Check that the file exists (resolved relative to runDir)
71
+ * 2. If `minSize` is set, check that file size >= minSize bytes
72
+ * 3. If `pattern` is set, check that file content matches the regex
73
+ *
74
+ * Returns "continue" if all checks pass, "pause" if any fail.
75
+ * If `produces` is empty or undefined, returns "continue" (nothing to check).
76
+ */
77
+ function handleContentHeuristic(runDir, step, verify) {
78
+ const produces = step.produces;
79
+ if (!produces || produces.length === 0) {
80
+ return "continue";
81
+ }
82
+ for (const relPath of produces) {
83
+ const absPath = resolve(runDir, relPath);
84
+ // Path traversal guard
85
+ if (!absPath.startsWith(resolve(runDir) + sep) && absPath !== resolve(runDir)) {
86
+ return "pause";
87
+ }
88
+ // 1. File existence
89
+ if (!existsSync(absPath)) {
90
+ return "pause";
91
+ }
92
+ // 2. Minimum size check
93
+ if (verify.minSize !== undefined) {
94
+ const stat = statSync(absPath);
95
+ if (stat.size < verify.minSize) {
96
+ return "pause";
97
+ }
98
+ }
99
+ // 3. Pattern match check (with timeout guard against ReDoS)
100
+ if (verify.pattern !== undefined) {
101
+ const content = readFileSync(absPath, "utf-8");
102
+ try {
103
+ if (!new RegExp(verify.pattern).test(content)) {
104
+ return "pause";
105
+ }
106
+ }
107
+ catch {
108
+ // Invalid regex at runtime — treat as verification failure
109
+ return "pause";
110
+ }
111
+ }
112
+ }
113
+ return "continue";
114
+ }
115
+ /**
116
+ * shell-command handler.
117
+ *
118
+ * Runs the command via `sh -c` with cwd set to the run directory
119
+ * and a 30-second timeout. Returns "continue" if exit code 0,
120
+ * "retry" otherwise (including timeout/signal kills).
121
+ *
122
+ * SECURITY: The command string comes from a frozen DEFINITION.yaml written
123
+ * at run-creation time. The trust boundary is the workflow definition author.
124
+ * Commands run with the same privileges as the GSD process. Only use
125
+ * shell-command verification with definitions you trust.
126
+ */
127
+ function handleShellCommand(runDir, verify) {
128
+ // Guard: reject commands containing shell expansion patterns that suggest injection
129
+ const dangerousPatterns = /\$\(|`|;\s*(rm|curl|wget|nc|bash|sh|eval)\b/;
130
+ if (dangerousPatterns.test(verify.command)) {
131
+ console.warn(`custom-verification: shell-command contains suspicious pattern, skipping: ${verify.command}`);
132
+ return "pause";
133
+ }
134
+ const result = spawnSync("sh", ["-c", verify.command], {
135
+ cwd: runDir,
136
+ timeout: 30_000,
137
+ encoding: "utf-8",
138
+ stdio: "pipe",
139
+ env: { ...process.env, PATH: process.env.PATH },
140
+ });
141
+ if (result.status === 0) {
142
+ return "continue";
143
+ }
144
+ return "retry";
145
+ }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * custom-workflow-engine.ts — WorkflowEngine implementation for custom workflows.
3
+ *
4
+ * Drives the auto-loop using GRAPH.yaml step state from a run directory.
5
+ * Each iteration: deriveState reads the graph, resolveDispatch picks the
6
+ * next eligible step, reconcile marks it complete and persists.
7
+ *
8
+ * Observability:
9
+ * - All state reads/writes go through graph.ts YAML I/O — inspectable on disk.
10
+ * - `resolveDispatch` returns unitType "custom-step" with unitId "<name>/<stepId>".
11
+ * - `getDisplayMetadata` provides step N/M progress for dashboard rendering.
12
+ * - Phase transitions are derivable from GRAPH.yaml step statuses.
13
+ */
14
+ import { readFileSync } from "node:fs";
15
+ import { join } from "node:path";
16
+ import { parse } from "yaml";
17
+ import { readGraph, writeGraph, getNextPendingStep, markStepComplete, expandIteration, } from "./graph.js";
18
+ import { injectContext } from "./context-injector.js";
19
+ /** Read and parse the frozen DEFINITION.yaml from a run directory. */
20
+ export function readFrozenDefinition(runDir) {
21
+ const defPath = join(runDir, "DEFINITION.yaml");
22
+ const raw = readFileSync(defPath, "utf-8");
23
+ return parse(raw, { schema: "core" });
24
+ }
25
+ export class CustomWorkflowEngine {
26
+ engineId = "custom";
27
+ runDir;
28
+ constructor(runDir) {
29
+ this.runDir = runDir;
30
+ }
31
+ /**
32
+ * Derive engine state from GRAPH.yaml on disk.
33
+ *
34
+ * Phase is "complete" when all steps are complete or expanded,
35
+ * "running" otherwise (any pending or active steps remain).
36
+ */
37
+ async deriveState(_basePath) {
38
+ const graph = readGraph(this.runDir);
39
+ const allDone = graph.steps.every((s) => s.status === "complete" || s.status === "expanded");
40
+ const phase = allDone ? "complete" : "running";
41
+ return {
42
+ phase,
43
+ currentMilestoneId: null,
44
+ activeSliceId: null,
45
+ activeTaskId: null,
46
+ isComplete: allDone,
47
+ raw: graph,
48
+ };
49
+ }
50
+ /**
51
+ * Resolve the next dispatch action from graph state.
52
+ *
53
+ * Uses getNextPendingStep to find the first step whose dependencies
54
+ * are all satisfied. If the step has an `iterate` config in the frozen
55
+ * DEFINITION.yaml, expands it into instance steps before dispatching.
56
+ *
57
+ * Returns a dispatch with unitType "custom-step" and unitId in
58
+ * "<workflowName>/<stepId>" format.
59
+ *
60
+ * Observability:
61
+ * - Iterate expansion is logged to stderr with item count and parent step ID.
62
+ * - Missing source artifacts throw with the full resolved path for diagnosis.
63
+ * - Zero-match expansions return a stop action with level "info".
64
+ * - Expanded GRAPH.yaml is written to disk before dispatch — inspectable on disk.
65
+ */
66
+ async resolveDispatch(state, _context) {
67
+ let graph = state.raw;
68
+ let next = getNextPendingStep(graph);
69
+ if (!next) {
70
+ return {
71
+ action: "stop",
72
+ reason: "All steps complete",
73
+ level: "info",
74
+ };
75
+ }
76
+ // Check frozen DEFINITION.yaml for iterate config on this step
77
+ const def = readFrozenDefinition(this.runDir);
78
+ const stepDef = def.steps.find((s) => s.id === next.id);
79
+ if (stepDef?.iterate) {
80
+ const iterate = stepDef.iterate;
81
+ // Read source artifact
82
+ const sourcePath = join(this.runDir, iterate.source);
83
+ let sourceContent;
84
+ try {
85
+ sourceContent = readFileSync(sourcePath, "utf-8");
86
+ }
87
+ catch {
88
+ throw new Error(`Iterate source artifact not found: ${sourcePath} (step "${next.id}", source: "${iterate.source}")`);
89
+ }
90
+ // Extract items via regex with global+multiline flags.
91
+ // Guard against ReDoS: if matching takes too long on large inputs, bail.
92
+ const regex = new RegExp(iterate.pattern, "gm");
93
+ const items = [];
94
+ const matchStart = Date.now();
95
+ let match;
96
+ while ((match = regex.exec(sourceContent)) !== null) {
97
+ if (match[1] !== undefined)
98
+ items.push(match[1]);
99
+ if (Date.now() - matchStart > 5_000) {
100
+ throw new Error(`Iterate pattern "${iterate.pattern}" exceeded 5s timeout on step "${next.id}" — possible ReDoS`);
101
+ }
102
+ }
103
+ // Expand the graph
104
+ const expandedGraph = expandIteration(graph, next.id, items, next.prompt);
105
+ writeGraph(this.runDir, expandedGraph);
106
+ graph = expandedGraph;
107
+ // Re-query for first instance step
108
+ next = getNextPendingStep(expandedGraph);
109
+ if (!next) {
110
+ return {
111
+ action: "stop",
112
+ reason: "Iterate expansion produced no instances",
113
+ level: "info",
114
+ };
115
+ }
116
+ }
117
+ // Enrich prompt with context from prior step artifacts
118
+ const enrichedPrompt = injectContext(this.runDir, next.id, next.prompt);
119
+ return {
120
+ action: "dispatch",
121
+ step: {
122
+ unitType: "custom-step",
123
+ unitId: `${graph.metadata.name}/${next.id}`,
124
+ prompt: enrichedPrompt,
125
+ },
126
+ };
127
+ }
128
+ /**
129
+ * Reconcile state after a step completes.
130
+ *
131
+ * Extracts the stepId from the completedStep's unitId (last segment after `/`),
132
+ * marks it complete in the graph, and writes the updated GRAPH.yaml to disk.
133
+ *
134
+ * Returns "milestone-complete" when all steps are now done, "continue" otherwise.
135
+ */
136
+ async reconcile(state, completedStep) {
137
+ const graph = state.raw;
138
+ // Extract stepId from "<workflowName>/<stepId>"
139
+ const parts = completedStep.unitId.split("/");
140
+ const stepId = parts[parts.length - 1];
141
+ const updatedGraph = markStepComplete(graph, stepId);
142
+ writeGraph(this.runDir, updatedGraph);
143
+ const allDone = updatedGraph.steps.every((s) => s.status === "complete" || s.status === "expanded");
144
+ return {
145
+ outcome: allDone ? "milestone-complete" : "continue",
146
+ };
147
+ }
148
+ /**
149
+ * Return UI-facing metadata for progress display.
150
+ *
151
+ * Shows "Step N/M" progress where N = completed count and M = total.
152
+ */
153
+ getDisplayMetadata(state) {
154
+ const graph = state.raw;
155
+ const total = graph.steps.length;
156
+ const completed = graph.steps.filter((s) => s.status === "complete").length;
157
+ return {
158
+ engineLabel: "WORKFLOW",
159
+ currentPhase: state.phase,
160
+ progressSummary: `Step ${completed}/${total}`,
161
+ stepCount: { completed, total },
162
+ };
163
+ }
164
+ }
@@ -30,6 +30,7 @@ function unitLabel(type) {
30
30
  case "triage-captures": return "Triage";
31
31
  case "quick-task": return "Quick Task";
32
32
  case "replan-slice": return "Replan";
33
+ case "custom-step": return "Workflow Step";
33
34
  default: return type;
34
35
  }
35
36
  }
@@ -30,8 +30,8 @@ export function generateDecisionsMd(decisions) {
30
30
  lines.push(' To reverse a decision, add a new row that supersedes it.');
31
31
  lines.push(' Read this file at the start of any planning or research phase. -->');
32
32
  lines.push('');
33
- lines.push('| # | When | Scope | Decision | Choice | Rationale | Revisable? |');
34
- lines.push('|---|------|-------|----------|--------|-----------|------------|');
33
+ lines.push('| # | When | Scope | Decision | Choice | Rationale | Revisable? | Made By |');
34
+ lines.push('|---|------|-------|----------|--------|-----------|------------|---------|');
35
35
  for (const d of decisions) {
36
36
  // Escape pipe characters within cell values to preserve table structure
37
37
  const cells = [
@@ -42,6 +42,7 @@ export function generateDecisionsMd(decisions) {
42
42
  d.choice,
43
43
  d.rationale,
44
44
  d.revisable,
45
+ d.made_by ?? 'agent',
45
46
  ].map(cell => (cell ?? '').replace(/\|/g, '\\|'));
46
47
  lines.push(`| ${cells.join(' | ')} |`);
47
48
  }
@@ -172,6 +173,7 @@ export async function saveDecisionToDb(fields, basePath) {
172
173
  choice: fields.choice,
173
174
  rationale: fields.rationale,
174
175
  revisable: fields.revisable ?? 'Yes',
176
+ made_by: fields.made_by ?? 'agent',
175
177
  superseded_by: null,
176
178
  });
177
179
  // Fetch all decisions (including superseded for the full register)
@@ -188,6 +190,7 @@ export async function saveDecisionToDb(fields, basePath) {
188
190
  choice: row['choice'],
189
191
  rationale: row['rationale'],
190
192
  revisable: row['revisable'],
193
+ made_by: row['made_by'] ?? 'agent',
191
194
  superseded_by: row['superseded_by'] ?? null,
192
195
  }));
193
196
  }