gsd-pi 2.38.0-dev.eeb3520 → 2.39.0-dev.64cd3ed

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 (255) hide show
  1. package/README.md +15 -11
  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/remote-questions-config.js +2 -2
  8. package/dist/resource-loader.js +34 -1
  9. package/dist/resources/extensions/async-jobs/index.js +10 -0
  10. package/dist/resources/extensions/browser-tools/index.js +3 -1
  11. package/dist/resources/extensions/browser-tools/package.json +3 -1
  12. package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
  13. package/dist/resources/extensions/cmux/index.js +55 -1
  14. package/dist/resources/extensions/context7/package.json +1 -1
  15. package/dist/resources/extensions/env-utils.js +29 -0
  16. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  17. package/dist/resources/extensions/github-sync/cli.js +284 -0
  18. package/dist/resources/extensions/github-sync/index.js +73 -0
  19. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  20. package/dist/resources/extensions/github-sync/sync.js +424 -0
  21. package/dist/resources/extensions/github-sync/templates.js +118 -0
  22. package/dist/resources/extensions/github-sync/types.js +7 -0
  23. package/dist/resources/extensions/google-search/package.json +3 -1
  24. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  25. package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
  26. package/dist/resources/extensions/gsd/auto-loop.js +650 -588
  27. package/dist/resources/extensions/gsd/auto-post-unit.js +99 -70
  28. package/dist/resources/extensions/gsd/auto-prompts.js +202 -48
  29. package/dist/resources/extensions/gsd/auto-start.js +13 -2
  30. package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
  31. package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
  32. package/dist/resources/extensions/gsd/auto.js +143 -96
  33. package/dist/resources/extensions/gsd/captures.js +9 -1
  34. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  35. package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
  36. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  37. package/dist/resources/extensions/gsd/commands.js +24 -3
  38. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  39. package/dist/resources/extensions/gsd/detection.js +1 -2
  40. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  41. package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
  42. package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
  43. package/dist/resources/extensions/gsd/doctor-format.js +15 -0
  44. package/dist/resources/extensions/gsd/doctor-providers.js +30 -11
  45. package/dist/resources/extensions/gsd/doctor.js +204 -12
  46. package/dist/resources/extensions/gsd/exit-command.js +2 -1
  47. package/dist/resources/extensions/gsd/export.js +1 -1
  48. package/dist/resources/extensions/gsd/files.js +48 -9
  49. package/dist/resources/extensions/gsd/forensics.js +1 -1
  50. package/dist/resources/extensions/gsd/git-service.js +30 -12
  51. package/dist/resources/extensions/gsd/gitignore.js +16 -3
  52. package/dist/resources/extensions/gsd/guided-flow.js +149 -38
  53. package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
  54. package/dist/resources/extensions/gsd/health-widget.js +3 -86
  55. package/dist/resources/extensions/gsd/index.js +24 -20
  56. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  57. package/dist/resources/extensions/gsd/migrate-external.js +18 -1
  58. package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
  59. package/dist/resources/extensions/gsd/package.json +1 -1
  60. package/dist/resources/extensions/gsd/paths.js +3 -0
  61. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  62. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  63. package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
  64. package/dist/resources/extensions/gsd/preferences.js +22 -11
  65. package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
  66. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  67. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  68. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  69. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -3
  70. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  71. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  72. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  73. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  74. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  75. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  76. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  77. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  78. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  79. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  80. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  81. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  82. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  83. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  84. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  85. package/dist/resources/extensions/gsd/prompts/run-uat.md +28 -11
  86. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  87. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  88. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  89. package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
  90. package/dist/resources/extensions/gsd/state.js +42 -23
  91. package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
  92. package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
  93. package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
  94. package/dist/resources/extensions/gsd/worktree.js +35 -16
  95. package/dist/resources/extensions/mcp-client/index.js +14 -1
  96. package/dist/resources/extensions/remote-questions/status.js +4 -1
  97. package/dist/resources/extensions/remote-questions/store.js +4 -1
  98. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  99. package/dist/resources/extensions/shared/frontmatter.js +1 -1
  100. package/dist/resources/extensions/subagent/index.js +12 -3
  101. package/dist/resources/extensions/subagent/isolation.js +2 -1
  102. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  103. package/dist/resources/extensions/universal-config/package.json +1 -1
  104. package/dist/welcome-screen.d.ts +12 -0
  105. package/dist/welcome-screen.js +53 -0
  106. package/package.json +1 -1
  107. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  108. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  109. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  110. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  111. package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
  112. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  113. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
  115. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/skills.d.ts +1 -0
  117. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/skills.js +6 -1
  119. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  121. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/index.js +1 -1
  123. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  124. package/packages/pi-coding-agent/package.json +1 -1
  125. package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
  126. package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
  127. package/packages/pi-coding-agent/src/core/skills.ts +9 -1
  128. package/packages/pi-coding-agent/src/index.ts +1 -0
  129. package/pkg/package.json +1 -1
  130. package/src/resources/extensions/async-jobs/index.ts +11 -0
  131. package/src/resources/extensions/browser-tools/index.ts +3 -0
  132. package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
  133. package/src/resources/extensions/cmux/index.ts +57 -1
  134. package/src/resources/extensions/env-utils.ts +31 -0
  135. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  136. package/src/resources/extensions/github-sync/cli.ts +364 -0
  137. package/src/resources/extensions/github-sync/index.ts +93 -0
  138. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  139. package/src/resources/extensions/github-sync/sync.ts +556 -0
  140. package/src/resources/extensions/github-sync/templates.ts +183 -0
  141. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  142. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  143. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  144. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  145. package/src/resources/extensions/github-sync/types.ts +47 -0
  146. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  147. package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
  148. package/src/resources/extensions/gsd/auto-loop.ts +553 -546
  149. package/src/resources/extensions/gsd/auto-post-unit.ts +80 -44
  150. package/src/resources/extensions/gsd/auto-prompts.ts +247 -50
  151. package/src/resources/extensions/gsd/auto-start.ts +18 -2
  152. package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
  153. package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
  154. package/src/resources/extensions/gsd/auto.ts +139 -101
  155. package/src/resources/extensions/gsd/captures.ts +10 -1
  156. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  157. package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
  158. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  159. package/src/resources/extensions/gsd/commands.ts +26 -4
  160. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  161. package/src/resources/extensions/gsd/detection.ts +2 -2
  162. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  163. package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
  164. package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
  165. package/src/resources/extensions/gsd/doctor-format.ts +20 -0
  166. package/src/resources/extensions/gsd/doctor-providers.ts +30 -9
  167. package/src/resources/extensions/gsd/doctor-types.ts +16 -1
  168. package/src/resources/extensions/gsd/doctor.ts +199 -14
  169. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  170. package/src/resources/extensions/gsd/export.ts +1 -1
  171. package/src/resources/extensions/gsd/files.ts +51 -11
  172. package/src/resources/extensions/gsd/forensics.ts +1 -1
  173. package/src/resources/extensions/gsd/git-service.ts +44 -10
  174. package/src/resources/extensions/gsd/gitignore.ts +17 -3
  175. package/src/resources/extensions/gsd/guided-flow.ts +177 -44
  176. package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
  177. package/src/resources/extensions/gsd/health-widget.ts +3 -89
  178. package/src/resources/extensions/gsd/index.ts +24 -17
  179. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  180. package/src/resources/extensions/gsd/migrate-external.ts +18 -1
  181. package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
  182. package/src/resources/extensions/gsd/paths.ts +4 -0
  183. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  184. package/src/resources/extensions/gsd/preferences-types.ts +4 -4
  185. package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
  186. package/src/resources/extensions/gsd/preferences.ts +25 -11
  187. package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
  188. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  189. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  190. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  191. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -3
  192. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  193. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  194. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  195. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  196. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  197. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  198. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  199. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  200. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  201. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  202. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  203. package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  204. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  205. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  206. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  207. package/src/resources/extensions/gsd/prompts/run-uat.md +28 -11
  208. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  209. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  210. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  211. package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
  212. package/src/resources/extensions/gsd/state.ts +39 -21
  213. package/src/resources/extensions/gsd/templates/runtime.md +21 -0
  214. package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
  215. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  216. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
  217. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
  218. package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
  219. package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
  220. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
  221. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
  222. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
  223. package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
  224. package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
  225. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
  226. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  227. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  228. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  229. package/src/resources/extensions/gsd/tests/run-uat.test.ts +16 -4
  230. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
  231. package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
  232. package/src/resources/extensions/gsd/types.ts +18 -1
  233. package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
  234. package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
  235. package/src/resources/extensions/gsd/worktree.ts +35 -15
  236. package/src/resources/extensions/mcp-client/index.ts +17 -1
  237. package/src/resources/extensions/remote-questions/status.ts +5 -1
  238. package/src/resources/extensions/remote-questions/store.ts +5 -1
  239. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  240. package/src/resources/extensions/shared/frontmatter.ts +1 -1
  241. package/src/resources/extensions/subagent/index.ts +12 -3
  242. package/src/resources/extensions/subagent/isolation.ts +3 -1
  243. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  244. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  245. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  246. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  247. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  248. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  249. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  250. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  251. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  252. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  253. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  254. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  255. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -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
