gsd-pi 2.41.0-dev.cac69f9 → 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 (263) hide show
  1. package/README.md +23 -0
  2. package/dist/cli.js +18 -3
  3. package/dist/loader.js +3 -1
  4. package/dist/resource-loader.js +39 -6
  5. package/dist/resources/extensions/async-jobs/async-bash-tool.js +52 -4
  6. package/dist/resources/extensions/async-jobs/await-tool.js +5 -0
  7. package/dist/resources/extensions/async-jobs/index.js +2 -0
  8. package/dist/resources/extensions/gsd/auto/loop.js +80 -0
  9. package/dist/resources/extensions/gsd/auto/phases.js +3 -5
  10. package/dist/resources/extensions/gsd/auto/session.js +6 -0
  11. package/dist/resources/extensions/gsd/auto-dashboard.js +2 -0
  12. package/dist/resources/extensions/gsd/auto-prompts.js +3 -16
  13. package/dist/resources/extensions/gsd/auto-start.js +8 -11
  14. package/dist/resources/extensions/gsd/auto.js +28 -1
  15. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -5
  16. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +7 -2
  17. package/dist/resources/extensions/gsd/commands/catalog.js +32 -0
  18. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +146 -0
  19. package/dist/resources/extensions/gsd/context-injector.js +74 -0
  20. package/dist/resources/extensions/gsd/custom-execution-policy.js +47 -0
  21. package/dist/resources/extensions/gsd/custom-verification.js +145 -0
  22. package/dist/resources/extensions/gsd/custom-workflow-engine.js +164 -0
  23. package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -0
  24. package/dist/resources/extensions/gsd/definition-loader.js +352 -0
  25. package/dist/resources/extensions/gsd/detection.js +19 -0
  26. package/dist/resources/extensions/gsd/dev-execution-policy.js +24 -0
  27. package/dist/resources/extensions/gsd/dev-workflow-engine.js +82 -0
  28. package/dist/resources/extensions/gsd/doctor-checks.js +31 -1
  29. package/dist/resources/extensions/gsd/doctor-providers.js +10 -0
  30. package/dist/resources/extensions/gsd/engine-resolver.js +40 -0
  31. package/dist/resources/extensions/gsd/engine-types.js +8 -0
  32. package/dist/resources/extensions/gsd/execution-policy.js +8 -0
  33. package/dist/resources/extensions/gsd/forensics.js +84 -0
  34. package/dist/resources/extensions/gsd/git-constants.js +1 -0
  35. package/dist/resources/extensions/gsd/git-service.js +1 -1
  36. package/dist/resources/extensions/gsd/graph.js +225 -0
  37. package/dist/resources/extensions/gsd/native-git-bridge.js +1 -0
  38. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  39. package/dist/resources/extensions/gsd/preferences.js +59 -8
  40. package/dist/resources/extensions/gsd/prompts/forensics.md +12 -5
  41. package/dist/resources/extensions/gsd/repo-identity.js +46 -5
  42. package/dist/resources/extensions/gsd/run-manager.js +134 -0
  43. package/dist/resources/extensions/gsd/service-tier.js +13 -4
  44. package/dist/resources/extensions/gsd/session-lock.js +2 -2
  45. package/dist/resources/extensions/gsd/workflow-engine.js +7 -0
  46. package/dist/resources/extensions/gsd/worktree-resolver.js +2 -2
  47. package/dist/resources/extensions/gsd/worktree.js +2 -2
  48. package/dist/resources/extensions/mcp-client/index.js +2 -1
  49. package/dist/resources/extensions/search-the-web/tool-search.js +3 -3
  50. package/dist/resources/skills/create-workflow/SKILL.md +103 -0
  51. package/dist/resources/skills/create-workflow/references/feature-patterns.md +128 -0
  52. package/dist/resources/skills/create-workflow/references/verification-policies.md +76 -0
  53. package/dist/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
  54. package/dist/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
  55. package/dist/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
  56. package/dist/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
  57. package/dist/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
  58. package/dist/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
  59. package/dist/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
  60. package/dist/web/standalone/.next/BUILD_ID +1 -1
  61. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  62. package/dist/web/standalone/.next/build-manifest.json +2 -2
  63. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  64. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  65. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/index.html +1 -1
  82. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  89. package/dist/web/standalone/.next/server/chunks/229.js +2 -2
  90. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  91. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  92. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  93. package/dist/web-mode.d.ts +2 -0
  94. package/dist/web-mode.js +40 -4
  95. package/package.json +1 -1
  96. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  97. package/packages/pi-agent-core/dist/agent.js +2 -0
  98. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  99. package/packages/pi-agent-core/dist/types.d.ts +6 -0
  100. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  101. package/packages/pi-agent-core/dist/types.js.map +1 -1
  102. package/packages/pi-agent-core/src/agent.test.ts +53 -0
  103. package/packages/pi-agent-core/src/agent.ts +3 -0
  104. package/packages/pi-agent-core/src/types.ts +6 -0
  105. package/packages/pi-agent-core/tsconfig.json +1 -1
  106. package/packages/pi-ai/dist/models.d.ts +5 -3
  107. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  108. package/packages/pi-ai/dist/models.generated.d.ts +801 -1468
  109. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  110. package/packages/pi-ai/dist/models.generated.js +1135 -1588
  111. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  112. package/packages/pi-ai/dist/models.js.map +1 -1
  113. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  114. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +60 -2
  115. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  116. package/packages/pi-ai/scripts/generate-models.ts +1543 -0
  117. package/packages/pi-ai/src/models.generated.ts +1140 -1593
  118. package/packages/pi-ai/src/models.ts +7 -4
  119. package/packages/pi-ai/src/utils/oauth/github-copilot.ts +74 -2
  120. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/agent-session.js +8 -1
  122. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +7 -0
  124. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/auth-storage.js +29 -2
  126. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +60 -0
  128. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/extensions/loader.js +18 -0
  131. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/core/lsp/client.js +23 -0
  134. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/model-registry.js +2 -0
  137. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/package-manager.d.ts +6 -0
  139. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/core/package-manager.js +63 -11
  141. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  142. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +9 -0
  143. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  144. package/packages/pi-coding-agent/dist/core/resource-loader.js +20 -6
  145. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  146. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -5
  148. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  149. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  150. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js +3 -0
  151. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js.map +1 -1
  152. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  153. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +9 -6
  154. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  155. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +30 -10
  157. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  158. package/packages/pi-coding-agent/package.json +1 -1
  159. package/packages/pi-coding-agent/src/core/agent-session.ts +7 -1
  160. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +68 -0
  161. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -2
  162. package/packages/pi-coding-agent/src/core/extensions/loader.ts +18 -0
  163. package/packages/pi-coding-agent/src/core/lsp/client.ts +29 -0
  164. package/packages/pi-coding-agent/src/core/model-registry.ts +3 -0
  165. package/packages/pi-coding-agent/src/core/package-manager.ts +99 -58
  166. package/packages/pi-coding-agent/src/core/resource-loader.ts +24 -6
  167. package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -5
  168. package/packages/pi-coding-agent/src/modes/interactive/components/extension-editor.ts +3 -0
  169. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +10 -6
  170. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +31 -11
  171. package/pkg/package.json +1 -1
  172. package/src/resources/extensions/async-jobs/async-bash-timeout.test.ts +122 -0
  173. package/src/resources/extensions/async-jobs/async-bash-tool.ts +40 -4
  174. package/src/resources/extensions/async-jobs/await-tool.test.ts +47 -0
  175. package/src/resources/extensions/async-jobs/await-tool.ts +5 -0
  176. package/src/resources/extensions/async-jobs/index.ts +1 -0
  177. package/src/resources/extensions/async-jobs/job-manager.ts +2 -0
  178. package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -1
  179. package/src/resources/extensions/gsd/auto/loop.ts +91 -0
  180. package/src/resources/extensions/gsd/auto/phases.ts +3 -5
  181. package/src/resources/extensions/gsd/auto/session.ts +6 -0
  182. package/src/resources/extensions/gsd/auto-dashboard.ts +2 -0
  183. package/src/resources/extensions/gsd/auto-prompts.ts +2 -18
  184. package/src/resources/extensions/gsd/auto-start.ts +7 -10
  185. package/src/resources/extensions/gsd/auto.ts +31 -1
  186. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +13 -5
  187. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +9 -2
  188. package/src/resources/extensions/gsd/commands/catalog.ts +32 -0
  189. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +164 -0
  190. package/src/resources/extensions/gsd/context-injector.ts +100 -0
  191. package/src/resources/extensions/gsd/custom-execution-policy.ts +73 -0
  192. package/src/resources/extensions/gsd/custom-verification.ts +180 -0
  193. package/src/resources/extensions/gsd/custom-workflow-engine.ts +216 -0
  194. package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -0
  195. package/src/resources/extensions/gsd/definition-loader.ts +462 -0
  196. package/src/resources/extensions/gsd/detection.ts +19 -0
  197. package/src/resources/extensions/gsd/dev-execution-policy.ts +51 -0
  198. package/src/resources/extensions/gsd/dev-workflow-engine.ts +110 -0
  199. package/src/resources/extensions/gsd/doctor-checks.ts +32 -1
  200. package/src/resources/extensions/gsd/doctor-providers.ts +13 -0
  201. package/src/resources/extensions/gsd/doctor-types.ts +1 -0
  202. package/src/resources/extensions/gsd/engine-resolver.ts +57 -0
  203. package/src/resources/extensions/gsd/engine-types.ts +71 -0
  204. package/src/resources/extensions/gsd/execution-policy.ts +43 -0
  205. package/src/resources/extensions/gsd/forensics.ts +92 -0
  206. package/src/resources/extensions/gsd/git-constants.ts +1 -0
  207. package/src/resources/extensions/gsd/git-service.ts +0 -1
  208. package/src/resources/extensions/gsd/gitignore.ts +1 -1
  209. package/src/resources/extensions/gsd/graph.ts +312 -0
  210. package/src/resources/extensions/gsd/native-git-bridge.ts +1 -0
  211. package/src/resources/extensions/gsd/preferences-types.ts +3 -0
  212. package/src/resources/extensions/gsd/preferences.ts +62 -6
  213. package/src/resources/extensions/gsd/prompts/forensics.md +12 -5
  214. package/src/resources/extensions/gsd/repo-identity.ts +48 -5
  215. package/src/resources/extensions/gsd/run-manager.ts +180 -0
  216. package/src/resources/extensions/gsd/service-tier.ts +17 -4
  217. package/src/resources/extensions/gsd/session-lock.ts +2 -2
  218. package/src/resources/extensions/gsd/tests/activity-log.test.ts +31 -69
  219. package/src/resources/extensions/gsd/tests/bundled-workflow-defs.test.ts +180 -0
  220. package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +283 -0
  221. package/src/resources/extensions/gsd/tests/context-injector.test.ts +313 -0
  222. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +540 -0
  223. package/src/resources/extensions/gsd/tests/custom-verification.test.ts +382 -0
  224. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +339 -0
  225. package/src/resources/extensions/gsd/tests/dashboard-custom-engine.test.ts +87 -0
  226. package/src/resources/extensions/gsd/tests/definition-loader.test.ts +778 -0
  227. package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +318 -0
  228. package/src/resources/extensions/gsd/tests/e2e-workflow-pipeline-integration.test.ts +476 -0
  229. package/src/resources/extensions/gsd/tests/engine-interfaces-contract.test.ts +271 -0
  230. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +48 -0
  231. package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +43 -0
  232. package/src/resources/extensions/gsd/tests/git-locale.test.ts +133 -0
  233. package/src/resources/extensions/gsd/tests/git-service.test.ts +44 -0
  234. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +599 -0
  235. package/src/resources/extensions/gsd/tests/iterate-engine-integration.test.ts +429 -0
  236. package/src/resources/extensions/gsd/tests/journal.test.ts +82 -127
  237. package/src/resources/extensions/gsd/tests/manifest-status.test.ts +73 -82
  238. package/src/resources/extensions/gsd/tests/run-manager.test.ts +229 -0
  239. package/src/resources/extensions/gsd/tests/service-tier.test.ts +30 -1
  240. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +56 -3
  241. package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +151 -0
  242. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +45 -0
  243. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +156 -263
  244. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -78
  245. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +81 -74
  246. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +1 -2
  247. package/src/resources/extensions/gsd/workflow-engine.ts +38 -0
  248. package/src/resources/extensions/gsd/worktree-resolver.ts +2 -3
  249. package/src/resources/extensions/gsd/worktree.ts +2 -2
  250. package/src/resources/extensions/mcp-client/index.ts +5 -1
  251. package/src/resources/extensions/search-the-web/tool-search.ts +3 -3
  252. package/src/resources/skills/create-workflow/SKILL.md +103 -0
  253. package/src/resources/skills/create-workflow/references/feature-patterns.md +128 -0
  254. package/src/resources/skills/create-workflow/references/verification-policies.md +76 -0
  255. package/src/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
  256. package/src/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
  257. package/src/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
  258. package/src/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
  259. package/src/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
  260. package/src/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
  261. package/src/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
  262. /package/dist/web/standalone/.next/static/{EnGUNqHeGbE0tuuUkTJVA → qw8qDHXOTLUXBq1vEknSz}/_buildManifest.js +0 -0
  263. /package/dist/web/standalone/.next/static/{EnGUNqHeGbE0tuuUkTJVA → qw8qDHXOTLUXBq1vEknSz}/_ssgManifest.js +0 -0
