gsd-pi 2.37.1 → 2.38.0-dev.63ad7e5

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 (201) 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/resources/extensions/browser-tools/package.json +3 -1
  10. package/dist/resources/extensions/cmux/index.js +55 -1
  11. package/dist/resources/extensions/context7/package.json +1 -1
  12. package/dist/resources/extensions/env-utils.js +29 -0
  13. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  14. package/dist/resources/extensions/google-search/package.json +3 -1
  15. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  16. package/dist/resources/extensions/gsd/auto-dispatch.js +74 -9
  17. package/dist/resources/extensions/gsd/auto-loop.js +68 -97
  18. package/dist/resources/extensions/gsd/auto-post-unit.js +87 -69
  19. package/dist/resources/extensions/gsd/auto-prompts.js +98 -33
  20. package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
  21. package/dist/resources/extensions/gsd/auto-start.js +13 -2
  22. package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
  23. package/dist/resources/extensions/gsd/auto.js +143 -96
  24. package/dist/resources/extensions/gsd/captures.js +9 -1
  25. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  26. package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
  27. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  28. package/dist/resources/extensions/gsd/commands.js +22 -2
  29. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  30. package/dist/resources/extensions/gsd/detection.js +1 -2
  31. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  32. package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
  33. package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
  34. package/dist/resources/extensions/gsd/doctor-format.js +15 -0
  35. package/dist/resources/extensions/gsd/doctor-providers.js +62 -12
  36. package/dist/resources/extensions/gsd/doctor.js +184 -11
  37. package/dist/resources/extensions/gsd/export.js +1 -1
  38. package/dist/resources/extensions/gsd/files.js +43 -2
  39. package/dist/resources/extensions/gsd/forensics.js +1 -1
  40. package/dist/resources/extensions/gsd/index.js +2 -1
  41. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  42. package/dist/resources/extensions/gsd/observability-validator.js +24 -0
  43. package/dist/resources/extensions/gsd/package.json +1 -1
  44. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  45. package/dist/resources/extensions/gsd/preferences-types.js +2 -2
  46. package/dist/resources/extensions/gsd/preferences-validation.js +43 -11
  47. package/dist/resources/extensions/gsd/preferences.js +5 -5
  48. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  49. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
  50. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  51. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  52. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  53. package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  54. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  55. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
  56. package/dist/resources/extensions/gsd/prompts/run-uat.md +25 -10
  57. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  58. package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
  59. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  60. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  61. package/dist/resources/extensions/gsd/state.js +1 -1
  62. package/dist/resources/extensions/gsd/templates/task-plan.md +11 -3
  63. package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
  64. package/dist/resources/extensions/gsd/worktree.js +35 -16
  65. package/dist/resources/extensions/remote-questions/status.js +2 -1
  66. package/dist/resources/extensions/remote-questions/store.js +2 -1
  67. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  68. package/dist/resources/extensions/subagent/index.js +12 -3
  69. package/dist/resources/extensions/subagent/isolation.js +2 -1
  70. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  71. package/dist/resources/extensions/universal-config/package.json +1 -1
  72. package/dist/welcome-screen.d.ts +12 -0
  73. package/dist/welcome-screen.js +53 -0
  74. package/package.json +2 -1
  75. package/packages/pi-ai/dist/env-api-keys.js +13 -0
  76. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  77. package/packages/pi-ai/dist/models.generated.d.ts +172 -0
  78. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  79. package/packages/pi-ai/dist/models.generated.js +172 -0
  80. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  81. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
  82. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
  83. package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
  84. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
  85. package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
  86. package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
  87. package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
  88. package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
  89. package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
  90. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  91. package/packages/pi-ai/dist/providers/anthropic.js +47 -764
  92. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  93. package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
  94. package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
  95. package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
  96. package/packages/pi-ai/dist/types.d.ts +2 -2
  97. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  98. package/packages/pi-ai/dist/types.js.map +1 -1
  99. package/packages/pi-ai/package.json +1 -0
  100. package/packages/pi-ai/src/env-api-keys.ts +14 -0
  101. package/packages/pi-ai/src/models.generated.ts +172 -0
  102. package/packages/pi-ai/src/providers/anthropic-shared.ts +761 -0
  103. package/packages/pi-ai/src/providers/anthropic-vertex.ts +130 -0
  104. package/packages/pi-ai/src/providers/anthropic.ts +76 -868
  105. package/packages/pi-ai/src/providers/register-builtins.ts +7 -0
  106. package/packages/pi-ai/src/types.ts +2 -0
  107. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  109. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  111. package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
  112. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  113. package/packages/pi-coding-agent/package.json +1 -1
  114. package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
  115. package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
  116. package/pkg/package.json +1 -1
  117. package/src/resources/extensions/cmux/index.ts +57 -1
  118. package/src/resources/extensions/env-utils.ts +31 -0
  119. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  120. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  121. package/src/resources/extensions/gsd/auto-dispatch.ts +99 -8
  122. package/src/resources/extensions/gsd/auto-loop.ts +88 -133
  123. package/src/resources/extensions/gsd/auto-post-unit.ts +64 -40
  124. package/src/resources/extensions/gsd/auto-prompts.ts +132 -36
  125. package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
  126. package/src/resources/extensions/gsd/auto-start.ts +18 -2
  127. package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
  128. package/src/resources/extensions/gsd/auto.ts +139 -101
  129. package/src/resources/extensions/gsd/captures.ts +10 -1
  130. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  131. package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
  132. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  133. package/src/resources/extensions/gsd/commands.ts +24 -2
  134. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  135. package/src/resources/extensions/gsd/detection.ts +2 -2
  136. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  137. package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
  138. package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
  139. package/src/resources/extensions/gsd/doctor-format.ts +20 -0
  140. package/src/resources/extensions/gsd/doctor-providers.ts +64 -10
  141. package/src/resources/extensions/gsd/doctor-types.ts +16 -1
  142. package/src/resources/extensions/gsd/doctor.ts +177 -13
  143. package/src/resources/extensions/gsd/export.ts +1 -1
  144. package/src/resources/extensions/gsd/files.ts +47 -2
  145. package/src/resources/extensions/gsd/forensics.ts +1 -1
  146. package/src/resources/extensions/gsd/index.ts +3 -1
  147. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  148. package/src/resources/extensions/gsd/observability-validator.ts +27 -0
  149. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  150. package/src/resources/extensions/gsd/preferences-types.ts +5 -5
  151. package/src/resources/extensions/gsd/preferences-validation.ts +42 -11
  152. package/src/resources/extensions/gsd/preferences.ts +5 -5
  153. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  154. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
  155. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  156. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  157. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  158. package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  159. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  160. package/src/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
  161. package/src/resources/extensions/gsd/prompts/run-uat.md +25 -10
  162. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  163. package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
  164. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  165. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  166. package/src/resources/extensions/gsd/state.ts +1 -1
  167. package/src/resources/extensions/gsd/templates/task-plan.md +11 -3
  168. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  169. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +11 -31
  170. package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
  171. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
  172. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +191 -3
  173. package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -0
  174. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  175. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  176. package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +511 -0
  177. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -0
  178. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  179. package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
  180. package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
  181. package/src/resources/extensions/gsd/types.ts +43 -1
  182. package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
  183. package/src/resources/extensions/gsd/worktree.ts +35 -15
  184. package/src/resources/extensions/remote-questions/status.ts +3 -1
  185. package/src/resources/extensions/remote-questions/store.ts +3 -1
  186. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  187. package/src/resources/extensions/subagent/index.ts +12 -3
  188. package/src/resources/extensions/subagent/isolation.ts +3 -1
  189. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  190. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  191. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  192. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  193. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  194. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  195. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  196. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  197. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  198. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  199. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  200. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  201. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -631,7 +631,7 @@ export function serializePreferencesToFrontmatter(prefs) {
631
631
  "dynamic_routing", "token_profile", "phases", "parallel",
632
632
  "auto_visualize", "auto_report",
633
633
  "verification_commands", "verification_auto_fix", "verification_max_retries",
634
- "search_provider", "compression_strategy", "context_selection",
634
+ "search_provider", "context_selection",
635
635
  ];