+ }
@@ -28,10 +28,12 @@ function modelToProviderId(model) {
28
28
  const prefix = model.split("/")[0].toLowerCase();
29
29
  // Map known prefixes to registry IDs
30
30
  const prefixMap = {
31
+ "anthropic-vertex": "anthropic-vertex",
31
32
  openrouter: "openrouter",
32
33
  groq: "groq",
33
34
  mistral: "mistral",
34
35
  google: "google",
36
+ "google-vertex": "google-vertex",
35
37
  anthropic: "anthropic",
36
38
  openai: "openai",
37
39
  "github-copilot": "github-copilot",
@@ -67,11 +69,19 @@ function collectConfiguredModelProviders() {
67
69
  }
68
70
  const modelEntries = typeof models === "object" ? Object.values(models) : [];
69
71
  for (const entry of modelEntries) {
70
- const modelId = typeof entry === "string" ? entry
71
- : typeof entry === "object" && entry !== null && "model" in entry
72
- ? String(entry.model)
73
- : null;
74
- 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);
75
85
  const pid = modelToProviderId(modelId);
76
86
  if (pid)
77
87
  providers.add(pid);
@@ -88,6 +98,9 @@ function collectConfiguredModelProviders() {
88
98
  }
89
99
  function resolveKey(providerId) {
90
100
  const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
101
+ if (providerId === "anthropic-vertex" && process.env.ANTHROPIC_VERTEX_PROJECT_ID) {
102
+ return { found: true, source: "env", backedOff: false };
103
+ }
91
104
  // Check auth.json
92
105
  const authPath = getAuthPath();
93
106
  if (existsSync(authPath)) {
@@ -138,7 +151,9 @@ function checkLlmProviders() {
138
151
  const results = [];
139
152
  for (const providerId of required) {
140
153
  const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
141
- const label = info?.label ?? providerId;
154
+ const label = providerId === "anthropic-vertex"
155
+ ? "Anthropic Vertex"
156
+ : info?.label ?? providerId;
142
157
  const lookup = resolveKey(providerId);
143
158
  if (!lookup.found) {
144
159
  // Check if a cross-provider can serve this provider's models
@@ -157,16 +172,20 @@ function checkLlmProviders() {
157
172
  });
158
173
  continue;
159
174
  }
160
- const envVar = info?.envVar ?? `${providerId.toUpperCase()}_API_KEY`;
175
+ const envVar = providerId === "anthropic-vertex"
176
+ ? "ANTHROPIC_VERTEX_PROJECT_ID"
177
+ : info?.envVar ?? `${providerId.toUpperCase()}_API_KEY`;
161
178
  results.push({
162
179
  name: providerId,
163
180
  label,
164
181
  category: "llm",
165
182
  status: "error",
166
- message: `${label} — no API key found`,
167
- detail: info?.hasOAuth
168
- ? `Run /gsd keys to authenticate`
169
- : `Set ${envVar} or run /gsd keys`,
183
+ message: `${label} — not configured`,
184
+ detail: providerId === "anthropic-vertex"
185
+ ? "Set ANTHROPIC_VERTEX_PROJECT_ID and authenticate with Google ADC"
186
+ : info?.hasOAuth
187
+ ? `Run /gsd keys to authenticate`
188
+ : `Set ${envVar} or run /gsd keys`,
170
189
  required: true,
171
190
  });
172
191
  }
@@ -1,7 +1,7 @@
1
- import { existsSync, mkdirSync } from "node:fs";
1
+ import { existsSync, mkdirSync, lstatSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { loadFile, parsePlan, parseRoadmap, parseSummary, saveFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
4
- import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile } from "./paths.js";
4
+ import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile, relMilestonePath } from "./paths.js";
5
5
  import { deriveState, isMilestoneComplete } from "./state.js";
6
6
  import { invalidateAllCaches } from "./cache.js";
7
7
  import { loadEffectiveGSDPreferences } from "./preferences.js";
@@ -9,7 +9,7 @@ import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
9
9
  import { checkGitHealth, checkRuntimeHealth } from "./doctor-checks.js";
10
10
  import { checkEnvironmentHealth } from "./doctor-environment.js";
11
11
  import { runProviderChecks } from "./doctor-providers.js";
12
- export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt } from "./doctor-format.js";
12
+ export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt, formatDoctorReportJson } from "./doctor-format.js";
13
13
  export { runEnvironmentChecks, runFullEnvironmentChecks, formatEnvironmentReport } from "./doctor-environment.js";
14
14
  export { computeProgressScore, computeProgressScoreWithContext, formatProgressLine, formatProgressReport } from "./progress-score.js";
15
15
  /**
@@ -257,10 +257,23 @@ async function markSliceDoneInRoadmap(basePath, milestoneId, sliceId, fixesAppli
257
257
  fixesApplied.push(`marked ${sliceId} done in ${roadmapPath}`);
258
258
  }
259
259
  }
260
+ async function markSliceUndoneInRoadmap(basePath, milestoneId, sliceId, fixesApplied) {
261
+ const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
262
+ if (!roadmapPath)
263
+ return;
264
+ const content = await loadFile(roadmapPath);
265
+ if (!content)
266
+ return;
267
+ const updated = content.replace(new RegExp(`^(\\s*-\\s+)\\[x\\]\\s+\\*\\*${sliceId}:`, "m"), `$1[ ] **${sliceId}:`);
268
+ if (updated !== content) {
269
+ await saveFile(roadmapPath, updated);
270
+ fixesApplied.push(`unmarked ${sliceId} in ${roadmapPath} (premature completion)`);
271
+ }
272
+ }
260
273
  function matchesScope(unitId, scope) {
261
274
  if (!scope)
262
275
  return true;
263
- return unitId === scope || unitId.startsWith(`${scope}/`) || unitId.startsWith(`${scope}`);
276
+ return unitId === scope || unitId.startsWith(`${scope}/`);
264
277
  }
265
278
  function auditRequirements(content) {
266
279
  if (!content)
@@ -324,10 +337,68 @@ export async function selectDoctorScope(basePath, requestedScope) {
324
337
  }
325
338
  return state.registry[0]?.id;
326
339
  }
340
+ // ── Helper: circular dependency detection ──────────────────────────────────
341
+ function detectCircularDependencies(slices) {
342
+ const known = new Set(slices.map(s => s.id));
343
+ const adj = new Map();
344
+ for (const s of slices)
345
+ adj.set(s.id, s.depends.filter(d => known.has(d)));
346
+ const state = new Map();
347
+ for (const s of slices)
348
+ state.set(s.id, "unvisited");
349
+ const cycles = [];
350
+ function dfs(id, path) {
351
+ const st = state.get(id);
352
+ if (st === "done")
353
+ return;
354
+ if (st === "visiting") {
355
+ cycles.push([...path.slice(path.indexOf(id)), id]);
356
+ return;
357
+ }
358
+ state.set(id, "visiting");
359
+ for (const dep of adj.get(id) ?? [])
360
+ dfs(dep, [...path, id]);
361
+ state.set(id, "done");
362
+ }
363
+ for (const s of slices)
364
+ if (state.get(s.id) === "unvisited")
365
+ dfs(s.id, []);
366
+ return cycles;
367
+ }
368
+ async function appendDoctorHistory(basePath, report) {
369
+ try {
370
+ const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
371
+ const entry = JSON.stringify({
372
+ ts: new Date().toISOString(),
373
+ ok: report.ok,
374
+ errors: report.issues.filter(i => i.severity === "error").length,
375
+ warnings: report.issues.filter(i => i.severity === "warning").length,
376
+ fixes: report.fixesApplied.length,
377
+ codes: [...new Set(report.issues.map(i => i.code))],
378
+ });
379
+ const existing = existsSync(historyPath) ? readFileSync(historyPath, "utf-8") : "";
380
+ await saveFile(historyPath, existing + entry + "\n");
381
+ }
382
+ catch { /* non-fatal */ }
383
+ }
384
+ /** Read the last N doctor history entries. Returns most-recent-first. */
385
+ export async function readDoctorHistory(basePath, lastN = 50) {
386
+ try {
387
+ const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
388
+ if (!existsSync(historyPath))
389
+ return [];
390
+ const lines = readFileSync(historyPath, "utf-8").split("\n").filter(l => l.trim());
391
+ return lines.slice(-lastN).reverse().map(l => JSON.parse(l));
392
+ }
393
+ catch {
394
+ return [];
395
+ }
396
+ }
327
397
  export async function runGSDDoctor(basePath, options) {
328
398
  const issues = [];
329
399
  const fixesApplied = [];
330
400
  const fix = options?.fix === true;
401
+ const dryRun = options?.dryRun === true;
331
402
  const fixLevel = options?.fixLevel ?? "all";
332
403
  // Issue codes that represent completion state transitions — creating summary
333
404
  // stubs, marking slices/milestones done in the roadmap. These belong to the
@@ -336,12 +407,18 @@ export async function runGSDDoctor(basePath, options) {
336
407
  // detected and reported but never auto-fixed.
337
408
  /** Whether a given issue code should be auto-fixed at the current fixLevel. */
338
409
  const shouldFix = (code) => {
339
- if (!fix)
410
+ if (!fix || dryRun)
340
411
  return false;
341
412
  if (fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code))
342
413
  return false;
343
414
  return true;
344
415
  };
416
+ /** Log a dry-run "would fix" entry when fix=true but dryRun=true. */
417
+ const dryRunCanFix = (code, message) => {
418
+ if (dryRun && fix && !(fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code))) {
419
+ fixesApplied.push(`[dry-run] would fix: ${message}`);
420
+ }
421
+ };
345
422
  const prefs = loadEffectiveGSDPreferences();
