gsd-pi 2.37.1 → 2.38.0-dev.29edcdc

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 (239) hide show
  1. package/README.md +1 -1
  2. package/dist/app-paths.js +1 -1
  3. package/dist/cli.js +9 -0
  4. package/dist/extension-discovery.d.ts +5 -3
  5. package/dist/extension-discovery.js +14 -9
  6. package/dist/extension-registry.js +2 -2
  7. package/dist/onboarding.js +1 -0
  8. package/dist/remote-questions-config.js +2 -2
  9. package/dist/resource-loader.js +34 -1
  10. package/dist/resources/extensions/browser-tools/package.json +3 -1
  11. package/dist/resources/extensions/cmux/index.js +55 -1
  12. package/dist/resources/extensions/context7/package.json +1 -1
  13. package/dist/resources/extensions/env-utils.js +29 -0
  14. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  15. package/dist/resources/extensions/github-sync/cli.js +284 -0
  16. package/dist/resources/extensions/github-sync/index.js +73 -0
  17. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  18. package/dist/resources/extensions/github-sync/sync.js +424 -0
  19. package/dist/resources/extensions/github-sync/templates.js +118 -0
  20. package/dist/resources/extensions/github-sync/types.js +7 -0
  21. package/dist/resources/extensions/google-search/package.json +3 -1
  22. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  23. package/dist/resources/extensions/gsd/auto-dispatch.js +75 -10
  24. package/dist/resources/extensions/gsd/auto-loop.js +597 -588
  25. package/dist/resources/extensions/gsd/auto-post-unit.js +111 -68
  26. package/dist/resources/extensions/gsd/auto-prompts.js +114 -45
  27. package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
  28. package/dist/resources/extensions/gsd/auto-start.js +13 -2
  29. package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
  30. package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
  31. package/dist/resources/extensions/gsd/auto.js +143 -96
  32. package/dist/resources/extensions/gsd/captures.js +9 -1
  33. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  34. package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
  35. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  36. package/dist/resources/extensions/gsd/commands.js +24 -3
  37. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  38. package/dist/resources/extensions/gsd/detection.js +1 -2
  39. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  40. package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
  41. package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
  42. package/dist/resources/extensions/gsd/doctor-format.js +15 -0
  43. package/dist/resources/extensions/gsd/doctor-providers.js +62 -12
  44. package/dist/resources/extensions/gsd/doctor.js +204 -12
  45. package/dist/resources/extensions/gsd/exit-command.js +2 -1
  46. package/dist/resources/extensions/gsd/export.js +1 -1
  47. package/dist/resources/extensions/gsd/files.js +47 -2
  48. package/dist/resources/extensions/gsd/forensics.js +1 -1
  49. package/dist/resources/extensions/gsd/git-service.js +15 -12
  50. package/dist/resources/extensions/gsd/guided-flow.js +82 -32
  51. package/dist/resources/extensions/gsd/index.js +24 -20
  52. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  53. package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
  54. package/dist/resources/extensions/gsd/observability-validator.js +24 -0
  55. package/dist/resources/extensions/gsd/package.json +1 -1
  56. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  57. package/dist/resources/extensions/gsd/preferences-types.js +3 -2
  58. package/dist/resources/extensions/gsd/preferences-validation.js +101 -11
  59. package/dist/resources/extensions/gsd/preferences.js +8 -5
  60. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  61. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
  62. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  63. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  64. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  65. package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  66. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  67. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
  68. package/dist/resources/extensions/gsd/prompts/run-uat.md +27 -10
  69. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  70. package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
  71. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  72. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  73. package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
  74. package/dist/resources/extensions/gsd/state.js +1 -1
  75. package/dist/resources/extensions/gsd/templates/task-plan.md +11 -3
  76. package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
  77. package/dist/resources/extensions/gsd/worktree.js +35 -16
  78. package/dist/resources/extensions/mcp-client/index.js +14 -1
  79. package/dist/resources/extensions/remote-questions/status.js +2 -1
  80. package/dist/resources/extensions/remote-questions/store.js +2 -1
  81. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  82. package/dist/resources/extensions/subagent/index.js +12 -3
  83. package/dist/resources/extensions/subagent/isolation.js +2 -1
  84. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  85. package/dist/resources/extensions/universal-config/package.json +1 -1
  86. package/dist/welcome-screen.d.ts +12 -0
  87. package/dist/welcome-screen.js +53 -0
  88. package/package.json +2 -1
  89. package/packages/pi-ai/dist/env-api-keys.js +13 -0
  90. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  91. package/packages/pi-ai/dist/models.generated.d.ts +172 -0
  92. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  93. package/packages/pi-ai/dist/models.generated.js +172 -0
  94. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  95. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
  96. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
  97. package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
  98. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
  99. package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
  100. package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
  101. package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
  102. package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
  103. package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
  104. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  105. package/packages/pi-ai/dist/providers/anthropic.js +47 -764
  106. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  107. package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
  108. package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
  109. package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
  110. package/packages/pi-ai/dist/types.d.ts +2 -2
  111. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  112. package/packages/pi-ai/dist/types.js.map +1 -1
  113. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  114. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  115. package/packages/pi-ai/package.json +1 -0
  116. package/packages/pi-ai/src/env-api-keys.ts +14 -0
  117. package/packages/pi-ai/src/models.generated.ts +172 -0
  118. package/packages/pi-ai/src/providers/anthropic-shared.ts +761 -0
  119. package/packages/pi-ai/src/providers/anthropic-vertex.ts +130 -0
  120. package/packages/pi-ai/src/providers/anthropic.ts +76 -868
  121. package/packages/pi-ai/src/providers/register-builtins.ts +7 -0
  122. package/packages/pi-ai/src/types.ts +2 -0
  123. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  124. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
  126. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  129. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
  132. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  133. package/packages/pi-coding-agent/package.json +1 -1
  134. package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
  135. package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
  136. package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
  137. package/pkg/package.json +1 -1
  138. package/src/resources/extensions/cmux/index.ts +57 -1
  139. package/src/resources/extensions/env-utils.ts +31 -0
  140. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  141. package/src/resources/extensions/github-sync/cli.ts +364 -0
  142. package/src/resources/extensions/github-sync/index.ts +93 -0
  143. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  144. package/src/resources/extensions/github-sync/sync.ts +556 -0
  145. package/src/resources/extensions/github-sync/templates.ts +183 -0
  146. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  147. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  148. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  149. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  150. package/src/resources/extensions/github-sync/types.ts +47 -0
  151. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  152. package/src/resources/extensions/gsd/auto-dispatch.ts +100 -9
  153. package/src/resources/extensions/gsd/auto-loop.ts +484 -546
  154. package/src/resources/extensions/gsd/auto-post-unit.ts +92 -42
  155. package/src/resources/extensions/gsd/auto-prompts.ts +150 -48
  156. package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
  157. package/src/resources/extensions/gsd/auto-start.ts +18 -2
  158. package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
  159. package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
  160. package/src/resources/extensions/gsd/auto.ts +139 -101
  161. package/src/resources/extensions/gsd/captures.ts +10 -1
  162. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  163. package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
  164. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  165. package/src/resources/extensions/gsd/commands.ts +26 -4
  166. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  167. package/src/resources/extensions/gsd/detection.ts +2 -2
  168. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  169. package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
  170. package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
  171. package/src/resources/extensions/gsd/doctor-format.ts +20 -0
  172. package/src/resources/extensions/gsd/doctor-providers.ts +64 -10
  173. package/src/resources/extensions/gsd/doctor-types.ts +16 -1
  174. package/src/resources/extensions/gsd/doctor.ts +199 -14
  175. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  176. package/src/resources/extensions/gsd/export.ts +1 -1
  177. package/src/resources/extensions/gsd/files.ts +50 -3
  178. package/src/resources/extensions/gsd/forensics.ts +1 -1
  179. package/src/resources/extensions/gsd/git-service.ts +20 -10
  180. package/src/resources/extensions/gsd/guided-flow.ts +110 -38
  181. package/src/resources/extensions/gsd/index.ts +24 -17
  182. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  183. package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
  184. package/src/resources/extensions/gsd/observability-validator.ts +27 -0
  185. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  186. package/src/resources/extensions/gsd/preferences-types.ts +9 -5
  187. package/src/resources/extensions/gsd/preferences-validation.ts +92 -11
  188. package/src/resources/extensions/gsd/preferences.ts +8 -5
  189. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  190. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
  191. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  192. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  193. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  194. package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  195. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  196. package/src/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
  197. package/src/resources/extensions/gsd/prompts/run-uat.md +27 -10
  198. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  199. package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
  200. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  201. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  202. package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
  203. package/src/resources/extensions/gsd/state.ts +1 -1
  204. package/src/resources/extensions/gsd/templates/task-plan.md +11 -3
  205. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  206. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
  207. package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
  208. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
  209. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +191 -3
  210. package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -0
  211. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  212. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  213. package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +511 -0
  214. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -0
  215. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  216. package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
  217. package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
  218. package/src/resources/extensions/gsd/types.ts +43 -1
  219. package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
  220. package/src/resources/extensions/gsd/worktree.ts +35 -15
  221. package/src/resources/extensions/mcp-client/index.ts +17 -1
  222. package/src/resources/extensions/remote-questions/status.ts +3 -1
  223. package/src/resources/extensions/remote-questions/store.ts +3 -1
  224. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  225. package/src/resources/extensions/subagent/index.ts +12 -3
  226. package/src/resources/extensions/subagent/isolation.ts +3 -1
  227. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  228. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  229. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  230. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  231. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  232. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  233. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  234. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  235. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  236. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  237. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  238. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  239. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -657,6 +657,81 @@ export async function checkRuntimeHealth(
657
657
  } catch {
658
658
  // Non-fatal — external state check failed
659
659
  }