@@ -358,6 +358,22 @@ export function isAutoPaused(): boolean {
358
358
  return s.paused;
359
359
  }
360
360
 
361
+ export function setActiveEngineId(id: string | null): void {
362
+ s.activeEngineId = id;
363
+ }
364
+
365
+ export function getActiveEngineId(): string | null {
366
+ return s.activeEngineId;
367
+ }
368
+
369
+ export function setActiveRunDir(runDir: string | null): void {
370
+ s.activeRunDir = runDir;
371
+ }
372
+
373
+ export function getActiveRunDir(): string | null {
374
+ return s.activeRunDir;
375
+ }
376
+
361
377
  /**
362
378
  * Return the model captured at auto-mode start for this session.
363
379
  * Used by error-recovery to fall back to the session's own model
@@ -782,6 +798,8 @@ export async function pauseAuto(
782
798
  stepMode: s.stepMode,
783
799
  pausedAt: new Date().toISOString(),
784
800
  sessionFile: s.pausedSessionFile,
801
+ activeEngineId: s.activeEngineId,
802
+ activeRunDir: s.activeRunDir,
785
803
  };
786
804
  const runtimeDir = join(gsdRoot(s.originalBasePath || s.basePath), "runtime");
787
805
  mkdirSync(runtimeDir, { recursive: true });
@@ -1018,7 +1036,19 @@ export async function startAuto(
1018
1036
  const pausedPath = join(gsdRoot(base), "runtime", "paused-session.json");
1019
1037
  if (existsSync(pausedPath)) {
1020
1038
  const meta = JSON.parse(readFileSync(pausedPath, "utf-8"));
1021
- if (meta.milestoneId) {
1039
+ if (meta.activeEngineId && meta.activeEngineId !== "dev") {
1040
+ // Custom workflow resume — restore engine state
1041
+ s.activeEngineId = meta.activeEngineId;
1042
+ s.activeRunDir = meta.activeRunDir ?? null;
1043
+ s.originalBasePath = meta.originalBasePath || base;
1044
+ s.stepMode = meta.stepMode ?? requestedStepMode;
1045
+ s.paused = true;
1046
+ try { unlinkSync(pausedPath); } catch { /* non-fatal */ }
1047
+ ctx.ui.notify(
1048
+ `Resuming paused custom workflow${meta.activeRunDir ? ` (${meta.activeRunDir})` : ""}.`,
1049
+ "info",
1050
+ );
1051
+ } else if (meta.milestoneId) {
1022
1052
  // Validate the milestone still exists and isn't already complete (#1664).
1023
1053
  const mDir = resolveMilestonePath(base, meta.milestoneId);
1024
1054
  const summaryFile = resolveMilestoneFile(base, meta.milestoneId, "SUMMARY");
@@ -20,21 +20,27 @@ import { saveActivityLog } from "../activity-log.js";
20
20
  // printed it before the TUI launched. Only re-print on /clear (subsequent sessions).
21
21
  let isFirstSession = true;
22
22
 
23
+ async function syncServiceTierStatus(ctx: ExtensionContext): Promise<void> {
24
+ const { getEffectiveServiceTier, formatServiceTierFooterStatus } = await import("../service-tier.js");
25
+ ctx.ui.setStatus("gsd-fast", formatServiceTierFooterStatus(getEffectiveServiceTier(), ctx.model?.id));
26
+ }
27
+
23
28
  export function registerHooks(pi: ExtensionAPI): void {
24
29
  pi.on("session_start", async (_event, ctx) => {
25
30
  resetWriteGateState();
26
31
  resetToolCallLoopGuard();
32
+ await syncServiceTierStatus(ctx);
27
33
  if (isFirstSession) {
28
34
  isFirstSession = false;
29
35
  } else {
30
36
  try {
31
37
  const gsdBinPath = process.env.GSD_BIN_PATH;
32
38
  if (gsdBinPath) {
33
- const { dirname } = await import('node:path');
39
+ const { dirname } = await import("node:path");
34
40
  const { printWelcomeScreen } = await import(
35
- join(dirname(gsdBinPath), 'welcome-screen.js')
41
+ join(dirname(gsdBinPath), "welcome-screen.js")
36
42
  ) as { printWelcomeScreen: (opts: { version: string; modelName?: string; provider?: string }) => void };
37
- printWelcomeScreen({ version: process.env.GSD_VERSION || '0.0.0' });
43
+ printWelcomeScreen({ version: process.env.GSD_VERSION || "0.0.0" });
38
44
  }
39
45
  } catch { /* non-fatal */ }
40
46
  }
@@ -192,8 +198,11 @@ export function registerHooks(pi: ExtensionAPI): void {
192
198
  markToolEnd(event.toolCallId);
193
199
  });
194
200
 
201
+ pi.on("model_select", async (_event, ctx) => {
202
+ await syncServiceTierStatus(ctx);
203
+ });
204
+
195
205
  pi.on("before_provider_request", async (event) => {
196
- if (!isAutoActive()) return;
197
206
  const modelId = event.model?.id;
198
207
  if (!modelId) return;
199
208
  const { getEffectiveServiceTier, supportsServiceTier } = await import("../service-tier.js");
@@ -205,4 +214,3 @@ export function registerHooks(pi: ExtensionAPI): void {
205
214
  return payload;
206
215
  });
207
216
  }
208
-
@@ -24,8 +24,15 @@ let enabled = true;
24
24
  function hashToolCall(toolName: string, args: Record<string, unknown>): string {
25
25
  const h = createHash("sha256");
26
26
  h.update(toolName);
27
- // Sort keys for deterministic hashing regardless of object key order
28
- h.update(JSON.stringify(args, Object.keys(args).sort()));
27
+ // Sort keys recursively for deterministic hashing regardless of object key order
28
+ h.update(JSON.stringify(args, (_key, value) =>
29
+ value && typeof value === "object" && !Array.isArray(value)
30
+ ? Object.keys(value).sort().reduce<Record<string, unknown>>((o, k) => {
31
+ o[k] = value[k];
32
+ return o;
33
+ }, {})
34
+ : value
35
+ ));
29
36
  return h.digest("hex").slice(0, 16);
30
37
  }
31
38
 
@@ -3,6 +3,7 @@ import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
 
5
5
  import { loadRegistry } from "../workflow-templates.js";
6
+ import { resolveProjectRoot } from "../worktree.js";
6
7
 
7
8
  const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
8
9
 
@@ -65,6 +66,7 @@ export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
65
66
  { cmd: "templates", desc: "List available workflow templates" },
66
67
  { cmd: "extensions", desc: "Manage extensions (list, enable, disable, info)" },
67
68
  { cmd: "fast", desc: "Toggle OpenAI service tier (on/off/flex/status)" },
69
+ { cmd: "workflow", desc: "Custom workflow lifecycle (new, run, list, validate, pause, resume)" },
68
70
  ];
69
71
 
70
72
  const NESTED_COMPLETIONS: CompletionMap = {
@@ -206,6 +208,14 @@ const NESTED_COMPLETIONS: CompletionMap = {
206
208
  { cmd: "ok", desc: "Model was appropriate for this task" },
207
209
  { cmd: "under", desc: "Model was underqualified for this task" },
208
210
  ],
211
+ workflow: [
212
+ { cmd: "new", desc: "Create a new workflow definition (via skill)" },
213
+ { cmd: "run", desc: "Create a run and start auto-mode" },
214
+ { cmd: "list", desc: "List workflow runs" },
215
+ { cmd: "validate", desc: "Validate a workflow definition YAML" },
216
+ { cmd: "pause", desc: "Pause custom workflow auto-mode" },
217
+ { cmd: "resume", desc: "Resume paused custom workflow auto-mode" },
218
+ ],
209
219
  };
210
220
 
211
221
  function filterOptions(
@@ -309,6 +319,28 @@ export function getGsdArgumentCompletions(prefix: string) {
309
319
  return [{ value: "undo --force", label: "--force", description: "Skip confirmation prompt" }];
310
320
  }
311
321
 
322
+ // Workflow definition-name completion for `workflow run <name>` and `workflow validate <name>`
323
+ if (command === "workflow" && (subcommand === "run" || subcommand === "validate") && parts.length <= 3) {
324
+ try {
325
+ const defsDir = join(resolveProjectRoot(process.cwd()), ".gsd", "workflow-defs");
326
+ if (existsSync(defsDir)) {
327
+ return readdirSync(defsDir)
328
+ .filter((f) => f.endsWith(".yaml") && f.startsWith(third))
329
+ .map((f) => {
330
+ const name = f.replace(/\.yaml$/, "");
331
+ return {
332
+ value: `workflow ${subcommand} ${name}`,
333
+ label: name,
334
+ description: `Workflow definition: ${name}`,
335
+ };
336
+ });
337
+ }
338
+ } catch {
339
+ // ignore filesystem errors during completion
340
+ }
341
+ return [];
342
+ }
343
+
312
344
  const nested = NESTED_COMPLETIONS[command];
313
345
  if (nested && parts.length <= 2) {
314
346
  return filterOptions(subcommand, nested, command);
@@ -2,6 +2,7 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent
2
2
 
3
3
  import { existsSync, readFileSync, unlinkSync } from "node:fs";
4
4
  import { join } from "node:path";
5
+ import { parse as parseYaml } from "yaml";
5
6
 
6
7
  import { handleQuick } from "../../quick.js";
7
8
  import { showDiscuss, showHeadlessMilestoneCreation, showQueue } from "../../guided-flow.js";
@@ -13,8 +14,171 @@ import { loadEffectiveGSDPreferences } from "../../preferences.js";
13
14
  import { nextMilestoneId } from "../../milestone-ids.js";
14
15
  import { findMilestoneIds } from "../../guided-flow.js";
15
16
  import { projectRoot } from "../context.js";
17
+ import { createRun, listRuns } from "../../run-manager.js";
18
+ import {
19
+ setActiveEngineId,
20
+ setActiveRunDir,
21
+ startAuto,
22
+ pauseAuto,
23
+ isAutoActive,
24
+ getActiveEngineId,
25
+ } from "../../auto.js";
26
+ import { validateDefinition } from "../../definition-loader.js";
27
+
28
+ // ─── Custom Workflow Subcommands ─────────────────────────────────────────
29
+
30
+ const WORKFLOW_USAGE = [
31
+ "Usage: /gsd workflow <subcommand>",
32
+ "",
33
+ " new — Create a new workflow definition (via skill)",
34
+ " run <name> [k=v] — Create a run and start auto-mode",
35
+ " list [name] — List workflow runs (optionally filtered by name)",
36
+ " validate <name> — Validate a workflow definition YAML",
37
+ " pause — Pause custom workflow auto-mode",
38
+ " resume — Resume paused custom workflow auto-mode",
39
+ ].join("\n");
40
+
41
+ async function handleCustomWorkflow(
42
+ sub: string,
43
+ ctx: ExtensionCommandContext,
44
+ pi: ExtensionAPI,
45
+ ): Promise<boolean> {
46
+ // Bare `/gsd workflow` — show usage
47
+ if (!sub) {
48
+ ctx.ui.notify(WORKFLOW_USAGE, "info");
49
+ return true;
50
+ }
51
+
52
+ // ── new ──
53
+ if (sub === "new") {
54
+ ctx.ui.notify("Use the create-workflow skill: /skill create-workflow", "info");
55
+ return true;
56
+ }
57
+
58
+ // ── run <name> [param=value ...] ──
59
+ if (sub === "run" || sub.startsWith("run ")) {
60
+ const args = sub.slice("run".length).trim();
61
+ if (!args) {
62
+ ctx.ui.notify("Usage: /gsd workflow run <name> [param=value ...]", "warning");
63
+ return true;
64
+ }
65
+ const parts = args.split(/\s+/);
66
+ const defName = parts[0];
67
+ const overrides: Record<string, string> = {};
68
+ for (let i = 1; i < parts.length; i++) {
69
+ const eqIdx = parts[i].indexOf("=");
70
+ if (eqIdx > 0) {
71
+ overrides[parts[i].slice(0, eqIdx)] = parts[i].slice(eqIdx + 1);
72
+ }
73
+ }
74
+ try {
75
+ const base = projectRoot();
76
+ const runDir = createRun(base, defName, Object.keys(overrides).length > 0 ? overrides : undefined);
77
+ setActiveEngineId("custom");
78
+ setActiveRunDir(runDir);
79
+ ctx.ui.notify(`Created workflow run: ${defName}\nRun dir: ${runDir}`, "info");
80
+ await startAuto(ctx, pi, base, false);
81
+ } catch (err) {
82
+ // Clean up engine state so a failed workflow run doesn't pollute the next /gsd auto
83
+ setActiveEngineId(null);
84
+ setActiveRunDir(null);
85
+ const msg = err instanceof Error ? err.message : String(err);
86
+ ctx.ui.notify(`Failed to run workflow "${defName}": ${msg}`, "error");
87
+ }
88
+ return true;
89
+ }
90
+
91
+ // ── list [name] ──
92
+ if (sub === "list" || sub.startsWith("list ")) {
93
+ const filterName = sub.slice("list".length).trim() || undefined;
94
+ const base = projectRoot();
95
+ const runs = listRuns(base, filterName);
96
+ if (runs.length === 0) {
97
+ ctx.ui.notify("No workflow runs found.", "info");
98
+ return true;
99
+ }
100
+ const lines = runs.map((r) => {
101
+ const stepInfo = `${r.steps.completed}/${r.steps.total} steps`;
102
+ return `• ${r.name} [${r.timestamp}] — ${r.status} (${stepInfo})`;
103
+ });
104
+ ctx.ui.notify(lines.join("\n"), "info");
105
+ return true;
106
+ }
107
+
108
+ // ── validate <name> ──
109
+ if (sub === "validate" || sub.startsWith("validate ")) {
110
+ const defName = sub.slice("validate".length).trim();
111
+ if (!defName) {
112
+ ctx.ui.notify("Usage: /gsd workflow validate <name>", "warning");
113
+ return true;
114
+ }
115
+ const base = projectRoot();
116
+ const defPath = join(base, ".gsd", "workflow-defs", `${defName}.yaml`);
117
+ if (!existsSync(defPath)) {
118
+ ctx.ui.notify(`Definition not found: ${defPath}`, "error");
119
+ return true;
120
+ }
121
+ try {
122
+ const raw = readFileSync(defPath, "utf-8");
123
+ const parsed = parseYaml(raw);
124
+ const result = validateDefinition(parsed);
125
+ if (result.valid) {
126
+ ctx.ui.notify(`✓ "${defName}" is a valid workflow definition.`, "info");
127
+ } else {
128
+ ctx.ui.notify(`✗ "${defName}" has errors:\n - ${result.errors.join("\n - ")}`, "error");
129
+ }
130
+ } catch (err) {
131
+ const msg = err instanceof Error ? err.message : String(err);
132
+ ctx.ui.notify(`Failed to validate "${defName}": ${msg}`, "error");
133
+ }
134
+ return true;
135
+ }
136
+
137
+ // ── pause ──
138
+ if (sub === "pause") {
139
+ const engineId = getActiveEngineId();
140
+ if (engineId === "dev" || engineId === null) {
141
+ ctx.ui.notify("No custom workflow is running. Use /gsd pause for dev workflow.", "warning");
142
+ return true;
143
+ }
144
+ if (!isAutoActive()) {
145
+ ctx.ui.notify("Auto-mode is not active.", "warning");
146
+ return true;
147
+ }
148
+ await pauseAuto(ctx, pi);
149
+ ctx.ui.notify("Custom workflow paused.", "info");
150
+ return true;
151
+ }
152
+
153
+ // ── resume ──
154
+ if (sub === "resume") {
155
+ const engineId = getActiveEngineId();
156
+ if (engineId === "dev" || engineId === null) {
157
+ ctx.ui.notify("No custom workflow to resume. Use /gsd auto for dev workflow.", "warning");
158
+ return true;
159
+ }
160
+ try {
161
+ await startAuto(ctx, pi, projectRoot(), false);
162
+ ctx.ui.notify("Custom workflow resumed.", "info");
163
+ } catch (err) {
164
+ const msg = err instanceof Error ? err.message : String(err);
165
+ ctx.ui.notify(`Failed to resume workflow: ${msg}`, "error");
166
+ }
167
+ return true;
168
+ }
169
+
170
+ // Unknown subcommand — show usage
171
+ ctx.ui.notify(`Unknown workflow subcommand: "${sub}"\n\n${WORKFLOW_USAGE}`, "warning");
172
+ return true;
173
+ }
16
174
 
17
175
  export async function handleWorkflowCommand(trimmed: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<boolean> {
176
+ // ── Custom workflow commands (`/gsd workflow ...`) ──
177
+ if (trimmed === "workflow" || trimmed.startsWith("workflow ")) {
178
+ const sub = trimmed.slice("workflow".length).trim();
179
+ return handleCustomWorkflow(sub, ctx, pi);
180
+ }
181
+
18
182
  if (trimmed === "queue") {
19
183
  await showQueue(ctx, pi, projectRoot());
20
184
  return true;
@@ -0,0 +1,100 @@
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
+
16
+ import { readFileSync, existsSync } from "node:fs";
17
+ import { join, resolve, sep } from "node:path";
18
+ import type { StepDefinition } from "./definition-loader.js";
19
+ import { readFrozenDefinition } from "./custom-workflow-engine.js";
20
+
21
+ /** Maximum characters per artifact to prevent context window blowout. */
22
+ const MAX_CONTEXT_CHARS = 10_000;
23
+
24
+ /**
25
+ * Inject context from prior step artifacts into a step's prompt.
26
+ *
27
+ * Reads the frozen DEFINITION.yaml from `runDir`, finds the step matching
28
+ * `stepId`, and for each step ID in its `contextFrom` array, looks up that
29
+ * step's `produces` paths, reads them from disk (relative to `runDir`),
30
+ * truncates to MAX_CONTEXT_CHARS, and prepends as labeled context blocks.
31
+ *
32
+ * @param runDir — absolute path to the workflow run directory
33
+ * @param stepId — the step ID whose prompt to enrich
34
+ * @param prompt — the original step prompt
35
+ * @returns The prompt with context blocks prepended, or unchanged if no context applies
36
+ * @throws Error if DEFINITION.yaml is missing or unreadable
37
+ */
38
+ export function injectContext(
39
+ runDir: string,
40
+ stepId: string,
41
+ prompt: string,
42
+ ): string {
43
+ const def = readFrozenDefinition(runDir);
44
+
45
+ const step = def.steps.find((s: StepDefinition) => s.id === stepId);
46
+ if (!step || !step.contextFrom || step.contextFrom.length === 0) {
47
+ return prompt;
48
+ }
49
+
50
+ const contextBlocks: string[] = [];
51
+
52
+ for (const refStepId of step.contextFrom) {
53
+ const refStep = def.steps.find((s: StepDefinition) => s.id === refStepId);
54
+ if (!refStep) {
55
+ console.warn(
56
+ `context-injector: step "${stepId}" references unknown step "${refStepId}" in contextFrom — skipping`,
57
+ );
58
+ continue;
59
+ }
60
+
61
+ if (!refStep.produces || refStep.produces.length === 0) {
62
+ continue;
63
+ }
64
+
65
+ for (const relPath of refStep.produces) {
66
+ const absPath = resolve(runDir, relPath);
67
+ // Path traversal guard: ensure resolved path stays within runDir
68
+ if (!absPath.startsWith(resolve(runDir) + sep) && absPath !== resolve(runDir)) {
69
+ console.warn(
70
+ `context-injector: artifact path "${relPath}" resolves outside runDir — skipping`,
71
+ );
72
+ continue;
73
+ }
74
+ if (!existsSync(absPath)) {
75
+ // Artifact not yet produced or optional — skip silently
76
+ continue;
77
+ }
78
+
79
+ let content = readFileSync(absPath, "utf-8");
80
+
81
+ if (content.length > MAX_CONTEXT_CHARS) {
82
+ console.warn(
83
+ `context-injector: truncating artifact "${relPath}" from step "${refStepId}" ` +
84
+ `(${content.length} chars → ${MAX_CONTEXT_CHARS} chars)`,
85
+ );
86
+ content = content.slice(0, MAX_CONTEXT_CHARS) + "\n...[truncated]";
87
+ }
88
+
89
+ contextBlocks.push(
90
+ `--- Context from step "${refStepId}" (file: ${relPath}) ---\n${content}\n---`,
91
+ );
92
+ }
93
+ }
94
+
95
+ if (contextBlocks.length === 0) {
96
+ return prompt;
97
+ }
98
+
99
+ return contextBlocks.join("\n\n") + "\n\n" + prompt;
100
+ }
@@ -0,0 +1,73 @@
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
+
14
+ import type { ExecutionPolicy } from "./execution-policy.js";
15
+ import type { RecoveryAction, CloseoutResult } from "./engine-types.js";
16
+ import { runCustomVerification } from "./custom-verification.js";
17
+
18
+ export class CustomExecutionPolicy implements ExecutionPolicy {
19
+ private readonly runDir: string;
20
+
21
+ constructor(runDir: string) {
22
+ this.runDir = runDir;
23
+ }
24
+
25
+ /** No workspace preparation needed for custom workflows. */
26
+ async prepareWorkspace(_basePath: string, _milestoneId: string): Promise<void> {
27
+ // No-op — custom workflows don't need worktree setup
28
+ }
29
+
30
+ /** Defer model selection to loop defaults. */
31
+ async selectModel(
32
+ _unitType: string,
33
+ _unitId: string,
34
+ _context: { basePath: string },
35
+ ): Promise<{ tier: string; modelDowngraded: boolean } | null> {
36
+ return null;
37
+ }
38
+
39
+ /**
40
+ * Verify step output by dispatching to the step's configured verification policy.
41
+ *
42
+ * Extracts the step ID from unitId (format: "<workflowName>/<stepId>")
43
+ * and calls runCustomVerification() which reads the frozen DEFINITION.yaml
44
+ * to determine which policy to apply.
45
+ */
46
+ async verify(
47
+ _unitType: string,
48
+ unitId: string,
49
+ _context: { basePath: string },
50
+ ): Promise<"continue" | "retry" | "pause"> {
51
+ const parts = unitId.split("/");
52
+ const stepId = parts[parts.length - 1];
53
+ return runCustomVerification(this.runDir, stepId);
54
+ }
55
+
56
+ /** Default recovery: retry the step. */
57
+ async recover(
58
+ _unitType: string,
59
+ _unitId: string,
60
+ _context: { basePath: string },
61
+ ): Promise<RecoveryAction> {
62
+ return { outcome: "retry", reason: "Default retry" };
63
+ }
64
+
65
+ /** No-op closeout — no commits or artifact capture. */
66
+ async closeout(
67
+ _unitType: string,
68
+ _unitId: string,
69
+ _context: { basePath: string; startedAt: number },
70
+ ): Promise<CloseoutResult> {
71
+ return { committed: false, artifacts: [] };
72
+ }
73
+ }