346
423
  if (prefs) {
347
424
  const prefIssues = validatePreferenceShape(prefs.preferences);
@@ -357,18 +434,30 @@ export async function runGSDDoctor(basePath, options) {
357
434
  });
358
435
  }
359
436
  }
360
- // Git health checks (orphaned worktrees, stale branches, corrupt merge state, tracked runtime files)
437
+ // Git health checks timed
438
+ const t0git = Date.now();
361
439
  const isolationMode = options?.isolationMode ??
362
440
  (prefs?.preferences?.git?.isolation === "none" ? "none" :
363
441
  prefs?.preferences?.git?.isolation === "branch" ? "branch" : "worktree");
364
442
  await checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode);
365
- // Runtime health checks (crash locks, completed-units, hook state, activity logs, STATE.md, gitignore)
443
+ const gitMs = Date.now() - t0git;
444
+ // Runtime health checks — timed
445
+ const t0runtime = Date.now();
366
446
  await checkRuntimeHealth(basePath, issues, fixesApplied, shouldFix);
367
- // Environment health checks (#1221: missing tools, port conflicts, stale deps, disk space)
368
- await checkEnvironmentHealth(basePath, issues, { includeRemote: !options?.scope });
447
+ const runtimeMs = Date.now() - t0runtime;
448
+ // Environment health checks timed
449
+ const t0env = Date.now();
450
+ await checkEnvironmentHealth(basePath, issues, {
451
+ includeRemote: !options?.scope,
452
+ includeBuild: options?.includeBuild,
453
+ includeTests: options?.includeTests,
454
+ });
455
+ const envMs = Date.now() - t0env;
369
456
  const milestonesPath = milestonesDir(basePath);