660
+
661
+ // ── Metrics ledger integrity ───────────────────────────────────────────
662
+ try {
663
+ const metricsPath = join(root, "metrics.json");
664
+ if (existsSync(metricsPath)) {
665
+ try {
666
+ const raw = readFileSync(metricsPath, "utf-8");
667
+ const ledger = JSON.parse(raw);
668
+ if (ledger.version !== 1 || !Array.isArray(ledger.units)) {
669
+ issues.push({
670
+ severity: "warning",
671
+ code: "metrics_ledger_corrupt",
672
+ scope: "project",
673
+ unitId: "project",
674
+ message: "metrics.json has an unexpected structure (version !== 1 or units is not an array) — metrics data may be unreliable",
675
+ file: ".gsd/metrics.json",
676
+ fixable: false,
677
+ });
678
+ }
679
+ } catch {
680
+ issues.push({
681
+ severity: "warning",
682
+ code: "metrics_ledger_corrupt",
683
+ scope: "project",
684
+ unitId: "project",
685
+ message: "metrics.json is not valid JSON — metrics data may be corrupt",
686
+ file: ".gsd/metrics.json",
687
+ fixable: false,
688
+ });
689
+ }
690
+ }
691
+ } catch {
692
+ // Non-fatal — metrics check failed
693
+ }
694
+
695
+ // ── Large planning file detection ──────────────────────────────────────
696
+ // Files over 100KB can cause LLM context pressure. Report the worst offenders.
697
+ try {
698
+ const MAX_FILE_BYTES = 100 * 1024; // 100KB
699
+ const milestonesPath = milestonesDir(basePath);
700
+ if (existsSync(milestonesPath)) {
701
+ const largeFiles: Array<{ path: string; sizeKB: number }> = [];
702
+ function scanForLargeFiles(dir: string, depth = 0): void {
703
+ if (depth > 6) return;
704
+ try {
705
+ for (const entry of readdirSync(dir)) {
706
+ const full = join(dir, entry);
707
+ try {
708
+ const s = statSync(full);
709
+ if (s.isDirectory()) { scanForLargeFiles(full, depth + 1); continue; }
710
+ if (entry.endsWith(".md") && s.size > MAX_FILE_BYTES) {
711
+ largeFiles.push({ path: full.replace(basePath + "/", ""), sizeKB: Math.round(s.size / 1024) });
712
+ }
713
+ } catch { /* skip entry */ }
714
+ }
715
+ } catch { /* skip dir */ }
716
+ }
717
+ scanForLargeFiles(milestonesPath);
718
+ if (largeFiles.length > 0) {
719
+ largeFiles.sort((a, b) => b.sizeKB - a.sizeKB);
720
+ const worst = largeFiles[0]!;
721
+ issues.push({
722
+ severity: "warning",
723
+ code: "large_planning_file",
724
+ scope: "project",
725
+ unitId: "project",
726
+ message: `${largeFiles.length} planning file(s) exceed 100KB — largest: ${worst.path} (${worst.sizeKB}KB). Large files cause LLM context pressure.`,
727
+ file: worst.path,
728
+ fixable: false,
729
+ });
730
+ }
731
+ }
732
+ } catch {
733
+ // Non-fatal — large file scan failed
734
+ }
660
735
  }
