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
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { createHash } from "node:crypto";
9
9
  import { execFileSync } from "node:child_process";
10
- import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
10
+ import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
11
11
  import { homedir } from "node:os";
12
12
  import { basename, dirname, join, resolve } from "node:path";
13
13
  const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
@@ -247,14 +247,52 @@ export function externalProjectsRoot() {
247
247
  const base = process.env.GSD_STATE_DIR || gsdHome;
248
248
  return join(base, "projects");
249
249
  }
250
+ // ─── Numbered Variant Cleanup ────────────────────────────────────────────────
251
+ /**
252
+ * macOS collision pattern: `.gsd 2`, `.gsd 3`, `.gsd 4`, etc.
253
+ *
254
+ * When `symlinkSync` (or Finder) tries to create `.gsd` but a real directory
255
+ * already exists at that path, macOS APFS silently renames the new entry to
256
+ * `.gsd 2`, then `.gsd 3`, and so on. These numbered variants confuse GSD
257
+ * because the canonical `.gsd` path no longer resolves to the external state
258
+ * directory, making tracked planning files appear deleted.
259
+ *
260
+ * This helper scans the project root for entries matching `.gsd <digits>` and
261
+ * removes them. It is called early in `ensureGsdSymlink()` so that the
262
+ * canonical `.gsd` path is always the one in use.
263
+ */
264
+ const GSD_NUMBERED_VARIANT_RE = /^\.gsd \d+$/;
265
+ export function cleanNumberedGsdVariants(projectPath) {
266
+ const removed = [];
267
+ try {
268
+ const entries = readdirSync(projectPath);
269
+ for (const entry of entries) {
270
+ if (GSD_NUMBERED_VARIANT_RE.test(entry)) {
271
+ const fullPath = join(projectPath, entry);
272
+ try {
273
+ rmSync(fullPath, { recursive: true, force: true });
274
+ removed.push(entry);
275
+ }
276
+ catch {
277
+ // Best-effort: if removal fails (e.g. permissions), continue with next
278
+ }
279
+ }
280
+ }
281
+ }
282
+ catch {
283
+ // Non-fatal: readdir failure should not block symlink creation
284
+ }
285
+ return removed;
286
+ }
250
287
  // ─── Symlink Management ─────────────────────────────────────────────────────
251
288
  /**
252
289
  * Ensure the `<project>/.gsd` symlink points to the external state directory.
253
290
  *
254
- * 1. mkdir -p the external dir
255
- * 2. If `<project>/.gsd` doesn't exist → create symlink
256
- * 3. If `<project>/.gsd` is already the correct symlink → no-op
257
- * 4. If `<project>/.gsd` is a real directoryreturn as-is (migration handles later)
291
+ * 1. Clean up any macOS numbered collision variants (`.gsd 2`, `.gsd 3`, etc.)
292
+ * 2. mkdir -p the external dir
293
+ * 3. If `<project>/.gsd` doesn't exist create symlink
294
+ * 4. If `<project>/.gsd` is already the correct symlink no-op
295
+ * 5. If `<project>/.gsd` is a real directory → return as-is (migration handles later)
258
296
  *
259
297
  * Returns the resolved external path.
260
298
  */