370
457
  if (!existsSync(milestonesPath)) {
371
- return { ok: issues.every(issue => issue.severity !== "error"), basePath, issues, fixesApplied };
458
+ const report = { ok: issues.every(i => i.severity !== "error"), basePath, issues, fixesApplied, timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: 0 } };
459
+ await appendDoctorHistory(basePath, report);
460
+ return report;
372
461
  }
373
462
  const requirementsPath = resolveGsdRootFile(basePath, "REQUIREMENTS");
374
463
  const requirementsContent = await loadFile(requirementsPath);
@@ -432,6 +521,46 @@ export async function runGSDDoctor(basePath, options) {
432
521
  if (!roadmapContent)
433
522
  continue;
434
523
  const roadmap = parseRoadmap(roadmapContent);
524
+ // ── Circular dependency detection ──────────────────────────────────────
525
+ for (const cycle of detectCircularDependencies(roadmap.slices)) {
526
+ issues.push({
527
+ severity: "error",
528
+ code: "circular_slice_dependency",
529
+ scope: "milestone",
530
+ unitId: milestoneId,
531
+ message: `Circular dependency detected: ${cycle.join(" → ")}`,
532
+ file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
533
+ fixable: false,
534
+ });
535
+ }
536
+ // ── Orphaned slice directories ─────────────────────────────────────────
537
+ try {
538
+ const slicesDir = join(milestonePath, "slices");
539
+ if (existsSync(slicesDir)) {
540
+ const knownSliceIds = new Set(roadmap.slices.map(s => s.id));
541
+ for (const entry of readdirSync(slicesDir)) {
542
+ try {
543
+ if (!lstatSync(join(slicesDir, entry)).isDirectory())
544
+ continue;
545
+ }
546
+ catch {
547
+ continue;
548
+ }
549
+ if (!knownSliceIds.has(entry)) {
550
+ issues.push({
551
+ severity: "warning",
552
+ code: "orphaned_slice_directory",
553
+ scope: "milestone",
554
+ unitId: milestoneId,
555
+ message: `Directory "${entry}" exists in ${milestoneId}/slices/ but is not referenced in the roadmap`,
556
+ file: `${relMilestonePath(basePath, milestoneId)}/slices/${entry}`,
557
+ fixable: false,
558
+ });
559
+ }
560
+ }
561
+ }
562
+ }
563
+ catch { /* non-fatal */ }
435
564
  for (const slice of roadmap.slices) {
436
565
  const unitId = `${milestoneId}/${slice.id}`;
437
566
  if (options?.scope && !matchesScope(unitId, options.scope) && options.scope !== milestoneId)
@@ -502,6 +631,34 @@ export async function runGSDDoctor(basePath, options) {
502
631
  }
503
632
  continue;
504
633
  }
634
+ // ── Duplicate task IDs ───────────────────────────────────────────────
635
+ const taskIdCounts = new Map();
636
+ for (const task of plan.tasks)
637
+ taskIdCounts.set(task.id, (taskIdCounts.get(task.id) ?? 0) + 1);
638
+ for (const [taskId, count] of taskIdCounts) {
639
+ if (count > 1) {
640
+ issues.push({ severity: "error", code: "duplicate_task_id", scope: "slice", unitId,
641
+ message: `Task ID "${taskId}" appears ${count} times in ${slice.id}-PLAN.md — duplicate IDs cause dispatch failures`,
642
+ file: relSliceFile(basePath, milestoneId, slice.id, "PLAN"), fixable: false });
643
+ }
644
+ }
645
+ // ── Task files on disk not in plan ────────────────────────────────────
646
+ try {
647
+ if (tasksDir) {
648
+ const planTaskIds = new Set(plan.tasks.map(t => t.id));
649
+ for (const f of readdirSync(tasksDir)) {
650
+ if (!f.endsWith("-SUMMARY.md"))
651
+ continue;
652
+ const diskTaskId = f.replace(/-SUMMARY\.md$/, "");
653
+ if (!planTaskIds.has(diskTaskId)) {
654
+ issues.push({ severity: "info", code: "task_file_not_in_plan", scope: "slice", unitId,
655
+ message: `Task summary "${f}" exists on disk but "${diskTaskId}" is not in ${slice.id}-PLAN.md`,
656
+ file: relTaskFile(basePath, milestoneId, slice.id, diskTaskId, "SUMMARY"), fixable: false });
657
+ }
658
+ }
659
+ }
660
+ }
661
+ catch { /* non-fatal */ }
505
662
  let allTasksDone = plan.tasks.length > 0;
506
663
  for (const task of plan.tasks) {
507
664
  const taskUnitId = `${unitId}/${task.id}`;
@@ -517,6 +674,7 @@ export async function runGSDDoctor(basePath, options) {
517
674
  file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"),
518
675
  fixable: true,
519
676
  });
677
+ dryRunCanFix("task_done_missing_summary", `create stub summary for ${taskUnitId}`);
520
678
  if (shouldFix("task_done_missing_summary")) {
521
679
  const stubPath = join(basePath, ".gsd", "milestones", milestoneId, "slices", slice.id, "tasks", `${task.id}-SUMMARY.md`);
522
680
  const stubContent = [
@@ -575,6 +733,22 @@ export async function runGSDDoctor(basePath, options) {
575
733
  }
576
734
  }
577
735
  }
736
+ // ── Future timestamp check ─────────────────────────────────────
737
+ if (task.done && hasSummary && summaryPath) {
738
+ try {
739
+ const rawSummary = await loadFile(summaryPath);
740
+ const m = rawSummary?.match(/^completed_at:\s*(.+)$/m);
741
+ if (m) {
742
+ const ts = new Date(m[1].trim());
743
+ if (!isNaN(ts.getTime()) && ts.getTime() > Date.now() + 24 * 60 * 60 * 1000) {
744
+ issues.push({ severity: "warning", code: "future_timestamp", scope: "task", unitId: taskUnitId,
745
+ message: `Task ${task.id} has completed_at "${m[1].trim()}" which is more than 24h in the future`,
746
+ file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"), fixable: false });
747
+ }
748
+ }
749
+ }
750
+ catch { /* non-fatal */ }
751
+ }
578
752
  allTasksDone = allTasksDone && task.done;
579
753
  }
580
754
  // Blocker-without-replan detection
@@ -604,6 +778,12 @@ export async function runGSDDoctor(basePath, options) {
604
778
  }
605
779
  }
606
780
  }