661
736
 
662
737
  /**
@@ -407,6 +407,63 @@ function checkGitRemote(basePath: string): EnvironmentCheckResult | null {
407
407
  return { name: "git_remote", status: "ok", message: "Git remote reachable" };
408
408
  }
409
409
 
410
+ /**
411
+ * Check if the project build passes (opt-in slow check, use --build flag).
412
+ * Runs npm run build and reports failure as env_build.
413
+ */
414
+ function checkBuildHealth(basePath: string): EnvironmentCheckResult | null {
415
+ const pkgPath = join(basePath, "package.json");
416
+ if (!existsSync(pkgPath)) return null;
417
+
418
+ try {
419
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
420
+ const buildScript = pkg.scripts?.build;
421
+ if (!buildScript) return null;
422
+
423
+ const result = tryExec("npm run build 2>&1", basePath);
424
+ if (result === null) {
425
+ return {
426
+ name: "build",
427
+ status: "error",
428
+ message: "Build failed — npm run build exited non-zero",
429
+ detail: "Fix build errors before dispatching work",
430
+ };
431
+ }
432
+ return { name: "build", status: "ok", message: "Build passes" };
433
+ } catch {
434
+ return null;
435
+ }
436
+ }
437
+
438
+ /**
439
+ * Check if tests pass (opt-in slow check, use --test flag).
440
+ * Runs npm test and reports failures as env_test.
441
+ */
442
+ function checkTestHealth(basePath: string): EnvironmentCheckResult | null {
443
+ const pkgPath = join(basePath, "package.json");
444
+ if (!existsSync(pkgPath)) return null;
445
+
446
+ try {
447
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
448
+ const testScript = pkg.scripts?.test;
449
+ // Skip if no test script or the default placeholder
450
+ if (!testScript || testScript.includes("no test specified")) return null;
451
+
452
+ const result = tryExec("npm test 2>&1", basePath);
453
+ if (result === null) {
454
+ return {
455
+ name: "test",
456
+ status: "warning",
457
+ message: "Tests failing — npm test exited non-zero",
458
+ detail: "Fix failing tests before shipping",
459
+ };
460
+ }
461
+ return { name: "test", status: "ok", message: "Tests pass" };
462
+ } catch {
463
+ return null;
464
+ }
465
+ }
466
+
410
467
  // ── Public API ─────────────────────────────────────────────────────────────