@@ -270,6 +308,9 @@ export function ensureGsdSymlink(projectPath) {
270
308
  if (localGsdNormalized === gsdHomePath) {
271
309
  return localGsd;
272
310
  }
311
+ // Clean up macOS numbered collision variants (.gsd 2, .gsd 3, etc.) before
312
+ // any existence checks — otherwise they accumulate and confuse state (#2205).
313
+ cleanNumberedGsdVariants(projectPath);
273
314
  // Ensure external directory exists
274
315
  mkdirSync(externalPath, { recursive: true });
275
316
  // Write repo metadata once so cleanup commands can identify this directory later.
@@ -0,0 +1,134 @@
1
+ /**
2
+ * run-manager.ts — Create and list isolated workflow run directories.
3
+ *
4
+ * Each run lives under `.gsd/workflow-runs/<name>/<timestamp>/` and contains:
5
+ * - DEFINITION.yaml — frozen snapshot of the workflow definition at run-creation time
6
+ * - GRAPH.yaml — initialized step graph with all steps pending
7
+ * - PARAMS.json — (optional) parameter overrides used for this run
8
+ *
9
+ * Observability:
10
+ * - All run state is on disk in human-readable YAML/JSON — inspectable with cat/less.
11
+ * - `listRuns()` returns structured metadata including step counts and overall status.
12
+ * - Timestamp directory names are filesystem-safe (ISO with hyphens replacing colons).
13
+ * - Errors include the full path context for diagnosis.
14
+ */
15
+ import { mkdirSync, writeFileSync, existsSync, readdirSync, statSync } from "node:fs";
16
+ import { join } from "node:path";
17
+ import { stringify } from "yaml";
18
+ import { loadDefinition, substituteParams } from "./definition-loader.js";
19
+ import { initializeGraph, writeGraph, readGraph } from "./graph.js";
20
+ // ─── Constants ───────────────────────────────────────────────────────────
21
+ const RUNS_DIR = "workflow-runs";
22
+ const DEFS_DIR = "workflow-defs";
23
+ // ─── Helpers ─────────────────────────────────────────────────────────────
24
+ /**
25
+ * Generate a filesystem-safe timestamp: `YYYY-MM-DDTHH-MM-SS`.
26
+ * Replaces colons with hyphens so the string is safe as a directory name
27
+ * on all platforms (Windows forbids colons in paths).
28
+ */
29
+ function makeTimestamp(date = new Date()) {
30
+ return date.toISOString().replace(/:/g, "-").replace(/\.\d{3}Z$/, "");
31
+ }
32
+ /**
33
+ * Derive overall status from a graph's step statuses.
34
+ */
35
+ function deriveStatus(graph) {
36
+ const hasActive = graph.steps.some((s) => s.status === "active");
37
+ const allDone = graph.steps.every((s) => s.status === "complete" || s.status === "expanded");
38
+ if (allDone)
39
+ return "complete";
40
+ if (hasActive)
41
+ return "running";
42
+ return "pending";
43
+ }
44
+ // ─── Public API ──────────────────────────────────────────────────────────
45
+ /**
46
+ * Create a new isolated run directory for a workflow definition.
47
+ *
48
+ * 1. Loads the definition from `<basePath>/.gsd/workflow-defs/<defName>.yaml`
49
+ * 2. Applies parameter substitution if overrides are provided
50
+ * 3. Creates `<basePath>/.gsd/workflow-runs/<defName>/<timestamp>/`
51
+ * 4. Writes frozen DEFINITION.yaml, initialized GRAPH.yaml, and optional PARAMS.json
52
+ *
53
+ * @param basePath — project root directory
54
+ * @param defName — definition filename (without .yaml extension)
55
+ * @param overrides — optional parameter overrides (merged with definition defaults)
56
+ * @returns Full path to the created run directory
57
+ * @throws Error if the definition file doesn't exist or is invalid
58
+ */
59
+ export function createRun(basePath, defName, overrides) {
60
+ const defsDir = join(basePath, ".gsd", DEFS_DIR);
61
+ // Load and validate the definition
62
+ const rawDef = loadDefinition(defsDir, defName);
63
+ // Apply parameter substitution if overrides provided
64
+ const def = overrides
65
+ ? substituteParams(rawDef, overrides)
66
+ : substituteParams(rawDef); // still resolve default params if any
67
+ // Create the run directory
68
+ const timestamp = makeTimestamp();
69
+ const runDir = join(basePath, ".gsd", RUNS_DIR, defName, timestamp);
70
+ mkdirSync(runDir, { recursive: true });
71
+ // Freeze the definition as DEFINITION.yaml
72
+ writeFileSync(join(runDir, "DEFINITION.yaml"), stringify(def), "utf-8");
73
+ // Initialize and write GRAPH.yaml
74
+ const graph = initializeGraph(def);
75
+ writeGraph(runDir, graph);
76
+ // Write PARAMS.json if overrides were provided
77
+ if (overrides && Object.keys(overrides).length > 0) {
78
+ writeFileSync(join(runDir, "PARAMS.json"), JSON.stringify(overrides, null, 2), "utf-8");
79
+ }
80
+ return runDir;
81
+ }
82
+ /**
83
+ * List existing workflow runs with metadata.
84
+ *
85
+ * Scans `<basePath>/.gsd/workflow-runs/` for run directories. Each run's
86
+ * GRAPH.yaml is read to derive step counts and overall status.
87
+ *
88
+ * @param basePath — project root directory
89
+ * @param defName — optional filter: only list runs for this definition name
90
+ * @returns Array of run metadata, sorted newest-first within each definition
91
+ */
92
+ export function listRuns(basePath, defName) {
93
+ const runsRoot = join(basePath, ".gsd", RUNS_DIR);
94
+ if (!existsSync(runsRoot))
95
+ return [];
96
+ const results = [];
97
+ // Get workflow name directories
98
+ const nameDirs = defName ? [defName] : readdirSync(runsRoot).filter((entry) => {
99
+ const full = join(runsRoot, entry);
100
+ return statSync(full).isDirectory();
101
+ });
102
+ for (const name of nameDirs) {
103
+ const nameDir = join(runsRoot, name);
104
+ if (!existsSync(nameDir))
105
+ continue;
106
+ const timestamps = readdirSync(nameDir).filter((entry) => {
107
+ const full = join(nameDir, entry);
108
+ return statSync(full).isDirectory();
109
+ });
110
+ // Sort newest-first (ISO strings sort lexicographically)
111
+ timestamps.sort().reverse();
112
+ for (const ts of timestamps) {
113
+ const runDir = join(nameDir, ts);
114
+ try {
115
+ const graph = readGraph(runDir);
116
+ const total = graph.steps.length;
117
+ const completed = graph.steps.filter((s) => s.status === "complete").length;
118
+ const pending = graph.steps.filter((s) => s.status === "pending").length;
119
+ const active = graph.steps.filter((s) => s.status === "active").length;
120
+ results.push({
121
+ name,
122
+ timestamp: ts,
123
+ runDir,
124
+ steps: { total, completed, pending, active },
125
+ status: deriveStatus(graph),
126
+ });
127
+ }
128
+ catch {
129
+ // Skip runs with invalid/missing GRAPH.yaml
130
+ }
131
+ }
132
+ }
133
+ return results;
134
+ }
@@ -11,6 +11,7 @@ import { existsSync, readFileSync } from "node:fs";
11
11
  import { saveFile } from "./files.js";
12
12
  import { getGlobalGSDPreferencesPath, loadEffectiveGSDPreferences, loadGlobalGSDPreferences, } from "./preferences.js";
13
13
  import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./commands-prefs-wizard.js";
14
+ const SERVICE_TIER_SCOPE_NOTE = "Only affects gpt-5.4 models, regardless of provider.";
14
15
  // ─── Gating ──────────────────────────────────────────────────────────────────
15
16
  /**
16
17
  * Returns true when the given model ID supports OpenAI service tiers.
@@ -37,7 +38,7 @@ export function formatServiceTierStatus(tier) {
37
38
  " /gsd fast flex Set to flex (0.5x cost, slower)",
38
39
  " /gsd fast off Disable service tier",
39
40
  "",
40
- "Only affects gpt-5.4 models.",
41
+ SERVICE_TIER_SCOPE_NOTE,
41
42
  ].join("\n");
42
43
  }
43
44
  const label = tier === "priority" ? "priority (2x cost, faster)" : "flex (0.5x cost, slower)";
@@ -49,9 +50,14 @@ export function formatServiceTierStatus(tier) {
49
50
  " /gsd fast flex Set to flex (0.5x cost, slower)",
50
51
  " /gsd fast off Disable service tier",
51
52
  "",
52
- "Only affects gpt-5.4 models.",
53
+ SERVICE_TIER_SCOPE_NOTE,
53
54
  ].join("\n");
54
55
  }
56
+ export function formatServiceTierFooterStatus(tier, modelId) {
57
+ if (!tier || !modelId || !supportsServiceTier(modelId))
58
+ return undefined;
59
+ return tier === "priority" ? "fast: ⚡ priority" : "fast: 💰 flex";
60
+ }
55
61
  // ─── Icon Resolution ─────────────────────────────────────────────────────────
56
62
  /**
57
63
  * Returns the appropriate icon for the active service tier and model.
@@ -121,17 +127,20 @@ export async function handleFast(args, ctx) {
121
127
  }
122
128
  if (trimmed === "on") {
123
129
  await writeGlobalServiceTier(ctx, "priority");
124
- ctx.ui.notify("Service tier set to priority (2x cost, faster responses). Only affects gpt-5.4 models.", "info");
130
+ ctx.ui.setStatus("gsd-fast", formatServiceTierFooterStatus("priority", ctx.model?.id));
131
+ ctx.ui.notify("Service tier set to priority (2x cost, faster responses). Only affects gpt-5.4 models, regardless of provider.", "info");
125
132
  return;
126
133
  }
127
134
  if (trimmed === "off") {
128
135
  await writeGlobalServiceTier(ctx, undefined);
136
+ ctx.ui.setStatus("gsd-fast", undefined);
129
137
  ctx.ui.notify("Service tier disabled.", "info");
130
138
  return;
131
139
  }
132
140
  if (trimmed === "flex") {
133
141
  await writeGlobalServiceTier(ctx, "flex");
134
- ctx.ui.notify("Service tier set to flex (0.5x cost, slower responses). Only affects gpt-5.4 models.", "info");
142
+ ctx.ui.setStatus("gsd-fast", formatServiceTierFooterStatus("flex", ctx.model?.id));
143
+ ctx.ui.notify("Service tier set to flex (0.5x cost, slower responses). Only affects gpt-5.4 models, regardless of provider.", "info");
135
144
  return;
136
145
  }
137
146
  ctx.ui.notify("Usage: /gsd fast [on|off|flex|status]\n\n on Priority tier (2x cost, faster)\n off Disable service tier\n flex Flex tier (0.5x cost, slower)\n status Show current setting", "warning");
@@ -199,7 +199,7 @@ export function acquireSessionLock(basePath) {
199
199
  // during a long LLM call — not a real takeover. Log and continue.
200
200
  const elapsed = Date.now() - _lockAcquiredAt;
201
201
  if (elapsed < 1_800_000) {
202
- process.stderr.write(`[gsd] Lock heartbeat mismatch after ${Math.round(elapsed / 1000)}s — event loop stall, continuing.\n`);
202
+ process.stderr.write(`[gsd] Lock heartbeat caught up after ${Math.round(elapsed / 1000)}s — long LLM call, no action needed.\n`);
203
203
  return; // Suppress false positive
204
204
  }
205
205
  // Past the stale window — check if the lock file still belongs to us before
@@ -252,7 +252,7 @@ export function acquireSessionLock(basePath) {
252
252
  // on benign mtime drift (laptop sleep, heavy LLM event loop stalls).
253
253
  const elapsed = Date.now() - _lockAcquiredAt;
254
254
  if (elapsed < 1_800_000) {
255
- process.stderr.write(`[gsd] Lock heartbeat mismatch after ${Math.round(elapsed / 1000)}s — event loop stall, continuing.\n`);
255
+ process.stderr.write(`[gsd] Lock heartbeat caught up after ${Math.round(elapsed / 1000)}s — long LLM call, no action needed.\n`);
256
256
  return;
257
257
  }
258
258
  // Check PID ownership before declaring compromise (#1578)
@@ -0,0 +1,7 @@
1
+ /**
2
+ * workflow-engine.ts — WorkflowEngine interface.
3
+ *
4
+ * Defines the contract every engine implementation must satisfy.
5
+ * Imports only from the leaf-node engine-types.
6
+ */
7
+ export {};
@@ -286,9 +286,9 @@ export class WorktreeResolver {
286
286
  });