781
+ // ── Stale REPLAN: exists but all tasks done ────────────────────────
782
+ if (replanPath && allTasksDone) {
783
+ issues.push({ severity: "info", code: "stale_replan_file", scope: "slice", unitId,
784
+ message: `${slice.id} has a REPLAN.md but all tasks are done — REPLAN.md may be stale`,
785
+ file: relSliceFile(basePath, milestoneId, slice.id, "REPLAN"), fixable: false });
786
+ }
607
787
  const sliceSummaryPath = resolveSliceFile(basePath, milestoneId, slice.id, "SUMMARY");
608
788
  const sliceUatPath = join(slicePath, `${slice.id}-UAT.md`);
609
789
  const hasSliceSummary = !!(sliceSummaryPath && await loadFile(sliceSummaryPath));
@@ -618,6 +798,7 @@ export async function runGSDDoctor(basePath, options) {
618
798
  file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
619
799
  fixable: true,
620
800
  });
801
+ dryRunCanFix("all_tasks_done_missing_slice_summary", `create placeholder summary for ${unitId}`);
621
802
  if (shouldFix("all_tasks_done_missing_slice_summary"))
622
803
  await ensureSliceSummaryStub(basePath, milestoneId, slice.id, fixesApplied);
623
804
  }
@@ -631,6 +812,7 @@ export async function runGSDDoctor(basePath, options) {
631
812
  file: `${relSlicePath(basePath, milestoneId, slice.id)}/${slice.id}-UAT.md`,
632
813
  fixable: true,
633
814
  });