411
468
 
412
469
  /**
@@ -454,6 +511,26 @@ export function runFullEnvironmentChecks(basePath: string): EnvironmentCheckResu
454
511
  return results;
455
512
  }
456
513
 
514
+ /**
515
+ * Run slow opt-in checks (build and/or test).
516
+ * These are never run on the pre-dispatch gate — only on explicit /gsd doctor --build/--test.
517
+ */
518
+ export function runSlowEnvironmentChecks(
519
+ basePath: string,
520
+ options?: { includeBuild?: boolean; includeTests?: boolean },
521
+ ): EnvironmentCheckResult[] {
522
+ const results: EnvironmentCheckResult[] = [];
523
+ if (options?.includeBuild) {
524
+ const buildCheck = checkBuildHealth(basePath);
525
+ if (buildCheck) results.push(buildCheck);
526
+ }
527
+ if (options?.includeTests) {
528
+ const testCheck = checkTestHealth(basePath);
529
+ if (testCheck) results.push(testCheck);
530
+ }
531
+ return results;
532
+ }
533
+
457
534
  /**
458
535
  * Convert environment check results to DoctorIssue format for the doctor pipeline.
459
536
  */
@@ -477,12 +554,16 @@ export function environmentResultsToDoctorIssues(results: EnvironmentCheckResult
477
554
  export async function checkEnvironmentHealth(
478
555
  basePath: string,
479
556
  issues: DoctorIssue[],
480
- options?: { includeRemote?: boolean },
557
+ options?: { includeRemote?: boolean; includeBuild?: boolean; includeTests?: boolean },
481
558
  ): Promise<void> {
482
559
  const results = options?.includeRemote
483
560
  ? runFullEnvironmentChecks(basePath)
484
561
  : runEnvironmentChecks(basePath);
485
562
 
563
+ if (options?.includeBuild || options?.includeTests) {
564
+ results.push(...runSlowEnvironmentChecks(basePath, options));
565
+ }
566
+
486
567
  issues.push(...environmentResultsToDoctorIssues(results));
487
568
  }
488
569
 
@@ -76,3 +76,23 @@ export function formatDoctorIssuesForPrompt(issues: DoctorIssue[]): string {
76
76
  return `- [${prefix}] ${issue.unitId} | ${issue.code} | ${issue.message}${issue.file ? ` | file: ${issue.file}` : ""} | fixable: ${issue.fixable ? "yes" : "no"}`;
77
77
  }).join("\n");