287
287
  // Surface a clear, actionable error. The worktree and milestone branch are
288
288
  // intentionally preserved — nothing has been deleted. The user can retry
289
- // /complete-milestone or merge manually once the underlying issue is fixed
289
+ // /gsd dispatch complete-milestone or merge manually once the underlying issue is fixed
290
290
  // (e.g. checkout to wrong branch, unresolved conflicts). (#1668)
291
- ctx.notify(`Milestone merge failed: ${msg}. Your worktree and milestone branch are preserved — retry /complete-milestone or merge manually.`, "warning");
291
+ ctx.notify(`Milestone merge failed: ${msg}. Your worktree and milestone branch are preserved — retry /gsd dispatch complete-milestone or merge manually.`, "warning");
292
292
  // Clean up stale merge state left by failed squash-merge (#1389)
293
293
  try {
294
294
  const gitDir = join(originalBase || this.s.basePath, ".git");
@@ -48,14 +48,14 @@ export function setActiveMilestoneId(basePath, milestoneId) {
48
48
  * record when the user starts from a different branch (#300). Always a no-op
49
49
  * if on a GSD slice branch.
50
50
  */
51
- export function captureIntegrationBranch(basePath, milestoneId, options) {
51
+ export function captureIntegrationBranch(basePath, milestoneId) {
52
52
  // In a worktree, the base branch is implicit (worktree/<name>).
53
53
  // Writing it to META.json would leave stale metadata after merge back to main.
54
54
  if (detectWorktreeName(basePath))
55
55
  return;
56
56
  const svc = getService(basePath);
57
57
  const current = svc.getCurrentBranch();
58
- writeIntegrationBranch(basePath, milestoneId, current, options);
58
+ writeIntegrationBranch(basePath, milestoneId, current);
59
59
  }
60
60
  // ─── Pure Utility Functions (unchanged) ────────────────────────────────────
61
61
  /**
@@ -108,7 +108,8 @@ async function getOrConnect(name, signal) {
108
108
  });
109
109
  }
110
110
  else if (config.transport === "http" && config.url) {
111
- transport = new StreamableHTTPClientTransport(new URL(config.url));
111
+ const resolvedUrl = config.url.replace(/\$\{([^}]+)\}/g, (_, name) => process.env[name] ?? "");
112
+ transport = new StreamableHTTPClientTransport(new URL(resolvedUrl));
112
113
  }
113
114
  else {
114
115
  throw new Error(`Server "${name}" has unsupported transport: ${config.transport}`);
@@ -259,9 +259,9 @@ export function registerSearchTool(pi) {
259
259
  // with brief interruptions every MAX_CONSECUTIVE_DUPES+1 calls.
260
260
  if (cacheKey === lastSearchKey) {
261
261
  consecutiveDupeCount++;
262
- if (consecutiveDupeCount >= MAX_CONSECUTIVE_DUPES) {
262
+ if (consecutiveDupeCount > MAX_CONSECUTIVE_DUPES) {
263
263
  return {
264
- content: [{ type: "text", text: `⚠️ Search loop detected: the query "${params.query}" has been searched ${consecutiveDupeCount + 1} times consecutively with identical results. The information you need is already in the previous search results above. Stop searching and use those results to proceed with your task.` }],
264
+ content: [{ type: "text", text: `⚠️ Search loop detected: the query "${params.query}" has been searched ${consecutiveDupeCount} times consecutively with identical results. The information you need is already in the previous search results above. Stop searching and use those results to proceed with your task.` }],
265
265
  isError: true,
266
266
  details: { errorKind: "search_loop", error: "Consecutive duplicate search detected" },
267
267
  };
@@ -269,7 +269,7 @@ export function registerSearchTool(pi) {
269
269
  }
270
270
  else {
271
271
  lastSearchKey = cacheKey;
272
- consecutiveDupeCount = 0;
272
+ consecutiveDupeCount = 1;
273
273
  }
274
274
  const cached = searchCache.get(cacheKey);
275
275
  if (cached) {
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: create-workflow
3
+ description: Conversational guide for creating valid YAML workflow definitions. Use when asked to "create a workflow", "new workflow definition", "build a workflow", "workflow YAML", "define workflow steps", or "workflow from template".
4
+ ---
5
+
6
+ <essential_principles>
7
+ You are a workflow definition author. You help users create valid V1 YAML workflow definitions that the GSD workflow engine can execute.
8
+
9
+ **V1 Schema Basics:**
10
+
11
+ - Every definition requires `version: 1`, a non-empty `name`, and at least one step in `steps[]`.
12
+ - Optional top-level fields: `description` (string), `params` (key-value defaults for `{{ key }}` substitution).
13
+ - Each step requires: `id` (unique string), `name` (non-empty string), `prompt` (non-empty string).
14
+ - Each step optionally has: `requires` or `depends_on` (array of step IDs), `produces` (array of artifact paths), `context_from` (array of step IDs), `verify` (verification policy object), `iterate` (fan-out config object).
15
+ - YAML uses **snake_case** keys: `depends_on`, `context_from`. The engine converts to camelCase internally.
16
+
17
+ **Validation Rules:**
18
+
19
+ - Step IDs must be unique across the workflow.
20
+ - Dependencies (`requires`/`depends_on`) must reference existing step IDs — no dangling refs.
21
+ - A step cannot depend on itself.
22
+ - The dependency graph must be acyclic (no circular dependencies).
23
+ - `produces` paths must not contain `..` (path traversal rejected).
24
+ - `iterate.source` must not contain `..` (path traversal rejected).
25
+ - `iterate.pattern` must be a valid regex with at least one capture group.
26
+
27
+ **Four Verification Policies:**
28
+
29
+ 1. `content-heuristic` — Checks artifact content. Optional: `minSize` (number), `pattern` (string).
30
+ 2. `shell-command` — Runs a shell command. Required: `command` (non-empty string).
31
+ 3. `prompt-verify` — Asks an LLM to verify. Required: `prompt` (non-empty string).
32
+ 4. `human-review` — Pauses for human approval. No extra fields required.
33
+
34
+ **Parameter Substitution:**
35
+
36
+ - Define defaults in top-level `params: { key: "default_value" }`.
37
+ - Use `{{ key }}` placeholders in step prompts — the engine replaces them at runtime.
38
+ - CLI overrides take precedence over definition defaults.
39
+ - Parameter values must not contain `..` (path traversal guard).
40
+ - Any unresolved `{{ key }}` after substitution causes an error.
41
+
42
+ **Path Traversal Guard:**
43
+
44
+ - The engine rejects any `produces` path or `iterate.source` containing `..`.
45
+ - Parameter values are also checked for `..` during substitution.
46
+
47
+ **Output Location:**
48
+
49
+ - Finished definitions go in `.gsd/workflow-defs/<name>.yaml`.
50
+ - After writing, tell the user to validate with `/gsd workflow validate <name>`.
51
+ </essential_principles>
52
+
53
+ <routing>
54
+ Determine the user's intent and route to the appropriate workflow:
55
+
56
+ **"I want to create a workflow from scratch" / "new workflow" / "build a workflow":**
57
+ → Read `workflows/create-from-scratch.md` and follow it.
58
+
59
+ **"I want to start from a template" / "from an example" / "customize a template":**
60
+ → Read `workflows/create-from-template.md` and follow it.
61
+
62
+ **"Help me understand the schema" / "what fields are available?":**
63
+ → Read `references/yaml-schema-v1.md` and explain the relevant parts.
64
+
65
+ **"How does verification work?" / "verify policies":**
66
+ → Read `references/verification-policies.md` and explain.
67
+
68
+ **"How do I use context_from / iterate / params?":**
69
+ → Read `references/feature-patterns.md` and explain the relevant feature.
70
+
71
+ **If intent is unclear, ask one clarifying question:**
72
+ - "Do you want to create a workflow from scratch, or start from an existing template?"
73
+ - Then route based on the answer.
74
+ </routing>
75
+
76
+ <reference_index>
77
+ Read these files when you need detailed schema knowledge during workflow authoring:
78
+
79
+ - `references/yaml-schema-v1.md` — Complete field-by-field V1 schema reference. Read when you need to explain any field's type, constraints, or defaults.
80
+ - `references/verification-policies.md` — All four verify policies with complete YAML examples. Read when helping the user choose or configure verification for a step.
81
+ - `references/feature-patterns.md` — Usage patterns for `context_from`, `iterate`, and `params` with complete YAML examples. Read when the user wants context chaining, fan-out iteration, or parameterized workflows.
82
+ </reference_index>
83
+
84
+ <templates_index>
85
+ Available templates in `templates/`:
86
+
87
+ - `workflow-definition.yaml` — Blank scaffold with all fields shown as comments. Copy and fill for a quick start.
88
+ - `blog-post-pipeline.yaml` — Linear chain with params and content-heuristic verification.
89
+ - `code-audit.yaml` — Iterate-based fan-out with shell-command verification.
90
+ - `release-checklist.yaml` — Diamond dependency graph with human-review verification.
91
+ </templates_index>
92
+
93
+ <output_conventions>
94
+ When assembling the final YAML:
95
+
96
+ 1. Use 2-space indentation consistently.
97
+ 2. Quote string values that contain special YAML characters (`:`, `{`, `}`, `[`, `]`, `#`).
98
+ 3. Always include `version: 1` as the first field.
99
+ 4. Order top-level fields: `version`, `name`, `description`, `params`, `steps`.
100
+ 5. Order step fields: `id`, `name`, `prompt`, `requires`, `produces`, `context_from`, `verify`, `iterate`.
101
+ 6. Write the file to `.gsd/workflow-defs/<name>.yaml`.
102
+ 7. After writing, tell the user: "Run `/gsd workflow validate <name>` to check the definition."
103
+ </output_conventions>
@@ -0,0 +1,128 @@
1
+ <feature_patterns>
2
+ Advanced workflow features: `context_from`, `iterate`, and `params`. Each section includes a complete YAML example.
3
+
4
+ **Feature 1: `context_from` — Context Chaining**
5
+
6
+ Injects artifacts from prior steps as context when the current step runs. The value is an array of step IDs.
7
+
8
+ ```yaml
9
+ version: 1
10
+ name: research-and-synthesize
11
+ steps:
12
+ - id: gather
13
+ name: Gather sources
14
+ prompt: "Find and summarize the top 5 sources on the topic."
15
+ produces:
16
+ - sources.md
17
+
18
+ - id: analyze
19
+ name: Analyze sources
20
+ prompt: "Analyze the gathered sources for key themes."
21
+ requires:
22
+ - gather
23
+ context_from:
24
+ - gather
25
+ produces:
26
+ - analysis.md
27
+
28
+ - id: synthesize
29
+ name: Write synthesis
30
+ prompt: "Synthesize the analysis into a coherent report."
31
+ requires:
32
+ - analyze
33
+ context_from:
34
+ - gather
35
+ - analyze
36
+ produces:
37
+ - report.md
38
+ ```
39
+
40
+ How it works:
41
+ - `context_from: [gather]` means the engine includes artifacts from the `gather` step when executing `analyze`.
42
+ - You can reference multiple prior steps: `context_from: [gather, analyze]`.
43
+ - The referenced steps must exist in the workflow (they are validated as step IDs).
44
+ - `context_from` does not imply a dependency — if you want the step to wait, also add the ID to `requires`.
45
+
46
+ **Feature 2: `iterate` — Fan-Out Iteration**
47
+
48
+ Reads an artifact, applies a regex pattern, and creates one sub-execution per match. The capture group extracts the iteration variable.
49
+
50
+ ```yaml
51
+ version: 1
52
+ name: file-by-file-review
53
+ steps:
54
+ - id: inventory
55
+ name: List files to review
56
+ prompt: "List all TypeScript files in src/ that need review, one per line."
57
+ produces:
58
+ - file-list.txt
59
+
60
+ - id: review
61
+ name: Review each file
62
+ prompt: "Review the file for code quality issues."
63
+ requires:
64
+ - inventory
65
+ iterate:
66
+ source: file-list.txt
67
+ pattern: "^(.+\\.ts)$"
68
+ produces:
69
+ - reviews/
70
+ ```
71
+
72
+ How it works:
73
+ - `source`: Path to an artifact (relative to the run directory). Must not contain `..`.
74
+ - `pattern`: A regex string applied with the global flag. Must contain at least one capture group `(...)`.
75
+ - The engine reads the source artifact, applies the pattern, and creates one execution per match.
76
+ - Each capture group match becomes available as the iteration variable.
77
+ - The regex is validated at definition-load time — invalid regex or missing capture groups are rejected.
78
+
79
+ Pattern requirements:
80
+ - Must be a valid JavaScript regex.
81
+ - Must contain at least one non-lookahead capture group: `(...)` not `(?:...)`.
82
+ - Example valid patterns: `^(.+)$`, `- (.+\.ts)`, `\[(.+?)\]`.
83
+
84
+ **Feature 3: `params` — Parameterized Workflows**
85
+
86
+ Define default parameter values at the top level. Use `{{ key }}` placeholders in step prompts. CLI overrides take precedence.
87
+
88
+ ```yaml
89
+ version: 1
90
+ name: blog-post
91
+ description: Generate a blog post on a configurable topic.
92
+ params:
93
+ topic: "AI in healthcare"
94
+ audience: "technical professionals"
95
+ word_count: "1500"
96
+ steps:
97
+ - id: outline
98
+ name: Create outline
99
+ prompt: "Create a detailed outline for a blog post about {{ topic }} targeting {{ audience }}."
100
+ produces:
101
+ - outline.md
102
+
103
+ - id: draft
104
+ name: Write draft
105
+ prompt: "Write a {{ word_count }}-word blog post about {{ topic }} for {{ audience }} based on the outline."
106
+ requires:
107
+ - outline
108
+ context_from:
109
+ - outline
110
+ produces:
111
+ - draft.md
112
+ verify:
113
+ policy: content-heuristic
114
+ minSize: 500
115
+ ```
116
+
117
+ How it works:
118
+ - `params` is a top-level object mapping string keys to string default values.
119
+ - `{{ key }}` in any step prompt is replaced with the corresponding param value.
120
+ - Merge order: definition `params` (defaults) ← CLI overrides (win).
121
+ - After substitution, any remaining `{{ key }}` that has no value causes an error — all placeholders must resolve.
122
+ - Parameter values must not contain `..` (path traversal guard).
123
+ - Keys in `{{ }}` match `\w+` (letters, digits, underscore).
124
+
125
+ Common usage:
126
+ - Make workflows reusable across different topics, projects, or configurations.
127
+ - Users override defaults at run time: `/gsd workflow run blog-post topic="Rust performance"`.
128
+ </feature_patterns>