815
+ dryRunCanFix("all_tasks_done_missing_slice_uat", `create placeholder UAT for ${unitId}`);
634
816
  if (shouldFix("all_tasks_done_missing_slice_uat"))
635
817
  await ensureSliceUatStub(basePath, milestoneId, slice.id, fixesApplied);
636
818
  }
@@ -644,6 +826,7 @@ export async function runGSDDoctor(basePath, options) {
644
826
  file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
645
827
  fixable: true,
646
828
  });
829
+ dryRunCanFix("all_tasks_done_roadmap_not_checked", `mark ${slice.id} done in roadmap`);
647
830
  if (shouldFix("all_tasks_done_roadmap_not_checked") && (hasSliceSummary || issues.some(issue => issue.code === "all_tasks_done_missing_slice_summary" && issue.unitId === unitId))) {
648
831
  await markSliceDoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
649
832
  }
@@ -658,6 +841,12 @@ export async function runGSDDoctor(basePath, options) {
658
841
  file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
659
842
  fixable: true,
660
843
  });
844
+ if (!allTasksDone) {
845
+ dryRunCanFix("slice_checked_missing_summary", `uncheck ${slice.id} in roadmap (tasks incomplete)`);
846
+ if (shouldFix("slice_checked_missing_summary")) {
847
+ await markSliceUndoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
848
+ }
849
+ }
661
850
  }