78
78
  }
79
+
80
+ /**
81
+ * Serialize a doctor report to JSON — suitable for CI/tooling integration.
82
+ * Usage: /gsd doctor --json
83
+ */
84
+ export function formatDoctorReportJson(report: DoctorReport): string {
85
+ return JSON.stringify(
86
+ {
87
+ ok: report.ok,
88
+ basePath: report.basePath,
89
+ generatedAt: new Date().toISOString(),
90
+ summary: summarizeDoctorIssues(report.issues),
91
+ issues: report.issues,
92
+ fixesApplied: report.fixesApplied,
93
+ ...(report.timing ? { timing: report.timing } : {}),
94
+ },
95
+ null,
96
+ 2,
97
+ );
98
+ }
@@ -14,6 +14,7 @@
14
14
  import { existsSync } from "node:fs";
15
15
  import { join } from "node:path";
16
16
  import { AuthStorage } from "@gsd/pi-coding-agent";
17
+ import { getEnvApiKey } from "@gsd/pi-ai";
17
18
  import { loadEffectiveGSDPreferences } from "./preferences.js";
18
19
  import { getAuthPath, PROVIDER_REGISTRY, type ProviderCategory } from "./key-manager.js";
19
20
 
@@ -50,12 +51,15 @@ function modelToProviderId(model: string): string | null {
50
51
  const prefix = model.split("/")[0].toLowerCase();
51
52
  // Map known prefixes to registry IDs
52
53
  const prefixMap: Record<string, string> = {
54
+ "anthropic-vertex": "anthropic-vertex",
53
55
  openrouter: "openrouter",
54
56
  groq: "groq",
55
57
  mistral: "mistral",
56
58
  google: "google",
59
+ "google-vertex": "google-vertex",
57
60
  anthropic: "anthropic",
58
61
  openai: "openai",
62
+ "github-copilot": "github-copilot",
59
63
  };
60
64
  if (prefixMap[prefix]) return prefixMap[prefix];
61
65
  }
