gsd-pi 2.37.1-dev.d3ace49 → 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 (188) hide show
  1. package/dist/app-paths.js +1 -1
  2. package/dist/cli.js +9 -0
  3. package/dist/extension-discovery.d.ts +5 -3
  4. package/dist/extension-discovery.js +14 -9
  5. package/dist/extension-registry.js +2 -2
  6. package/dist/remote-questions-config.js +2 -2
  7. package/dist/resource-loader.js +34 -1
  8. package/dist/resources/extensions/browser-tools/package.json +3 -1
  9. package/dist/resources/extensions/cmux/index.js +55 -1
  10. package/dist/resources/extensions/context7/package.json +1 -1
  11. package/dist/resources/extensions/env-utils.js +29 -0
  12. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  13. package/dist/resources/extensions/github-sync/cli.js +284 -0
  14. package/dist/resources/extensions/github-sync/index.js +73 -0
  15. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  16. package/dist/resources/extensions/github-sync/sync.js +424 -0
  17. package/dist/resources/extensions/github-sync/templates.js +118 -0
  18. package/dist/resources/extensions/github-sync/types.js +7 -0
  19. package/dist/resources/extensions/google-search/package.json +3 -1
  20. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  21. package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
  22. package/dist/resources/extensions/gsd/auto-loop.js +597 -588
  23. package/dist/resources/extensions/gsd/auto-post-unit.js +99 -70
  24. package/dist/resources/extensions/gsd/auto-prompts.js +23 -43
  25. package/dist/resources/extensions/gsd/auto-start.js +13 -2
  26. package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
  27. package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
  28. package/dist/resources/extensions/gsd/auto.js +143 -96
  29. package/dist/resources/extensions/gsd/captures.js +9 -1
  30. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  31. package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
  32. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  33. package/dist/resources/extensions/gsd/commands.js +24 -3
  34. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  35. package/dist/resources/extensions/gsd/detection.js +1 -2
  36. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  37. package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
  38. package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
  39. package/dist/resources/extensions/gsd/doctor-format.js +15 -0
  40. package/dist/resources/extensions/gsd/doctor-providers.js +27 -11
  41. package/dist/resources/extensions/gsd/doctor.js +204 -12
  42. package/dist/resources/extensions/gsd/exit-command.js +2 -1
  43. package/dist/resources/extensions/gsd/export.js +1 -1
  44. package/dist/resources/extensions/gsd/files.js +6 -2
  45. package/dist/resources/extensions/gsd/forensics.js +1 -1
  46. package/dist/resources/extensions/gsd/git-service.js +15 -12
  47. package/dist/resources/extensions/gsd/guided-flow.js +82 -32
  48. package/dist/resources/extensions/gsd/index.js +24 -20
  49. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  50. package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
  51. package/dist/resources/extensions/gsd/package.json +1 -1
  52. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  53. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  54. package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
  55. package/dist/resources/extensions/gsd/preferences.js +8 -5
  56. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  57. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
  58. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  59. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  60. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  61. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  62. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  63. package/dist/resources/extensions/gsd/prompts/run-uat.md +27 -10
  64. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  65. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  66. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  67. package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
  68. package/dist/resources/extensions/gsd/state.js +1 -1
  69. package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
  70. package/dist/resources/extensions/gsd/worktree.js +35 -16
  71. package/dist/resources/extensions/mcp-client/index.js +14 -1
  72. package/dist/resources/extensions/remote-questions/status.js +2 -1
  73. package/dist/resources/extensions/remote-questions/store.js +2 -1
  74. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  75. package/dist/resources/extensions/subagent/index.js +12 -3
  76. package/dist/resources/extensions/subagent/isolation.js +2 -1
  77. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  78. package/dist/resources/extensions/universal-config/package.json +1 -1
  79. package/dist/welcome-screen.d.ts +12 -0
  80. package/dist/welcome-screen.js +53 -0
  81. package/package.json +1 -1
  82. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  83. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  84. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  85. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  86. package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
  87. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  88. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  89. package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
  90. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  91. package/packages/pi-coding-agent/package.json +1 -1
  92. package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
  93. package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
  94. package/pkg/package.json +1 -1
  95. package/src/resources/extensions/cmux/index.ts +57 -1
  96. package/src/resources/extensions/env-utils.ts +31 -0
  97. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  98. package/src/resources/extensions/github-sync/cli.ts +364 -0
  99. package/src/resources/extensions/github-sync/index.ts +93 -0
  100. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  101. package/src/resources/extensions/github-sync/sync.ts +556 -0
  102. package/src/resources/extensions/github-sync/templates.ts +183 -0
  103. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  104. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  105. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  106. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  107. package/src/resources/extensions/github-sync/types.ts +47 -0
  108. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  109. package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
  110. package/src/resources/extensions/gsd/auto-loop.ts +484 -546
  111. package/src/resources/extensions/gsd/auto-post-unit.ts +80 -44
  112. package/src/resources/extensions/gsd/auto-prompts.ts +25 -45
  113. package/src/resources/extensions/gsd/auto-start.ts +18 -2
  114. package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
  115. package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
  116. package/src/resources/extensions/gsd/auto.ts +139 -101
  117. package/src/resources/extensions/gsd/captures.ts +10 -1
  118. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  119. package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
  120. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  121. package/src/resources/extensions/gsd/commands.ts +26 -4
  122. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  123. package/src/resources/extensions/gsd/detection.ts +2 -2
  124. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  125. package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
  126. package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
  127. package/src/resources/extensions/gsd/doctor-format.ts +20 -0
  128. package/src/resources/extensions/gsd/doctor-providers.ts +26 -9
  129. package/src/resources/extensions/gsd/doctor-types.ts +16 -1
  130. package/src/resources/extensions/gsd/doctor.ts +199 -14
  131. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  132. package/src/resources/extensions/gsd/export.ts +1 -1
  133. package/src/resources/extensions/gsd/files.ts +5 -3
  134. package/src/resources/extensions/gsd/forensics.ts +1 -1
  135. package/src/resources/extensions/gsd/git-service.ts +20 -10
  136. package/src/resources/extensions/gsd/guided-flow.ts +110 -38
  137. package/src/resources/extensions/gsd/index.ts +24 -17
  138. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  139. package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
  140. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  141. package/src/resources/extensions/gsd/preferences-types.ts +4 -4
  142. package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
  143. package/src/resources/extensions/gsd/preferences.ts +8 -5
  144. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  145. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
  146. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  147. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  148. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  149. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  150. package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  151. package/src/resources/extensions/gsd/prompts/run-uat.md +27 -10
  152. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  153. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  154. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  155. package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
  156. package/src/resources/extensions/gsd/state.ts +1 -1
  157. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  158. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
  159. package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
  160. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
  161. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
  162. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  163. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  164. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  165. package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
  166. package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
  167. package/src/resources/extensions/gsd/types.ts +0 -1
  168. package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
  169. package/src/resources/extensions/gsd/worktree.ts +35 -15
  170. package/src/resources/extensions/mcp-client/index.ts +17 -1
  171. package/src/resources/extensions/remote-questions/status.ts +3 -1
  172. package/src/resources/extensions/remote-questions/store.ts +3 -1
  173. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  174. package/src/resources/extensions/subagent/index.ts +12 -3
  175. package/src/resources/extensions/subagent/isolation.ts +3 -1
  176. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  177. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  178. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  179. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  180. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  181. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  182. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  183. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  184. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  185. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  186. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  187. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  188. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -297,116 +297,163 @@ export async function stopAuto(ctx, pi, reason) {
297
297
  return;
298
298
  const loadedPreferences = loadEffectiveGSDPreferences()?.preferences;
299
299
  const reasonSuffix = reason ? ` — ${reason}` : "";
300
- clearUnitTimeout();
301
- if (lockBase())
302
- clearLock(lockBase());
303
- if (lockBase())
304
- releaseSessionLock(lockBase());
305
- clearSkillSnapshot();
306
- resetSkillTelemetry();
307
- // Remove SIGTERM handler registered at auto-mode start
308
- deregisterSigtermHandler();
309
- // ── Auto-worktree: exit worktree and reset s.basePath on stop ──
310
- if (s.currentMilestoneId) {
311
- const notifyCtx = ctx
312
- ? { notify: ctx.ui.notify.bind(ctx.ui) }
313
- : { notify: () => { } };
314
- buildResolver().exitMilestone(s.currentMilestoneId, notifyCtx, {
315
- preserveBranch: true,
316
- });
317
- }
318
- // ── DB cleanup: close the SQLite connection ──
319
- if (isDbAvailable()) {
300
+ try {
301
+ // ── Step 1: Timers and locks ──
320
302
  try {
321
- const { closeDatabase } = await import("./gsd-db.js");
322
- closeDatabase();
303
+ clearUnitTimeout();
304
+ if (lockBase())
305
+ clearLock(lockBase());
306
+ if (lockBase())
307
+ releaseSessionLock(lockBase());
323
308
  }
324
309
  catch (e) {
325
- debugLog("db-close-failed", {
326
- error: e instanceof Error ? e.message : String(e),
327
- });
310
+ debugLog("stop-cleanup-locks", { error: e instanceof Error ? e.message : String(e) });
328
311
  }
329
- }
330
- if (s.originalBasePath) {
331
- s.basePath = s.originalBasePath;
312
+ // ── Step 2: Skill state ──
332
313
  try {
333
- process.chdir(s.basePath);
314
+ clearSkillSnapshot();
315
+ resetSkillTelemetry();
334
316
  }
335
- catch {
336
- /* best-effort */
317
+ catch (e) {
318
+ debugLog("stop-cleanup-skills", { error: e instanceof Error ? e.message : String(e) });
337
319
  }
338
- }
339
- const ledger = getLedger();
340
- if (ledger && ledger.units.length > 0) {
341
- const totals = getProjectTotals(ledger.units);
342
- ctx?.ui.notify(`Auto-mode stopped${reasonSuffix}. Session: ${formatCost(totals.cost)} · ${formatTokenCount(totals.tokens.total)} tokens · ${ledger.units.length} units`, "info");
343
- }
344
- else {
345
- ctx?.ui.notify(`Auto-mode stopped${reasonSuffix}.`, "info");
346
- }
347
- if (s.basePath) {
320
+ // ── Step 3: SIGTERM handler ──
348
321
  try {
349
- await rebuildState(s.basePath);
322
+ deregisterSigtermHandler();
350
323
  }
351
324
  catch (e) {
352
- debugLog("stop-rebuild-state-failed", {
353
- error: e instanceof Error ? e.message : String(e),
354
- });
325
+ debugLog("stop-cleanup-sigterm", { error: e instanceof Error ? e.message : String(e) });
355
326
  }
356
- }
357
- clearCmuxSidebar(loadedPreferences);
358
- logCmuxEvent(loadedPreferences, `Auto-mode stopped${reasonSuffix || ""}.`, reason?.startsWith("Blocked:") ? "warning" : "info");
359
- if (isDebugEnabled()) {
360
- const logPath = writeDebugSummary();
361
- if (logPath) {
362
- ctx?.ui.notify(`Debug log written → ${logPath}`, "info");
327
+ // ── Step 4: Auto-worktree exit ──
328
+ try {
329
+ if (s.currentMilestoneId) {
330
+ const notifyCtx = ctx
331
+ ? { notify: ctx.ui.notify.bind(ctx.ui) }
332
+ : { notify: () => { } };
333
+ buildResolver().exitMilestone(s.currentMilestoneId, notifyCtx, {
334
+ preserveBranch: true,
335
+ });
336
+ }
337
+ }
338
+ catch (e) {
339
+ debugLog("stop-cleanup-worktree", { error: e instanceof Error ? e.message : String(e) });
340
+ }
341
+ // ── Step 5: DB cleanup ──
342
+ if (isDbAvailable()) {
343
+ try {
344
+ const { closeDatabase } = await import("./gsd-db.js");
345
+ closeDatabase();
346
+ }
347
+ catch (e) {
348
+ debugLog("db-close-failed", {
349
+ error: e instanceof Error ? e.message : String(e),
350
+ });
351
+ }
352
+ }
353
+ // ── Step 6: Restore basePath and chdir ──
354
+ try {
355
+ if (s.originalBasePath) {
356
+ s.basePath = s.originalBasePath;
357
+ try {
358
+ process.chdir(s.basePath);
359
+ }
360
+ catch {
361
+ /* best-effort */
362
+ }
363
+ }
364
+ }
365
+ catch (e) {
366
+ debugLog("stop-cleanup-basepath", { error: e instanceof Error ? e.message : String(e) });
367
+ }
368
+ // ── Step 7: Ledger notification ──
369
+ try {
370
+ const ledger = getLedger();
371
+ if (ledger && ledger.units.length > 0) {
372
+ const totals = getProjectTotals(ledger.units);
373
+ ctx?.ui.notify(`Auto-mode stopped${reasonSuffix}. Session: ${formatCost(totals.cost)} · ${formatTokenCount(totals.tokens.total)} tokens · ${ledger.units.length} units`, "info");
374
+ }
375
+ else {
376
+ ctx?.ui.notify(`Auto-mode stopped${reasonSuffix}.`, "info");
377
+ }
378
+ }
379
+ catch (e) {
380
+ debugLog("stop-cleanup-ledger", { error: e instanceof Error ? e.message : String(e) });
381
+ }
382
+ // ── Step 8: Rebuild state ──
383
+ if (s.basePath) {
384
+ try {
385
+ await rebuildState(s.basePath);
386
+ }
387
+ catch (e) {
388
+ debugLog("stop-rebuild-state-failed", {
389
+ error: e instanceof Error ? e.message : String(e),
390
+ });
391
+ }
392
+ }
393
+ // ── Step 9: Cmux sidebar / event log ──
394
+ try {
395
+ clearCmuxSidebar(loadedPreferences);
396
+ logCmuxEvent(loadedPreferences, `Auto-mode stopped${reasonSuffix || ""}.`, reason?.startsWith("Blocked:") ? "warning" : "info");
397
+ }
398
+ catch (e) {
399
+ debugLog("stop-cleanup-cmux", { error: e instanceof Error ? e.message : String(e) });
400
+ }
401
+ // ── Step 10: Debug summary ──
402
+ try {
403
+ if (isDebugEnabled()) {
404
+ const logPath = writeDebugSummary();
405
+ if (logPath) {
406
+ ctx?.ui.notify(`Debug log written → ${logPath}`, "info");
407
+ }
408
+ }
409
+ }
410
+ catch (e) {
411
+ debugLog("stop-cleanup-debug", { error: e instanceof Error ? e.message : String(e) });
412
+ }
413
+ // ── Step 11: Reset metrics, routing, hooks ──
414
+ try {
415
+ resetMetrics();
416
+ resetRoutingHistory();
417
+ resetHookState();
418
+ if (s.basePath)
419
+ clearPersistedHookState(s.basePath);
420
+ }
421
+ catch (e) {
422
+ debugLog("stop-cleanup-metrics", { error: e instanceof Error ? e.message : String(e) });
423
+ }
424
+ // ── Step 12: Remove paused-session metadata (#1383) ──
425
+ try {
426
+ const pausedPath = join(gsdRoot(s.originalBasePath || s.basePath), "runtime", "paused-session.json");
427
+ if (existsSync(pausedPath))
428
+ unlinkSync(pausedPath);
429
+ }
430
+ catch { /* non-fatal */ }
431
+ // ── Step 13: Restore original model (before reset clears IDs) ──
432
+ try {
433
+ if (pi && ctx && s.originalModelId && s.originalModelProvider) {
434
+ const original = ctx.modelRegistry.find(s.originalModelProvider, s.originalModelId);
435
+ if (original)
436
+ await pi.setModel(original);
437
+ }
438
+ }
439
+ catch (e) {
440
+ debugLog("stop-cleanup-model", { error: e instanceof Error ? e.message : String(e) });
363
441
  }
364
442
  }
365
- resetMetrics();
366
- resetRoutingHistory();
367
- resetHookState();
368
- if (s.basePath)
369
- clearPersistedHookState(s.basePath);
370
- // Remove paused-session metadata if present (#1383)
371
- try {
372
- const pausedPath = join(gsdRoot(s.originalBasePath || s.basePath), "runtime", "paused-session.json");
373
- if (existsSync(pausedPath))
374
- unlinkSync(pausedPath);
375
- }
376
- catch { /* non-fatal */ }
377
- s.active = false;
378
- s.paused = false;
379
- s.stepMode = false;
380
- s.unitDispatchCount.clear();
381
- s.unitRecoveryCount.clear();
382
- clearInFlightTools();
383
- s.lastBudgetAlertLevel = 0;
384
- s.lastStateRebuildAt = 0;
385
- s.unitLifetimeDispatches.clear();
386
- s.currentUnit = null;
387
- s.autoModeStartModel = null;
388
- s.currentMilestoneId = null;
389
- s.originalBasePath = "";
390
- s.completedUnits = [];
391
- s.pendingQuickTasks = [];
392
- clearSliceProgressCache();
393
- clearActivityLogState();
394
- resetProactiveHealing();
395
- s.pendingCrashRecovery = null;
396
- s.pendingVerificationRetry = null;
397
- s.verificationRetryCount.clear();
398
- s.pausedSessionFile = null;
399
- ctx?.ui.setStatus("gsd-auto", undefined);
400
- ctx?.ui.setWidget("gsd-progress", undefined);
401
- ctx?.ui.setFooter(undefined);
402
- if (pi && ctx && s.originalModelId && s.originalModelProvider) {
403
- const original = ctx.modelRegistry.find(s.originalModelProvider, s.originalModelId);
404
- if (original)
405
- await pi.setModel(original);
406
- s.originalModelId = null;
407
- s.originalModelProvider = null;
443
+ finally {
444
+ // ── Critical invariants: these MUST execute regardless of errors ──
445
+ // External cleanup (not covered by session reset)
446
+ clearInFlightTools();
447
+ clearSliceProgressCache();
448
+ clearActivityLogState();
449
+ resetProactiveHealing();
450
+ // UI cleanup
451
+ ctx?.ui.setStatus("gsd-auto", undefined);
452
+ ctx?.ui.setWidget("gsd-progress", undefined);
453
+ ctx?.ui.setFooter(undefined);
454
+ // Reset all session state in one call
455
+ s.reset();
408
456
  }
409
- s.cmdCtx = null;
410
457
  }
411
458
  /**
412
459
  * Pause auto-mode without destroying state. Context is preserved.
@@ -30,8 +30,16 @@ const VALID_CLASSIFICATIONS = [
30
30
  */
31
31
  export function resolveCapturesPath(basePath) {
32
32
  const resolved = resolve(basePath);
33
+ // Direct layout: /.gsd/worktrees/
33
34
  const worktreeMarker = `${sep}.gsd${sep}worktrees${sep}`;
34
- const idx = resolved.indexOf(worktreeMarker);
35
+ let idx = resolved.indexOf(worktreeMarker);
36
+ if (idx === -1) {
37
+ // Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
38
+ const symlinkRe = new RegExp(`\\${sep}\\.gsd\\${sep}projects\\${sep}[a-f0-9]+\\${sep}worktrees\\${sep}`);
39
+ const match = resolved.match(symlinkRe);
40
+ if (match && match.index !== undefined)
41
+ idx = match.index;
42
+ }
35
43
  if (idx !== -1) {
36
44
  // basePath is inside a worktree — resolve to project root
37
45
  const projectRoot = resolved.slice(0, idx);
@@ -8,12 +8,13 @@
8
8
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
9
9
  import { dirname, join } from "node:path";
10
10
  import { homedir } from "node:os";
11
+ const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
11
12
  // ─── Registry I/O ───────────────────────────────────────────────────────────
12
13
  function getRegistryPath() {
13
- return join(homedir(), ".gsd", "extensions", "registry.json");
14
+ return join(gsdHome, "extensions", "registry.json");
14
15
  }
15
16
  function getAgentExtensionsDir() {
16
- return join(homedir(), ".gsd", "agent", "extensions");
17
+ return join(gsdHome, "agent", "extensions");
17
18
  }
18
19
  function loadRegistry() {
19
20
  const filePath = getRegistryPath();
@@ -10,7 +10,7 @@ import { deriveState } from "./state.js";
10
10
  import { gsdRoot } from "./paths.js";
11
11
  import { appendCapture, hasPendingCaptures, loadPendingCaptures } from "./captures.js";
12
12
  import { appendOverride, appendKnowledge } from "./files.js";
13
- import { formatDoctorIssuesForPrompt, formatDoctorReport, runGSDDoctor, selectDoctorScope, filterDoctorIssues, } from "./doctor.js";
13
+ import { formatDoctorIssuesForPrompt, formatDoctorReport, formatDoctorReportJson, runGSDDoctor, selectDoctorScope, filterDoctorIssues, } from "./doctor.js";
14
14
  import { isAutoActive } from "./auto.js";
15
15
  import { projectRoot } from "./commands.js";
16
16
  import { loadPrompt } from "./prompt-loader.js";
@@ -28,15 +28,28 @@ export function dispatchDoctorHeal(pi, scope, reportText, structuredIssues) {
28
28
  }
29
29
  export async function handleDoctor(args, ctx, pi) {
30
30
  const trimmed = args.trim();
31
- const parts = trimmed ? trimmed.split(/\s+/) : [];
31
+ // Extract flags before positional parsing
32
+ const jsonMode = trimmed.includes("--json");
33
+ const dryRun = trimmed.includes("--dry-run");
34
+ const includeBuild = trimmed.includes("--build");
35
+ const includeTests = trimmed.includes("--test");
36
+ const stripped = trimmed.replace(/--json|--dry-run|--build|--test/g, "").trim();
37
+ const parts = stripped ? stripped.split(/\s+/) : [];
32
38
  const mode = parts[0] === "fix" || parts[0] === "heal" || parts[0] === "audit" ? parts[0] : "doctor";
33
39
  const requestedScope = mode === "doctor" ? parts[0] : parts[1];
34
40
  const scope = await selectDoctorScope(projectRoot(), requestedScope);
35
41
  const effectiveScope = mode === "audit" ? requestedScope : scope;
36
42
  const report = await runGSDDoctor(projectRoot(), {
37
- fix: mode === "fix" || mode === "heal",
43
+ fix: mode === "fix" || mode === "heal" || dryRun,
44
+ dryRun,
38
45
  scope: effectiveScope,
46
+ includeBuild,
47
+ includeTests,
39
48
  });
49
+ if (jsonMode) {
50
+ ctx.ui.notify(formatDoctorReportJson(report), "info");
51
+ return;
52
+ }
40
53
  const reportText = formatDoctorReport(report, {
41
54
  scope: effectiveScope,
42
55
  includeWarnings: mode === "audit",
@@ -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) {
@@ -3,10 +3,12 @@
3
3
  *
4
4
  * One command, one wizard. Routes to smart entry or status.
5
5
  */
6
+ import { importExtensionModule } from "@gsd/pi-coding-agent";
6
7
  import { existsSync, readFileSync, readdirSync, unlinkSync } from "node:fs";
7
8
  import { homedir } from "node:os";
8
9
  import { join } from "node:path";
9
10
  import { gsdRoot } from "./paths.js";
11
+ const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
10
12
  import { enableDebug } from "./debug-logger.js";
11
13
  import { deriveState } from "./state.js";
12
14
  import { GSDDashboardOverlay } from "./dashboard-overlay.js";
@@ -135,7 +137,7 @@ async function guardRemoteSession(ctx, pi) {
135
137
  }
136
138
  export function registerGSDCommand(pi) {
137
139
  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",
140
+ 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
141
  getArgumentCompletions: (prefix) => {
140
142
  const subcommands = [
141
143
  { cmd: "help", desc: "Categorized command reference with descriptions" },
@@ -186,7 +188,11 @@ export function registerGSDCommand(pi) {
186
188
  { cmd: "templates", desc: "List available workflow templates" },
187
189
  { cmd: "extensions", desc: "Manage extensions (list, enable, disable, info)" },
188
190
  ];
191
+ const hasTrailingSpace = prefix.endsWith(" ");
189
192
  const parts = prefix.trim().split(/\s+/);
193
+ if (hasTrailingSpace && parts.length >= 1) {
194
+ parts.push("");
195
+ }
190
196
  if (parts.length <= 1) {
191
197
  return subcommands
192
198
  .filter((item) => item.cmd.startsWith(parts[0] ?? ""))
@@ -433,7 +439,7 @@ export function registerGSDCommand(pi) {
433
439
  if (parts.length === 3 && ["enable", "disable", "info"].includes(parts[1])) {
434
440
  const idPrefix = parts[2] ?? "";
435
441
  try {
436
- const extDir = join(homedir(), ".gsd", "agent", "extensions");
442
+ const extDir = join(gsdHome, "agent", "extensions");
437
443
  const ids = [];
438
444
  for (const entry of readdirSync(extDir, { withFileTypes: true })) {
439
445
  if (!entry.isDirectory())
@@ -468,6 +474,10 @@ export function registerGSDCommand(pi) {
468
474
  { cmd: "fix", desc: "Auto-fix detected issues" },
469
475
  { cmd: "heal", desc: "AI-driven deep healing" },
470
476
  { cmd: "audit", desc: "Run health audit without fixing" },
477
+ { cmd: "--dry-run", desc: "Show what --fix would change without applying" },
478
+ { cmd: "--json", desc: "Output report as JSON (CI/tooling friendly)" },
479
+ { cmd: "--build", desc: "Include slow build health check (npm run build)" },
480
+ { cmd: "--test", desc: "Include slow test health check (npm test)" },
471
481
  ];
472
482
  if (parts.length <= 2) {
473
483
  return modes
@@ -491,6 +501,17 @@ export function registerGSDCommand(pi) {
491
501
  .filter((p) => p.cmd.startsWith(phasePrefix))
492
502
  .map((p) => ({ value: `dispatch ${p.cmd}`, label: p.cmd, description: p.desc }));
493
503
  }
504
+ if (parts[0] === "rate" && parts.length <= 2) {
505
+ const tierPrefix = parts[1] ?? "";
506
+ const tiers = [
507
+ { cmd: "over", desc: "Model was overqualified for this task" },
508
+ { cmd: "ok", desc: "Model was appropriate for this task" },
509
+ { cmd: "under", desc: "Model was underqualified for this task" },
510
+ ];
511
+ return tiers
512
+ .filter((t) => t.cmd.startsWith(tierPrefix))
513
+ .map((t) => ({ value: `rate ${t.cmd}`, label: t.cmd, description: t.desc }));
514
+ }
494
515
  return [];
495
516
  },
496
517
  async handler(args, ctx) {
@@ -509,7 +530,7 @@ export async function handleGSDCommand(args, ctx, pi) {
509
530
  return;
510
531
  }
511
532
  if (trimmed === "widget" || trimmed.startsWith("widget ")) {
512
- const { cycleWidgetMode, setWidgetMode, getWidgetMode } = await import("./auto-dashboard.js");
533
+ const { cycleWidgetMode, setWidgetMode, getWidgetMode } = await importExtensionModule(import.meta.url, "./auto-dashboard.js");
513
534
  const arg = trimmed.replace(/^widget\s*/, "").trim();
514
535
  if (arg === "full" || arg === "small" || arg === "min" || arg === "off") {
515
536
  setWidgetMode(arg);
@@ -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.