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
@@ -2,7 +2,7 @@ import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, rmSync,
2
2
  import { basename, dirname, join, sep } from "node:path";
3
3
 
4
4
  import type { DoctorIssue, DoctorIssueCode } from "./doctor-types.js";
5
- import { readRepoMeta, externalProjectsRoot } from "./repo-identity.js";
5
+ import { readRepoMeta, externalProjectsRoot, cleanNumberedGsdVariants } from "./repo-identity.js";
6
6
  import { loadFile, parseRoadmap } from "./files.js";
7
7
  import { resolveMilestoneFile, milestonesDir, gsdRoot, resolveGsdRootFile, relGsdRootFile } from "./paths.js";
8
8
  import { deriveState, isMilestoneComplete } from "./state.js";
@@ -776,6 +776,37 @@ export async function checkRuntimeHealth(
776
776
  // Non-fatal — external state check failed
777
777
  }
778
778
 
779
+ // ── Numbered .gsd collision variants (#2205) ───────────────────────────
780
+ // macOS APFS can create ".gsd 2", ".gsd 3" etc. when a directory blocks
781
+ // symlink creation. These must be removed so the canonical .gsd is used.
782
+ try {
783
+ const variantPattern = /^\.gsd \d+$/;
784
+ const entries = readdirSync(basePath);
785
+ const variants = entries.filter(e => variantPattern.test(e));
786
+ if (variants.length > 0) {
787
+ for (const v of variants) {
788
+ issues.push({
789
+ severity: "warning",
790
+ code: "numbered_gsd_variant",
791
+ scope: "project",
792
+ unitId: "project",
793
+ message: `Found macOS collision variant "${v}" — this can cause GSD state to appear deleted.`,
794
+ file: v,
795
+ fixable: true,
796
+ });
797
+ }
798
+
799
+ if (shouldFix("numbered_gsd_variant")) {
800
+ const removed = cleanNumberedGsdVariants(basePath);
801
+ for (const name of removed) {
802
+ fixesApplied.push(`removed numbered .gsd variant: ${name}`);
803
+ }
804
+ }
805
+ }
806
+ } catch {
807
+ // Non-fatal — variant check failed
808
+ }
809
+
779
810
  // ── Metrics ledger integrity ───────────────────────────────────────────