636
636
  const seen = new Set();
637
637
  for (const key of orderedKeys) {
@@ -7,6 +7,7 @@ import { existsSync, readFileSync, readdirSync, unlinkSync } from "node:fs";
7
7
  import { homedir } from "node:os";
8
8
  import { join } from "node:path";
9
9
  import { gsdRoot } from "./paths.js";
10
+ const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
10
11
  import { enableDebug } from "./debug-logger.js";
11
12
  import { deriveState } from "./state.js";
12
13
  import { GSDDashboardOverlay } from "./dashboard-overlay.js";
@@ -135,7 +136,7 @@ async function guardRemoteSession(ctx, pi) {
135
136
  }
136
137
  export function registerGSDCommand(pi) {
137
138
  pi.registerCommand("gsd", {
138
- description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|update",
139
+ description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|capture|triage|dispatch|history|undo|rate|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update",
139
140
  getArgumentCompletions: (prefix) => {
140
141
  const subcommands = [
141
142
  { cmd: "help", desc: "Categorized command reference with descriptions" },
@@ -186,7 +187,11 @@ export function registerGSDCommand(pi) {
186
187
  { cmd: "templates", desc: "List available workflow templates" },
187
188
  { cmd: "extensions", desc: "Manage extensions (list, enable, disable, info)" },
188
189
  ];
190
+ const hasTrailingSpace = prefix.endsWith(" ");
189
191
  const parts = prefix.trim().split(/\s+/);
192
+ if (hasTrailingSpace && parts.length >= 1) {
193
+ parts.push("");
194
+ }
190
195
  if (parts.length <= 1) {
191
196
  return subcommands
192
197
  .filter((item) => item.cmd.startsWith(parts[0] ?? ""))
@@ -433,7 +438,7 @@ export function registerGSDCommand(pi) {
433
438
  if (parts.length === 3 && ["enable", "disable", "info"].includes(parts[1])) {
434
439
  const idPrefix = parts[2] ?? "";
435
440
  try {
436
- const extDir = join(homedir(), ".gsd", "agent", "extensions");
441
+ const extDir = join(gsdHome, "agent", "extensions");
437
442
  const ids = [];
438
443
  for (const entry of readdirSync(extDir, { withFileTypes: true })) {
439
444
  if (!entry.isDirectory())
@@ -468,6 +473,10 @@ export function registerGSDCommand(pi) {
468
473
  { cmd: "fix", desc: "Auto-fix detected issues" },
469
474
  { cmd: "heal", desc: "AI-driven deep healing" },
470
475
  { cmd: "audit", desc: "Run health audit without fixing" },
476
+ { cmd: "--dry-run", desc: "Show what --fix would change without applying" },
477
+ { cmd: "--json", desc: "Output report as JSON (CI/tooling friendly)" },
478
+ { cmd: "--build", desc: "Include slow build health check (npm run build)" },
479
+ { cmd: "--test", desc: "Include slow test health check (npm test)" },
471
480
  ];
472
481
  if (parts.length <= 2) {
473
482
  return modes
@@ -491,6 +500,17 @@ export function registerGSDCommand(pi) {
491
500
  .filter((p) => p.cmd.startsWith(phasePrefix))
492
501
  .map((p) => ({ value: `dispatch ${p.cmd}`, label: p.cmd, description: p.desc }));
493
502
  }
503
+ if (parts[0] === "rate" && parts.length <= 2) {
504
+ const tierPrefix = parts[1] ?? "";
505
+ const tiers = [
506
+ { cmd: "over", desc: "Model was overqualified for this task" },
507
+ { cmd: "ok", desc: "Model was appropriate for this task" },
508
+ { cmd: "under", desc: "Model was underqualified for this task" },
509
+ ];
510
+ return tiers
511
+ .filter((t) => t.cmd.startsWith(tierPrefix))
512
+ .map((t) => ({ value: `rate ${t.cmd}`, label: t.cmd, description: t.desc }));
513
+ }
494
514
  return [];
495
515
  },
496
516
  async handler(args, ctx) {
@@ -8,7 +8,6 @@
8
8
  * @see D001 (module location), D002 (200K fallback), D003 (section-boundary truncation)
9
9
  */
10
10
  import { getCharsPerToken } from "./token-counter.js";
11
- import { compressToTarget } from "./prompt-compressor.js";
12
11
  // ─── Budget ratio constants ──────────────────────────────────────────────────
13
12
  // Percentages of total context window allocated to each budget category.
14
13
  // These are applied after tokens→chars conversion.
@@ -132,20 +131,13 @@ export function resolveExecutorContextWindow(registry, preferences, sessionConte
132
131
  return DEFAULT_CONTEXT_WINDOW;
133
132
  }
134
133
  /**
135
- * Smart context reduction: compress first, then truncate if still over budget.
136
- * Returns the content within budget with maximum information preservation.
134
+ * Reduce content to fit within budget using section-boundary truncation.
137
135
  */
138
136
  export function reduceToFit(content, budgetChars) {
139
137
  if (!content || content.length <= budgetChars) {
140
138
  return { content, droppedSections: 0 };
141
139
  }
142
- // Step 1: Try compression
143
- const compressed = compressToTarget(content, budgetChars);
144
- if (compressed.compressedChars <= budgetChars) {
145
- return { content: compressed.content, droppedSections: 0 };
146
- }
147
- // Step 2: Truncate the compressed content at section boundaries
148
- return truncateAtSectionBoundary(compressed.content, budgetChars);
140
+ return truncateAtSectionBoundary(content, budgetChars);
149
141
  }
150
142
  // ─── Internal helpers ────────────────────────────────────────────────────────
151
143
  /**
@@ -9,6 +9,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
9
9
  import { join } from "node:path";
10
10
  import { homedir } from "node:os";
11
11
  import { gsdRoot } from "./paths.js";
12
+ const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
12
13
  // ─── Project File Markers ───────────────────────────────────────────────────────
13
14
  const PROJECT_FILES = [
14
15
  "package.json",
@@ -309,7 +310,6 @@ function detectVerificationCommands(basePath, detectedFiles, packageManager) {
309
310
  * Check if global GSD setup exists (has ~/.gsd/ with preferences).
310
311
  */
311
312
  export function hasGlobalSetup() {
312
- const gsdHome = join(homedir(), ".gsd");
313
313
  return (existsSync(join(gsdHome, "preferences.md")) ||
314
314
  existsSync(join(gsdHome, "PREFERENCES.md")));
315
315
  }
@@ -318,7 +318,6 @@ export function hasGlobalSetup() {
318
318
  * Returns true if ~/.gsd/ doesn't exist or has no preferences or auth.
319
319
  */
320
320
  export function isFirstEverLaunch() {
321
- const gsdHome = join(homedir(), ".gsd");
322
321
  if (!existsSync(gsdHome))
323
322
  return true;
324
323
  // If we have preferences, not first launch
@@ -194,8 +194,6 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
194
194
 
195
195
  - `search_provider`: `"brave"`, `"tavily"`, `"ollama"`, `"native"`, or `"auto"` — selects the search backend for research phases. `"native"` forces Anthropic's built-in web search only; provider values force that backend and disable native search; `"auto"` uses the default heuristic. Default: `"auto"`.
196
196
 
197
- - `compression_strategy`: `"truncate"` or `"compress"` — controls how context that exceeds the budget is reduced. `"truncate"` (default) drops sections from the end. `"compress"` applies heuristic compression before truncating, preserving more content at the cost of some fidelity. Default: `"truncate"`.
198
-
199
197
  - `context_selection`: `"full"` or `"smart"` — controls how files are inlined into context. `"full"` inlines entire files; `"smart"` uses semantic chunking to include only the most relevant sections. Default is derived from `token_profile`.
200
198
 
201
199
  - `parallel`: configures parallel orchestration for running multiple slices concurrently. Keys:
@@ -618,6 +618,88 @@ export async function checkRuntimeHealth(basePath, issues, fixesApplied, shouldF
618
618
  catch {
619
619
  // Non-fatal — external state check failed
620
620
  }
621
+ // ── Metrics ledger integrity ───────────────────────────────────────────
622
+ try {
623
+ const metricsPath = join(root, "metrics.json");
624
+ if (existsSync(metricsPath)) {
625
+ try {
626
+ const raw = readFileSync(metricsPath, "utf-8");
627
+ const ledger = JSON.parse(raw);
628
+ if (ledger.version !== 1 || !Array.isArray(ledger.units)) {
629
+ issues.push({
630
+ severity: "warning",
631
+ code: "metrics_ledger_corrupt",
632
+ scope: "project",
633
+ unitId: "project",
634
+ message: "metrics.json has an unexpected structure (version !== 1 or units is not an array) — metrics data may be unreliable",
635
+ file: ".gsd/metrics.json",
636
+ fixable: false,
637
+ });
638
+ }
639
+ }
640
+ catch {
641
+ issues.push({
642
+ severity: "warning",
643
+ code: "metrics_ledger_corrupt",
644
+ scope: "project",
645
+ unitId: "project",
646
+ message: "metrics.json is not valid JSON — metrics data may be corrupt",
647
+ file: ".gsd/metrics.json",
648
+ fixable: false,
649
+ });
650
+ }
651
+ }
652
+ }
653
+ catch {
654
+ // Non-fatal — metrics check failed
655
+ }
656
+ // ── Large planning file detection ──────────────────────────────────────
657
+ // Files over 100KB can cause LLM context pressure. Report the worst offenders.
658
+ try {
659
+ const MAX_FILE_BYTES = 100 * 1024; // 100KB
660
+ const milestonesPath = milestonesDir(basePath);
661
+ if (existsSync(milestonesPath)) {
662
+ const largeFiles = [];
663
+ function scanForLargeFiles(dir, depth = 0) {
664
+ if (depth > 6)
665
+ return;
666
+ try {
667
+ for (const entry of readdirSync(dir)) {
668
+ const full = join(dir, entry);
669
+ try {
670
+ const s = statSync(full);
671
+ if (s.isDirectory()) {
672
+ scanForLargeFiles(full, depth + 1);
673
+ continue;
674
+ }
675
+ if (entry.endsWith(".md") && s.size > MAX_FILE_BYTES) {
676
+ largeFiles.push({ path: full.replace(basePath + "/", ""), sizeKB: Math.round(s.size / 1024) });
677
+ }
678
+ }
679
+ catch { /* skip entry */ }
680
+ }
681
+ }
682
+ catch { /* skip dir */ }
683
+ }
684
+ scanForLargeFiles(milestonesPath);
685
+ if (largeFiles.length > 0) {
686
+ largeFiles.sort((a, b) => b.sizeKB - a.sizeKB);
687
+ const worst = largeFiles[0];
688
+ issues.push({
689
+ severity: "warning",
690
+ code: "large_planning_file",
691
+ scope: "project",
692
+ unitId: "project",
693
+ message: `${largeFiles.length} planning file(s) exceed 100KB — largest: ${worst.path} (${worst.sizeKB}KB). Large files cause LLM context pressure.`,
694
+ file: worst.path,
695
+ fixable: false,
696
+ });
697
+ }
698
+ }
699
+ }
700
+ catch {
701
+ // Non-fatal — large file scan failed
702
+ }
621
703
  }
622
704
  /**
623
705
  * Build STATE.md markdown content from derived state.
@@ -355,6 +355,63 @@ function checkGitRemote(basePath) {
355
355
  }
356
356
  return { name: "git_remote", status: "ok", message: "Git remote reachable" };
357
357
  }
358
+ /**
359
+ * Check if the project build passes (opt-in slow check, use --build flag).
360
+ * Runs npm run build and reports failure as env_build.
361
+ */
362
+ function checkBuildHealth(basePath) {
363
+ const pkgPath = join(basePath, "package.json");
364
+ if (!existsSync(pkgPath))
365
+ return null;
366
+ try {
367
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
368
+ const buildScript = pkg.scripts?.build;
369
+ if (!buildScript)
370
+ return null;
371
+ const result = tryExec("npm run build 2>&1", basePath);
372
+ if (result === null) {
373
+ return {
374
+ name: "build",
375
+ status: "error",
376
+ message: "Build failed — npm run build exited non-zero",
377
+ detail: "Fix build errors before dispatching work",
378
+ };
379
+ }
380
+ return { name: "build", status: "ok", message: "Build passes" };
381
+ }
382
+ catch {
383
+ return null;
384
+ }
385
+ }
386
+ /**
387
+ * Check if tests pass (opt-in slow check, use --test flag).
388
+ * Runs npm test and reports failures as env_test.
389
+ */
390
+ function checkTestHealth(basePath) {
391
+ const pkgPath = join(basePath, "package.json");
392
+ if (!existsSync(pkgPath))
393
+ return null;
394
+ try {
395
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
396
+ const testScript = pkg.scripts?.test;
397
+ // Skip if no test script or the default placeholder
398
+ if (!testScript || testScript.includes("no test specified"))
399
+ return null;
400
+ const result = tryExec("npm test 2>&1", basePath);
401
+ if (result === null) {
402
+ return {
403
+ name: "test",
404
+ status: "warning",
405
+ message: "Tests failing — npm test exited non-zero",
406
+ detail: "Fix failing tests before shipping",
407
+ };
408
+ }
409
+ return { name: "test", status: "ok", message: "Tests pass" };
410
+ }
411
+ catch {
412
+ return null;
413
+ }
414
+ }
358
415
  // ── Public API ─────────────────────────────────────────────────────────────
359
416
  /**
360
417
  * Run all environment health checks. Returns structured results for
@@ -394,6 +451,24 @@ export function runFullEnvironmentChecks(basePath) {
394
451
  results.push(remoteCheck);
395
452
  return results;
396
453
  }
454
+ /**
455
+ * Run slow opt-in checks (build and/or test).
456
+ * These are never run on the pre-dispatch gate — only on explicit /gsd doctor --build/--test.
457
+ */
458
+ export function runSlowEnvironmentChecks(basePath, options) {
459
+ const results = [];
460
+ if (options?.includeBuild) {
461
+ const buildCheck = checkBuildHealth(basePath);
462
+ if (buildCheck)
463
+ results.push(buildCheck);
464
+ }
465
+ if (options?.includeTests) {
466
+ const testCheck = checkTestHealth(basePath);
467
+ if (testCheck)
468
+ results.push(testCheck);
469
+ }
470
+ return results;
471
+ }
397
472
  /**
398
473
  * Convert environment check results to DoctorIssue format for the doctor pipeline.
399
474
  */
@@ -417,6 +492,9 @@ export async function checkEnvironmentHealth(basePath, issues, options) {
417
492
  const results = options?.includeRemote
418
493
  ? runFullEnvironmentChecks(basePath)
419
494
  : runEnvironmentChecks(basePath);
495
+ if (options?.includeBuild || options?.includeTests) {
496
+ results.push(...runSlowEnvironmentChecks(basePath, options));
497
+ }
420
498
  issues.push(...environmentResultsToDoctorIssues(results));
421
499
  }
422
500
  /**
@@ -69,3 +69,18 @@ export function formatDoctorIssuesForPrompt(issues) {
69
69
  return `- [${prefix}] ${issue.unitId} | ${issue.code} | ${issue.message}${issue.file ? ` | file: ${issue.file}` : ""} | fixable: ${issue.fixable ? "yes" : "no"}`;
70
70
  }).join("\n");
71
71
  }
72
+ /**
73
+ * Serialize a doctor report to JSON — suitable for CI/tooling integration.
74
+ * Usage: /gsd doctor --json
75
+ */
76
+ export function formatDoctorReportJson(report) {
77
+ return JSON.stringify({
78
+ ok: report.ok,
79
+ basePath: report.basePath,
80
+ generatedAt: new Date().toISOString(),
81
+ summary: summarizeDoctorIssues(report.issues),
82
+ issues: report.issues,
83
+ fixesApplied: report.fixesApplied,
84
+ ...(report.timing ? { timing: report.timing } : {}),
85
+ }, null, 2);
86
+ }
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import { existsSync } from "node:fs";
14
14
  import { AuthStorage } from "@gsd/pi-coding-agent";
15
+ import { getEnvApiKey } from "@gsd/pi-ai";
15
16
  import { loadEffectiveGSDPreferences } from "./preferences.js";
16
17
  import { getAuthPath, PROVIDER_REGISTRY } from "./key-manager.js";
17
18
  // ── Model → Provider ID mapping ───────────────────────────────────────────────
@@ -27,12 +28,15 @@ function modelToProviderId(model) {
27
28
  const prefix = model.split("/")[0].toLowerCase();
28
29
  // Map known prefixes to registry IDs
29
30
  const prefixMap = {
31
+ "anthropic-vertex": "anthropic-vertex",
30
32
  openrouter: "openrouter",
31
33
  groq: "groq",
32
34
  mistral: "mistral",
33
35
  google: "google",
36
+ "google-vertex": "google-vertex",
34
37
  anthropic: "anthropic",
35
38
  openai: "openai",
39
+ "github-copilot": "github-copilot",
36
40
  };
37
41
  if (prefixMap[prefix])
38
42
  return prefixMap[prefix];
@@ -65,11 +69,19 @@ function collectConfiguredModelProviders() {
65
69
  }
66
70
  const modelEntries = typeof models === "object" ? Object.values(models) : [];
67
71
  for (const entry of modelEntries) {
68
- const modelId = typeof entry === "string" ? entry
69
- : typeof entry === "object" && entry !== null && "model" in entry
70
- ? String(entry.model)
71
- : null;
72
- if (modelId) {
72
+ if (typeof entry === "string") {
73
+ const pid = modelToProviderId(entry);
74
+ if (pid)
75
+ providers.add(pid);
76
+ continue;
77
+ }
78
+ if (typeof entry === "object" && entry !== null && "model" in entry) {
79
+ const configuredProvider = "provider" in entry ? entry.provider : undefined;
80
+ if (typeof configuredProvider === "string" && configuredProvider.trim().length > 0) {
81
+ providers.add(configuredProvider);
82
+ continue;
83
+ }
84
+ const modelId = String(entry.model);
73
85
  const pid = modelToProviderId(modelId);
74
86
  if (pid)
75
87
  providers.add(pid);
@@ -108,31 +120,69 @@ function resolveKey(providerId) {
108
120
  // auth.json malformed — fall through to env check
109
121
  }
110
122
  }
111
- // Check environment variable
123
+ // Check environment variable using the authoritative env var resolution
124
+ // (handles multi-var lookups like ANTHROPIC_OAUTH_TOKEN || ANTHROPIC_API_KEY,
125
+ // COPILOT_GITHUB_TOKEN || GH_TOKEN || GITHUB_TOKEN, Vertex ADC, Bedrock, etc.)
126
+ if (getEnvApiKey(providerId)) {
127
+ return { found: true, source: "env", backedOff: false };
128
+ }
129
+ // Fall back to PROVIDER_REGISTRY env var for providers not covered by getEnvApiKey
130
+ // (e.g., search providers like Brave, Tavily; tool providers like Jina, Context7)
112
131
  if (info?.envVar && process.env[info.envVar]) {
113
132
  return { found: true, source: "env", backedOff: false };
114
133
  }
115
134
  return { found: false, source: "none", backedOff: false };
116
135
  }
117
136
  // ── Individual check groups ────────────────────────────────────────────────────
137
+ /**
138
+ * Providers that can serve models normally associated with another provider.
139
+ * Key = the provider whose models can be served, Value = alternative providers to check.
140
+ * e.g. GitHub Copilot subscriptions can access Claude and GPT models.
141
+ */
142
+ const PROVIDER_ROUTES = {
143
+ anthropic: ["github-copilot"],
144
+ openai: ["github-copilot"],
145
+ };
118
146
  function checkLlmProviders() {
119
147
  const required = collectConfiguredModelProviders();
120
148
  const results = [];
121
149
  for (const providerId of required) {
122
150
  const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
123
- const label = info?.label ?? providerId;
151
+ const label = providerId === "anthropic-vertex"
152
+ ? "Anthropic Vertex"
153
+ : info?.label ?? providerId;
124
154
  const lookup = resolveKey(providerId);
125
155
  if (!lookup.found) {
126
- const envVar = info?.envVar ?? `${providerId.toUpperCase()}_API_KEY`;
156
+ // Check if a cross-provider can serve this provider's models
157
+ const routes = PROVIDER_ROUTES[providerId];
158
+ const routeProvider = routes?.find(routeId => resolveKey(routeId).found);
159
+ if (routeProvider) {
160
+ const routeInfo = PROVIDER_REGISTRY.find(p => p.id === routeProvider);
161
+ const routeLabel = routeInfo?.label ?? routeProvider;
162
+ results.push({
163
+ name: providerId,
164
+ label,
165
+ category: "llm",
166
+ status: "ok",
167
+ message: `${label} — available via ${routeLabel}`,
168
+ required: true,
169
+ });
170
+ continue;
171
+ }
172
+ const envVar = providerId === "anthropic-vertex"
173
+ ? "ANTHROPIC_VERTEX_PROJECT_ID"
174
+ : info?.envVar ?? `${providerId.toUpperCase()}_API_KEY`;
127
175
  results.push({
128
176
  name: providerId,
129
177
  label,
130
178
  category: "llm",
131
179
  status: "error",
132
- message: `${label} — no API key found`,
133
- detail: info?.hasOAuth
134
- ? `Run /gsd keys to authenticate`
135
- : `Set ${envVar} or run /gsd keys`,
180
+ message: `${label} — not configured`,
181
+ detail: providerId === "anthropic-vertex"
182
+ ? "Set ANTHROPIC_VERTEX_PROJECT_ID and authenticate with Google ADC"
183
+ : info?.hasOAuth
184
+ ? `Run /gsd keys to authenticate`
185
+ : `Set ${envVar} or run /gsd keys`,
136
186
  required: true,
137
187
  });
138
188
  }