662
851
  if (slice.done && !hasSliceUat) {
663
852
  issues.push({
@@ -696,13 +885,16 @@ export async function runGSDDoctor(basePath, options) {
696
885
  });
697
886
  }
698
887
  }
699
- if (fix && fixesApplied.length > 0) {
888
+ if (fix && !dryRun && fixesApplied.length > 0) {
700
889
  await updateStateFile(basePath, fixesApplied);
701
890
  }
702
- return {
891
+ const report = {
703
892
  ok: issues.every(issue => issue.severity !== "error"),
704
893
  basePath,
705
894
  issues,
706
895
  fixesApplied,
896
+ timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: Math.max(0, Date.now() - t0env - envMs) },
707
897
  };
898
+ await appendDoctorHistory(basePath, report);
899
+ return report;
708
900
  }
@@ -1,9 +1,10 @@
1
+ import { importExtensionModule } from "@gsd/pi-coding-agent";
1
2
  export function registerExitCommand(pi, deps = {}) {
2
3
  pi.registerCommand("exit", {
3
4
  description: "Exit GSD gracefully",
4
5
  handler: async (_args, ctx) => {
5
6
  // Stop auto-mode first so locks and activity state are cleaned up before shutdown.
6
- const stopAuto = deps.stopAuto ?? (await import("./auto.js")).stopAuto;
7
+ const stopAuto = deps.stopAuto ?? (await importExtensionModule(import.meta.url, "./auto.js")).stopAuto;
7
8
  await stopAuto(ctx, pi, "Graceful exit");
8
9
  ctx.shutdown();
9
10
  },
@@ -5,7 +5,7 @@ import { join, basename } from "node:path";
5
5
  import { exec } from "node:child_process";
6
6
  import { getLedger, getProjectTotals, aggregateByPhase, aggregateBySlice, aggregateByModel, formatCost, formatTokenCount, loadLedgerFromDisk, } from "./metrics.js";
7
7
  import { gsdRoot } from "./paths.js";
8
- import { formatDuration, fileLink } from "../shared/mod.js";
8
+ import { formatDuration, fileLink } from "../shared/format-utils.js";
9
9
  import { getErrorMessage } from "./error-utils.js";
10
10
  /**
11
11
  * Open a file in the user's default browser.