780
811
  try {
781
812
  const metricsPath = join(root, "metrics.json");
@@ -305,11 +305,24 @@ function checkOptionalProviders(): ProviderCheckResult[] {
305
305
  const optional = ["brave", "tavily", "jina", "context7"] as const;
306
306
  const results: ProviderCheckResult[] = [];
307
307
 
308
+ // Determine which search providers are configured so we can suppress
309
+ // "not configured" noise for alternative search providers when at least
310
+ // one is already active (e.g. don't warn about missing BRAVE_API_KEY
311
+ // when Tavily is configured).
312
+ const searchProviderIds = ["brave", "tavily"] as const;
313
+ const hasAnySearchProvider = searchProviderIds.some(id => resolveKey(id).found);
314
+
308
315
  for (const providerId of optional) {
309
316
  const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
310
317
  if (!info) continue;
311
318
 
312
319
  const lookup = resolveKey(providerId);
320
+
321
+ // Skip unconfigured search providers when another search provider is active
322
+ if (!lookup.found && hasAnySearchProvider && info.category === "search") {
323
+ continue;
324
+ }
325
+
313
326
  results.push({
314
327
  name: providerId,
315
328
  label: info.label,
@@ -33,6 +33,7 @@ export type DoctorIssueCode =
33
33
  | "unresolvable_dependency"
34
34
  | "failed_migration"
35
35
  | "broken_symlink"
36
+ | "numbered_gsd_variant"
36
37
  // Environment health checks (#1221)
37
38
  | "env_node_version"
38
39
  | "env_dependencies"
@@ -0,0 +1,57 @@
1
+ /**
2
+ * engine-resolver.ts — Route sessions to engine/policy pairs.
3
+ *
4
+ * Routes `null` and `"dev"` engine IDs to the DevWorkflowEngine/DevExecutionPolicy
5
+ * pair. Any other non-null engine ID is treated as a custom workflow engine that
6
+ * reads its state from an `activeRunDir`. Respects `GSD_ENGINE_BYPASS=1` kill
7
+ * switch to skip the engine layer entirely.
8
+ */
9
+
10
+ import type { WorkflowEngine } from "./workflow-engine.js";
11
+ import type { ExecutionPolicy } from "./execution-policy.js";
12
+ import { DevWorkflowEngine } from "./dev-workflow-engine.js";
13
+ import { DevExecutionPolicy } from "./dev-execution-policy.js";
14
+ import { CustomWorkflowEngine } from "./custom-workflow-engine.js";
15
+ import { CustomExecutionPolicy } from "./custom-execution-policy.js";
16
+
17
+ /** A resolved engine + policy pair ready for the auto-loop. */
18
+ export interface ResolvedEngine {
19
+ engine: WorkflowEngine;
20
+ policy: ExecutionPolicy;
21
+ }
22
+
23
+ /**
24
+ * Resolve an engine/policy pair for the given session.
25
+ *
26
+ * - `null` or `"dev"` → DevWorkflowEngine + DevExecutionPolicy
27
+ * - any other non-null ID → CustomWorkflowEngine(activeRunDir) + CustomExecutionPolicy()
28
+ * (requires activeRunDir to be a non-empty string)
29
+ *
30
+ * Note: `GSD_ENGINE_BYPASS=1` is checked in autoLoop before calling this function.
31
+ */
32
+ export function resolveEngine(
33
+ session: { activeEngineId: string | null; activeRunDir?: string | null },
34
+ ): ResolvedEngine {
35
+ const { activeEngineId, activeRunDir } = session;
36
+
37
+ if (activeEngineId === null || activeEngineId === "dev") {
38
+ return {
39
+ engine: new DevWorkflowEngine(),
40
+ policy: new DevExecutionPolicy(),
41
+ };
42
+ }
43
+
44
+ // Any non-null, non-"dev" engine ID is a custom workflow engine.
45
+ // activeRunDir is required — the engine reads GRAPH.yaml from it.
46
+ if (!activeRunDir || typeof activeRunDir !== "string") {
47
+ throw new Error(
48
+ `Custom engine "${activeEngineId}" requires activeRunDir to be a non-empty string, ` +
49
+ `got: ${JSON.stringify(activeRunDir)}`,
50
+ );
51
+ }
52
+
53
+ return {
54
+ engine: new CustomWorkflowEngine(activeRunDir),
55
+ policy: new CustomExecutionPolicy(activeRunDir),
56
+ };
57
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * engine-types.ts — Engine-polymorphic type contracts.
3
+ *
4
+ * LEAF NODE: This file must have ZERO imports from any GSD module.
5
+ * Only `node:` imports are permitted. All engine/policy interfaces
6
+ * depend on these types; nothing here depends on GSD internals.
7
+ */
8
+
9
+ /** Snapshot of engine state at a point in time. */
10
+ export interface EngineState {
11
+ phase: string;
12
+ currentMilestoneId: string | null;
13
+ activeSliceId: string | null;
14
+ activeTaskId: string | null;
15
+ isComplete: boolean;
16
+ /** Opaque engine-specific state — never narrowed to a GSD-specific type. */
17
+ raw: unknown;
18
+ }
19
+
20
+ /** A unit of work the engine wants the agent to execute. */
21
+ export interface StepContract {
22
+ unitType: string;
23
+ unitId: string;
24
+ prompt: string;
25
+ }
26
+
27
+ /** UI-facing metadata for progress display. */
28
+ export interface DisplayMetadata {
29
+ engineLabel: string;
30
+ currentPhase: string;
31
+ progressSummary: string;
32
+ stepCount: { completed: number; total: number } | null;
33
+ }
34
+
35
+ /**
36
+ * Discriminated union: what the engine tells the loop to do next.
37
+ *
38
+ * - `dispatch` — execute a step
39
+ * - `stop` — halt the loop with a reason and severity
40
+ * - `skip` — nothing to do right now, advance without executing
41
+ */
42
+ export type EngineDispatchAction =
43
+ | { action: "dispatch"; step: StepContract }
44
+ | { action: "stop"; reason: string; level: "info" | "warning" | "error" }
45
+ | { action: "skip" };
46
+
47
+ /** Outcome of reconciling state after a step completes. */
48
+ export interface ReconcileResult {
49
+ outcome: "continue" | "milestone-complete" | "pause" | "stop";
50
+ reason?: string;
51
+ }
52
+
53
+ /** Recovery strategy when a step fails. */
54
+ export interface RecoveryAction {
55
+ outcome: "retry" | "skip" | "stop" | "pause";
56
+ reason?: string;
57
+ }
58
+
59
+ /** Result of closing out a completed unit. */
60
+ export interface CloseoutResult {
61
+ committed: boolean;
62
+ artifacts: string[];
63
+ }
64
+
65
+ /** Record of a completed execution step. */
66
+ export interface CompletedStep {
67
+ unitType: string;
68
+ unitId: string;
69
+ startedAt: number;
70
+ finishedAt: number;
71
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * execution-policy.ts — ExecutionPolicy interface.
3
+ *
4
+ * Defines the policy layer that governs model selection, verification,
5
+ * recovery, and closeout for each execution step. Imports only from
6
+ * the leaf-node engine-types.
7
+ */
8
+
9
+ import type { RecoveryAction, CloseoutResult } from "./engine-types.js";
10
+
11
+ /** Policy governing how each step is executed, verified, and closed out. */
12
+ export interface ExecutionPolicy {
13
+ /** Prepare the workspace before a milestone begins (e.g. worktree setup). */
14
+ prepareWorkspace(basePath: string, milestoneId: string): Promise<void>;
15
+
16
+ /** Select the model tier for a given unit. Returns null to use defaults. */
17
+ selectModel(
18
+ unitType: string,
19
+ unitId: string,
20
+ context: { basePath: string },
21
+ ): Promise<{ tier: string; modelDowngraded: boolean } | null>;
22
+
23
+ /** Verify unit output. Returns disposition for the loop. */
24
+ verify(
25
+ unitType: string,
26
+ unitId: string,
27
+ context: { basePath: string },
28
+ ): Promise<"continue" | "retry" | "pause">;
29
+
30
+ /** Determine recovery action when a unit fails. */
31
+ recover(
32
+ unitType: string,
33
+ unitId: string,
34
+ context: { basePath: string },
35
+ ): Promise<RecoveryAction>;
36
+
37
+ /** Close out a completed unit (commit, snapshot, artifact capture). */
38
+ closeout(
39
+ unitType: string,
40
+ unitId: string,
41
+ context: { basePath: string; startedAt: number },
42
+ ): Promise<CloseoutResult>;
43
+ }
@@ -30,6 +30,9 @@ import { loadPrompt } from "./prompt-loader.js";
30
30
  import { gsdRoot } from "./paths.js";
31
31
  import { formatDuration } from "../shared/format-utils.js";
32
32
  import { getAutoWorktreePath } from "./auto-worktree.js";
33
+ import { loadEffectiveGSDPreferences, loadGlobalGSDPreferences, getGlobalGSDPreferencesPath } from "./preferences.js";
34
+ import { showNextAction } from "../shared/tui.js";
35
+ import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./commands-prefs-wizard.js";
33
36
 
34
37
  // ─── Types ────────────────────────────────────────────────────────────────────
35
38
 
@@ -67,6 +70,71 @@ interface ForensicReport {
67
70
  recentUnits: { type: string; id: string; cost: number; duration: number; model: string; finishedAt: number }[];
68
71
  }
69
72
 
73
+ // ─── Duplicate Detection ──────────────────────────────────────────────────────
74
+
75
+ const DEDUP_PROMPT_SECTION = `
76
+ ## Duplicate Detection (REQUIRED before issue creation)
77
+
78
+ Before offering to create a GitHub issue, you MUST search for existing issues and PRs that may already address this bug. This step uses the user's AI tokens for analysis.
79
+
80
+ ### Search Steps
81
+
82
+ 1. **Search closed issues** for similar keywords from your diagnosis:
83
+ \`\`\`
84
+ gh issue list --repo gsd-build/gsd-2 --state closed --search "<keywords from root cause>" --limit 20
85
+ \`\`\`
86
+
87
+ 2. **Search open PRs** that might contain the fix:
88
+ \`\`\`
89
+ gh pr list --repo gsd-build/gsd-2 --state open --search "<keywords>" --limit 10
90
+ \`\`\`
91
+
92
+ 3. **Search merged PRs** that may have already fixed this:
93
+ \`\`\`
94
+ gh pr list --repo gsd-build/gsd-2 --state merged --search "<keywords>" --limit 10
95
+ \`\`\`
96
+
97
+ ### Analysis
98
+
99
+ For each result, compare it against your root-cause diagnosis:
100
+ - Does the issue describe the same code path or file?
101
+ - Does the PR modify the same file:line you identified?
102
+ - Is the symptom description semantically similar even if keywords differ?
103
+
104
+ ### Present Findings
105
+
106
+ If you find potential matches, present them to the user:
107
+
108
+ 1. **"Already fixed by PR #X — skip issue creation"** — when a merged PR or closed issue clearly addresses the same root cause. Explain why you believe it matches.
109
+ 2. **"Add my findings to existing issue #Y"** — when an open issue exists for the same bug. Use \`gh issue comment #Y --repo gsd-build/gsd-2\` to add forensic evidence.
110
+ 3. **"Create new issue anyway"** — when existing results do not cover this specific failure.
111
+
112
+ Only proceed to issue creation if no matches were found OR the user explicitly chooses "Create new issue anyway".
113
+ `;
114
+
115
+ async function writeForensicsDedupPref(ctx: ExtensionCommandContext, enabled: boolean): Promise<void> {
116
+ const prefsPath = getGlobalGSDPreferencesPath();
117
+ await ensurePreferencesFile(prefsPath, ctx, "global");
118
+ const existing = loadGlobalGSDPreferences();
119
+ const prefs: Record<string, unknown> = existing?.preferences ? { ...existing.preferences } : {};
120
+ prefs.version = prefs.version || 1;
121
+ prefs.forensics_dedup = enabled;
122
+
123
+ const frontmatter = serializePreferencesToFrontmatter(prefs);
124
+ const raw = existsSync(prefsPath) ? readFileSync(prefsPath, "utf-8") : "";
125
+ let body = "\n# GSD Skill Preferences\n\nSee `~/.gsd/agent/extensions/gsd/docs/preferences-reference.md` for full field documentation and examples.\n";
126
+ const start = raw.startsWith("---\n") ? 4 : raw.startsWith("---\r\n") ? 5 : -1;
127
+ if (start !== -1) {
128
+ const closingIdx = raw.indexOf("\n---", start);
129
+ if (closingIdx !== -1) {
130
+ const after = raw.slice(closingIdx + 4);
131
+ if (after.trim()) body = after;
132
+ }
133
+ }
134
+
135
+ writeFileSync(prefsPath, `---\n${frontmatter}---${body}`, "utf-8");
136
+ }
137
+
70
138
  // ─── Entry Point ──────────────────────────────────────────────────────────────
71
139
 
72
140
  export async function handleForensics(
@@ -98,6 +166,29 @@ export async function handleForensics(
98
166
  return;
99
167
  }
100
168
 
169
+ // ─── Duplicate detection opt-in ─────────────────────────────────────────────
170
+ const effectivePrefs = loadEffectiveGSDPreferences()?.preferences;
171
+ let dedupEnabled = effectivePrefs?.forensics_dedup === true;
172
+
173
+ if (effectivePrefs?.forensics_dedup === undefined) {
174
+ const choice = await showNextAction(ctx, {
175
+ title: "Duplicate detection available",
176
+ summary: ["Before filing a GitHub issue, forensics can search existing issues and PRs to avoid duplicates.", "This uses additional AI tokens for analysis."],
177
+ actions: [
178
+ { id: "enable", label: "Enable duplicate detection", description: "Search issues/PRs before filing (recommended)", recommended: true },
179
+ { id: "skip", label: "Skip for now", description: "File without checking for duplicates" },
180
+ ],
181
+ notYetMessage: "You can enable this later via preferences (forensics_dedup: true).",
182
+ });
183
+
184
+ if (choice === "enable") {
185
+ await writeForensicsDedupPref(ctx, true);
186
+ dedupEnabled = true;
187
+ }
188
+ }
189
+
190
+ const dedupSection = dedupEnabled ? DEDUP_PROMPT_SECTION : "";
191
+
101
192
  ctx.ui.notify("Building forensic report...", "info");
102
193
 
103
194
  const report = await buildForensicReport(basePath);
@@ -117,6 +208,7 @@ export async function handleForensics(
117
208
  problemDescription,
118
209
  forensicData,
119
210
  gsdSourceDir,
211
+ dedupSection,
120
212
  });
121
213
 
122
214
  ctx.ui.notify(`Forensic report saved: ${relative(basePath, savedPath)}`, "info");
@@ -8,4 +8,5 @@ export const GIT_NO_PROMPT_ENV = {
8
8
  GIT_TERMINAL_PROMPT: "0",
9
9
  GIT_ASKPASS: "",
10
10
  GIT_SVN_ID: "",
11
+ LC_ALL: "C", // force English git output so stderr string checks work on all locales (#1997)
11
12
  };
@@ -245,7 +245,6 @@ export function writeIntegrationBranch(
245
245
  basePath: string,
246
246
  milestoneId: string,
247
247
  branch: string,
248
- _options?: { commitDocs?: boolean },
249
248
  ): void {
250
249
  // Don't record slice branches as the integration target
251
250
  if (SLICE_BRANCH_RE.test(branch)) return;
@@ -137,7 +137,7 @@ export function hasGitTrackedGsdFiles(basePath: string): boolean {
137
137
  */
138
138
  export function ensureGitignore(
139
139
  basePath: string,
140
- options?: { manageGitignore?: boolean; commitDocs?: boolean },
140
+ options?: { manageGitignore?: boolean },
141
141
  ): boolean {
142
142
  // If manage_gitignore is explicitly false, do not touch .gitignore at all
143
143
  if (options?.manageGitignore === false) return false;