@@ -86,11 +90,20 @@ function collectConfiguredModelProviders(): Set<string> {
86
90
 
87
91
  const modelEntries = typeof models === "object" ? Object.values(models) : [];
88
92
  for (const entry of modelEntries) {
89
- const modelId = typeof entry === "string" ? entry
90
- : typeof entry === "object" && entry !== null && "model" in entry
91
- ? String((entry as { model: unknown }).model)
92
- : null;
93
- if (modelId) {
93
+ if (typeof entry === "string") {
94
+ const pid = modelToProviderId(entry);
95
+ if (pid) providers.add(pid);
96
+ continue;
97
+ }
98
+
99
+ if (typeof entry === "object" && entry !== null && "model" in entry) {
100
+ const configuredProvider = "provider" in entry ? (entry as { provider?: unknown }).provider : undefined;
101
+ if (typeof configuredProvider === "string" && configuredProvider.trim().length > 0) {
102
+ providers.add(configuredProvider);
103
+ continue;
104
+ }
105
+
106
+ const modelId = String((entry as { model: unknown }).model);
94
107
  const pid = modelToProviderId(modelId);
95
108
  if (pid) providers.add(pid);
96
109
  }
@@ -139,7 +152,15 @@ function resolveKey(providerId: string): KeyLookup {
139
152
  }
140
153
  }
141
154
 
142
- // Check environment variable
155
+ // Check environment variable using the authoritative env var resolution
156
+ // (handles multi-var lookups like ANTHROPIC_OAUTH_TOKEN || ANTHROPIC_API_KEY,
157
+ // COPILOT_GITHUB_TOKEN || GH_TOKEN || GITHUB_TOKEN, Vertex ADC, Bedrock, etc.)
158
+ if (getEnvApiKey(providerId)) {
159
+ return { found: true, source: "env", backedOff: false };
160
+ }
161
+
162
+ // Fall back to PROVIDER_REGISTRY env var for providers not covered by getEnvApiKey
163
+ // (e.g., search providers like Brave, Tavily; tool providers like Jina, Context7)
143
164
  if (info?.envVar && process.env[info.envVar]) {
144
165
  return { found: true, source: "env", backedOff: false };
145
166
  }
@@ -149,24 +170,57 @@ function resolveKey(providerId: string): KeyLookup {
149
170
 
150
171
  // ── Individual check groups ────────────────────────────────────────────────────
151
172
 
173
+ /**
174
+ * Providers that can serve models normally associated with another provider.
175
+ * Key = the provider whose models can be served, Value = alternative providers to check.
176
+ * e.g. GitHub Copilot subscriptions can access Claude and GPT models.
177
+ */
178
+ const PROVIDER_ROUTES: Record<string, string[]> = {
179
+ anthropic: ["github-copilot"],
180
+ openai: ["github-copilot"],
181
+ };
182
+
152
183
  function checkLlmProviders(): ProviderCheckResult[] {
153
184
  const required = collectConfiguredModelProviders();
154
185
  const results: ProviderCheckResult[] = [];
155
186
 
156
187
  for (const providerId of required) {
157
188
  const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
158
- const label = info?.label ?? providerId;
189
+ const label = providerId === "anthropic-vertex"
190
+ ? "Anthropic Vertex"
191
+ : info?.label ?? providerId;
159
192
  const lookup = resolveKey(providerId);
160
193
 
161
194
  if (!lookup.found) {
162
- const envVar = info?.envVar ?? `${providerId.toUpperCase()}_API_KEY`;
195
+ // Check if a cross-provider can serve this provider's models
196
+ const routes = PROVIDER_ROUTES[providerId];
197
+ const routeProvider = routes?.find(routeId => resolveKey(routeId).found);
198
+ if (routeProvider) {
199
+ const routeInfo = PROVIDER_REGISTRY.find(p => p.id === routeProvider);
200
+ const routeLabel = routeInfo?.label ?? routeProvider;
201
+ results.push({
202
+ name: providerId,
203
+ label,
204
+ category: "llm",
205
+ status: "ok",
206
+ message: `${label} — available via ${routeLabel}`,
207
+ required: true,
208
+ });
209
+ continue;
210
+ }
211
+
212
+ const envVar = providerId === "anthropic-vertex"
213
+ ? "ANTHROPIC_VERTEX_PROJECT_ID"
214
+ : info?.envVar ?? `${providerId.toUpperCase()}_API_KEY`;
163
215
  results.push({
164
216
  name: providerId,
165
217
  label,
166
218
  category: "llm",
167
219
  status: "error",
168
- message: `${label} — no API key found`,
169
- detail: info?.hasOAuth
220
+ message: `${label} — not configured`,
221
+ detail: providerId === "anthropic-vertex"
222
+ ? "Set ANTHROPIC_VERTEX_PROJECT_ID and authenticate with Google ADC"
223
+ : info?.hasOAuth
170
224
  ? `Run /gsd keys to authenticate`
171
225
  : `Set ${envVar} or run /gsd keys`,
172
226
  required: true,
@@ -53,7 +53,20 @@ export type DoctorIssueCode =
53
53
  | "stranded_lock_directory"
54
54
  // Git / worktree integrity checks
55
55
  | "integration_branch_missing"
56
- | "worktree_directory_orphaned";
56
+ | "worktree_directory_orphaned"
57
+ // GSD state structural checks
58
+ | "circular_slice_dependency"
59
+ | "orphaned_slice_directory"
60
+ | "duplicate_task_id"
61
+ | "task_file_not_in_plan"
62
+ | "stale_replan_file"
63
+ | "future_timestamp"
64
+ // Runtime data integrity
65
+ | "metrics_ledger_corrupt"
66
+ | "large_planning_file"
67
+ // Slow environment checks (opt-in via --build / --test flags)
68
+ | "env_build"
69
+ | "env_test";
57
70
 
58
71
  /**
59
72
  * Issue codes that represent expected completion-transition states.
@@ -83,6 +96,8 @@ export interface DoctorReport {
83
96
  basePath: string;
84
97
  issues: DoctorIssue[];
85
98
  fixesApplied: string[];
99
+ /** Per-domain check durations in milliseconds. Present on explicit /gsd doctor runs. */
100
+ timing?: { git: number; runtime: number; environment: number; gsdState: number };
86
101
  }
87
102
 
88
103
  export interface DoctorSummary {