gsd-pi 2.19.0 → 2.20.0
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.
- package/README.md +5 -1
- package/dist/cli.js +3 -3
- package/dist/onboarding.d.ts +3 -1
- package/dist/onboarding.js +77 -3
- package/dist/remote-questions-config.d.ts +1 -1
- package/dist/resources/extensions/google-search/index.ts +164 -47
- package/dist/resources/extensions/gsd/auto-prompts.ts +103 -24
- package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/dist/resources/extensions/gsd/auto.ts +424 -30
- package/dist/resources/extensions/gsd/commands.ts +518 -36
- package/dist/resources/extensions/gsd/context-budget.ts +243 -0
- package/dist/resources/extensions/gsd/context-store.ts +195 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +41 -3
- package/dist/resources/extensions/gsd/db-writer.ts +341 -0
- package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
- package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/dist/resources/extensions/gsd/doctor.ts +283 -2
- package/dist/resources/extensions/gsd/export.ts +81 -2
- package/dist/resources/extensions/gsd/files.ts +39 -9
- package/dist/resources/extensions/gsd/git-service.ts +6 -0
- package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
- package/dist/resources/extensions/gsd/history.ts +0 -1
- package/dist/resources/extensions/gsd/index.ts +277 -1
- package/dist/resources/extensions/gsd/md-importer.ts +526 -0
- package/dist/resources/extensions/gsd/metrics.ts +39 -3
- package/dist/resources/extensions/gsd/notifications.ts +0 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +70 -1
- package/dist/resources/extensions/gsd/preferences.ts +125 -150
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/dist/resources/extensions/gsd/prompts/system.md +2 -1
- package/dist/resources/extensions/gsd/quick.ts +156 -0
- package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/dist/resources/extensions/gsd/skill-health.ts +417 -0
- package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/dist/resources/extensions/gsd/state.ts +30 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
- package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
- package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
- package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/dist/resources/extensions/gsd/types.ts +29 -0
- package/dist/resources/extensions/gsd/undo.ts +0 -1
- package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/dist/resources/extensions/gsd/visualizer-data.ts +352 -1
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +166 -22
- package/dist/resources/extensions/gsd/visualizer-views.ts +464 -2
- package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/dist/resources/extensions/remote-questions/config.ts +4 -2
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +2 -4
- package/dist/resources/extensions/remote-questions/format.ts +154 -8
- package/dist/resources/extensions/remote-questions/manager.ts +9 -7
- package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/dist/resources/extensions/remote-questions/types.ts +2 -1
- package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- package/dist/resources/extensions/voice/index.ts +4 -3
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
- package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
- package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
- package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
- package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
- package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
- package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
- package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
- package/src/resources/extensions/google-search/index.ts +164 -47
- package/src/resources/extensions/gsd/auto-prompts.ts +103 -24
- package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/src/resources/extensions/gsd/auto.ts +424 -30
- package/src/resources/extensions/gsd/commands.ts +518 -36
- package/src/resources/extensions/gsd/context-budget.ts +243 -0
- package/src/resources/extensions/gsd/context-store.ts +195 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +41 -3
- package/src/resources/extensions/gsd/db-writer.ts +341 -0
- package/src/resources/extensions/gsd/debug-logger.ts +178 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/src/resources/extensions/gsd/doctor.ts +283 -2
- package/src/resources/extensions/gsd/export.ts +81 -2
- package/src/resources/extensions/gsd/files.ts +39 -9
- package/src/resources/extensions/gsd/git-service.ts +6 -0
- package/src/resources/extensions/gsd/gsd-db.ts +752 -0
- package/src/resources/extensions/gsd/guided-flow.ts +26 -1
- package/src/resources/extensions/gsd/history.ts +0 -1
- package/src/resources/extensions/gsd/index.ts +277 -1
- package/src/resources/extensions/gsd/md-importer.ts +526 -0
- package/src/resources/extensions/gsd/metrics.ts +39 -3
- package/src/resources/extensions/gsd/notifications.ts +0 -1
- package/src/resources/extensions/gsd/post-unit-hooks.ts +70 -1
- package/src/resources/extensions/gsd/preferences.ts +125 -150
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/src/resources/extensions/gsd/prompts/system.md +2 -1
- package/src/resources/extensions/gsd/quick.ts +156 -0
- package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/src/resources/extensions/gsd/skill-health.ts +417 -0
- package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/src/resources/extensions/gsd/state.ts +30 -0
- package/src/resources/extensions/gsd/templates/preferences.md +1 -0
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
- package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/src/resources/extensions/gsd/types.ts +29 -0
- package/src/resources/extensions/gsd/undo.ts +0 -1
- package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +352 -1
- package/src/resources/extensions/gsd/visualizer-overlay.ts +166 -22
- package/src/resources/extensions/gsd/visualizer-views.ts +464 -2
- package/src/resources/extensions/gsd/worktree-command.ts +18 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/src/resources/extensions/remote-questions/config.ts +4 -2
- package/src/resources/extensions/remote-questions/discord-adapter.ts +2 -4
- package/src/resources/extensions/remote-questions/format.ts +154 -8
- package/src/resources/extensions/remote-questions/manager.ts +9 -7
- package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/src/resources/extensions/remote-questions/types.ts +2 -1
- package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- package/src/resources/extensions/voice/index.ts +4 -3
|
@@ -64,8 +64,17 @@ import {
|
|
|
64
64
|
formatValidationIssues,
|
|
65
65
|
} from "./observability-validator.js";
|
|
66
66
|
import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
|
67
|
-
import { runGSDDoctor, rebuildState } from "./doctor.js";
|
|
67
|
+
import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
|
|
68
|
+
import {
|
|
69
|
+
preDispatchHealthGate,
|
|
70
|
+
recordHealthSnapshot,
|
|
71
|
+
checkHealEscalation,
|
|
72
|
+
resetProactiveHealing,
|
|
73
|
+
formatHealthSummary,
|
|
74
|
+
getConsecutiveErrorUnits,
|
|
75
|
+
} from "./doctor-proactive.js";
|
|
68
76
|
import { snapshotSkills, clearSkillSnapshot } from "./skill-discovery.js";
|
|
77
|
+
import { captureAvailableSkills, getAndClearSkills, resetSkillTelemetry } from "./skill-telemetry.js";
|
|
69
78
|
import {
|
|
70
79
|
initMetrics, resetMetrics, snapshotUnitMetrics, getLedger,
|
|
71
80
|
getProjectTotals, formatCost, formatTokenCount,
|
|
@@ -100,6 +109,7 @@ import {
|
|
|
100
109
|
} from "./auto-worktree.js";
|
|
101
110
|
import { pruneQueueOrder } from "./queue-order.js";
|
|
102
111
|
import { showNextAction } from "../shared/next-action-ui.js";
|
|
112
|
+
import { debugLog, debugTime, debugCount, debugPeak, enableDebug, isDebugEnabled, writeDebugSummary, getDebugLogPath } from "./debug-logger.js";
|
|
103
113
|
import {
|
|
104
114
|
resolveExpectedArtifactPath,
|
|
105
115
|
verifyExpectedArtifact,
|
|
@@ -133,6 +143,7 @@ import {
|
|
|
133
143
|
deregisterSigtermHandler as _deregisterSigtermHandler,
|
|
134
144
|
detectWorkingTreeActivity,
|
|
135
145
|
} from "./auto-supervisor.js";
|
|
146
|
+
import { isDbAvailable } from "./gsd-db.js";
|
|
136
147
|
import { hasPendingCaptures, loadPendingCaptures, countPendingCaptures } from "./captures.js";
|
|
137
148
|
|
|
138
149
|
// ─── State ────────────────────────────────────────────────────────────────────
|
|
@@ -241,6 +252,15 @@ let currentUnit: { type: string; id: string; startedAt: number } | null = null;
|
|
|
241
252
|
/** Track dynamic routing decision for the current unit (for metrics) */
|
|
242
253
|
let currentUnitRouting: { tier: string; modelDowngraded: boolean } | null = null;
|
|
243
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Model captured at auto-mode start. Used to prevent model bleed between
|
|
257
|
+
* concurrent GSD instances sharing the same global settings.json (#650).
|
|
258
|
+
* When preferences don't specify a model for a unit type, this ensures
|
|
259
|
+
* the session's original model is re-applied instead of reading from
|
|
260
|
+
* the shared global settings (which another instance may have overwritten).
|
|
261
|
+
*/
|
|
262
|
+
let autoModeStartModel: { provider: string; id: string } | null = null;
|
|
263
|
+
|
|
244
264
|
/** Track current milestone to detect transitions */
|
|
245
265
|
let currentMilestoneId: string | null = null;
|
|
246
266
|
let lastBudgetAlertLevel: BudgetAlertLevel = 0;
|
|
@@ -262,6 +282,10 @@ let idleWatchdogHandle: ReturnType<typeof setInterval> | null = null;
|
|
|
262
282
|
let dispatchGapHandle: ReturnType<typeof setTimeout> | null = null;
|
|
263
283
|
const DISPATCH_GAP_TIMEOUT_MS = 5_000; // 5 seconds
|
|
264
284
|
|
|
285
|
+
/** Prompt character measurement for token savings analysis (R051). */
|
|
286
|
+
let lastPromptCharCount: number | undefined;
|
|
287
|
+
let lastBaselineCharCount: number | undefined;
|
|
288
|
+
|
|
265
289
|
/** SIGTERM handler registered while auto-mode is active — cleared on stop/pause. */
|
|
266
290
|
let _sigtermHandler: (() => void) | null = null;
|
|
267
291
|
|
|
@@ -475,6 +499,7 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI): Promi
|
|
|
475
499
|
clearUnitTimeout();
|
|
476
500
|
if (lockBase()) clearLock(lockBase());
|
|
477
501
|
clearSkillSnapshot();
|
|
502
|
+
resetSkillTelemetry();
|
|
478
503
|
_dispatching = false;
|
|
479
504
|
_skipDepth = 0;
|
|
480
505
|
|
|
@@ -482,12 +507,17 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI): Promi
|
|
|
482
507
|
deregisterSigtermHandler();
|
|
483
508
|
|
|
484
509
|
// ── Auto-worktree: exit worktree and reset basePath on stop ──
|
|
510
|
+
// Preserve the milestone branch so the next /gsd auto can re-enter
|
|
511
|
+
// where it left off. The branch is only deleted during milestone
|
|
512
|
+
// completion (mergeMilestoneToMain) after the work has been squash-merged.
|
|
485
513
|
if (currentMilestoneId && isInAutoWorktree(basePath)) {
|
|
486
514
|
try {
|
|
487
|
-
|
|
515
|
+
// Auto-commit any dirty state before leaving so work isn't lost
|
|
516
|
+
try { autoCommitCurrentBranch(basePath, "stop", currentMilestoneId); } catch { /* non-fatal */ }
|
|
517
|
+
teardownAutoWorktree(originalBasePath, currentMilestoneId, { preserveBranch: true });
|
|
488
518
|
basePath = originalBasePath;
|
|
489
519
|
gitService = new GitServiceImpl(basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
490
|
-
ctx?.ui.notify("Exited auto-worktree.", "info");
|
|
520
|
+
ctx?.ui.notify("Exited auto-worktree (branch preserved for resume).", "info");
|
|
491
521
|
} catch (err) {
|
|
492
522
|
ctx?.ui.notify(
|
|
493
523
|
`Auto-worktree teardown failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -496,6 +526,14 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI): Promi
|
|
|
496
526
|
}
|
|
497
527
|
}
|
|
498
528
|
|
|
529
|
+
// ── DB cleanup: close the SQLite connection ──
|
|
530
|
+
if (isDbAvailable()) {
|
|
531
|
+
try {
|
|
532
|
+
const { closeDatabase } = await import("./gsd-db.js");
|
|
533
|
+
closeDatabase();
|
|
534
|
+
} catch { /* non-fatal */ }
|
|
535
|
+
}
|
|
536
|
+
|
|
499
537
|
// Always restore cwd to project root on stop (#608).
|
|
500
538
|
// Even if isInAutoWorktree returned false (e.g., module state was already
|
|
501
539
|
// cleared by mergeMilestoneToMain), the process cwd may still be inside
|
|
@@ -521,6 +559,14 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI): Promi
|
|
|
521
559
|
try { await rebuildState(basePath); } catch { /* non-fatal */ }
|
|
522
560
|
}
|
|
523
561
|
|
|
562
|
+
// Write debug summary before resetting state
|
|
563
|
+
if (isDebugEnabled()) {
|
|
564
|
+
const logPath = writeDebugSummary();
|
|
565
|
+
if (logPath) {
|
|
566
|
+
ctx?.ui.notify(`Debug log written → ${logPath}`, "info");
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
524
570
|
resetMetrics();
|
|
525
571
|
resetRoutingHistory();
|
|
526
572
|
resetHookState();
|
|
@@ -534,11 +580,13 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI): Promi
|
|
|
534
580
|
lastBudgetAlertLevel = 0;
|
|
535
581
|
unitLifetimeDispatches.clear();
|
|
536
582
|
currentUnit = null;
|
|
583
|
+
autoModeStartModel = null;
|
|
537
584
|
currentMilestoneId = null;
|
|
538
585
|
originalBasePath = "";
|
|
539
586
|
completedUnits = [];
|
|
540
587
|
clearSliceProgressCache();
|
|
541
588
|
clearActivityLogState();
|
|
589
|
+
resetProactiveHealing();
|
|
542
590
|
pendingCrashRecovery = null;
|
|
543
591
|
_handlingAgentEnd = false;
|
|
544
592
|
ctx?.ui.setStatus("gsd-auto", undefined);
|
|
@@ -725,27 +773,122 @@ export async function startAuto(
|
|
|
725
773
|
clearLock(base);
|
|
726
774
|
}
|
|
727
775
|
|
|
728
|
-
|
|
776
|
+
// ── Debug mode: env-var activation ──────────────────────────────────────
|
|
777
|
+
if (!isDebugEnabled() && process.env.GSD_DEBUG === "1") {
|
|
778
|
+
enableDebug(base);
|
|
779
|
+
}
|
|
780
|
+
if (isDebugEnabled()) {
|
|
781
|
+
const { isNativeParserAvailable } = await import("./native-parser-bridge.js");
|
|
782
|
+
debugLog("debug-start", {
|
|
783
|
+
platform: process.platform,
|
|
784
|
+
arch: process.arch,
|
|
785
|
+
node: process.version,
|
|
786
|
+
model: ctx.model?.id ?? "unknown",
|
|
787
|
+
provider: ctx.model?.provider ?? "unknown",
|
|
788
|
+
nativeParser: isNativeParserAvailable(),
|
|
789
|
+
cwd: base,
|
|
790
|
+
});
|
|
791
|
+
ctx.ui.notify(`Debug logging enabled → ${getDebugLogPath()}`, "info");
|
|
792
|
+
}
|
|
729
793
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
794
|
+
let state = await deriveState(base);
|
|
795
|
+
|
|
796
|
+
// ── Milestone branch recovery (#601) ─────────────────────────────────────
|
|
797
|
+
// When auto-mode was previously stopped, the milestone branch is preserved
|
|
798
|
+
// but the worktree is removed. The project root (integration branch) may
|
|
799
|
+
// not have the roadmap/artifacts — they live on the milestone branch.
|
|
800
|
+
// If state looks like pre-planning but a milestone branch exists with prior
|
|
801
|
+
// work, skip the early-return checks and let worktree setup + dispatch
|
|
802
|
+
// handle it correctly from the branch's state.
|
|
803
|
+
let hasSurvivorBranch = false;
|
|
804
|
+
if (
|
|
805
|
+
state.activeMilestone &&
|
|
806
|
+
(state.phase === "pre-planning" || state.phase === "needs-discussion") &&
|
|
807
|
+
shouldUseWorktreeIsolation() &&
|
|
808
|
+
!detectWorktreeName(base) &&
|
|
809
|
+
!base.includes(`${pathSep}.gsd${pathSep}worktrees${pathSep}`)
|
|
810
|
+
) {
|
|
811
|
+
const milestoneBranch = `milestone/${state.activeMilestone.id}`;
|
|
812
|
+
const { nativeBranchExists } = await import("./native-git-bridge.js");
|
|
813
|
+
hasSurvivorBranch = nativeBranchExists(base, milestoneBranch);
|
|
814
|
+
if (hasSurvivorBranch) {
|
|
815
|
+
ctx.ui.notify(
|
|
816
|
+
`Found prior session branch ${milestoneBranch}. Resuming.`,
|
|
817
|
+
"info",
|
|
818
|
+
);
|
|
819
|
+
}
|
|
735
820
|
}
|
|
736
821
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
822
|
+
if (!hasSurvivorBranch) {
|
|
823
|
+
// No active work at all — start a new milestone via the discuss flow.
|
|
824
|
+
// After discussion completes, checkAutoStartAfterDiscuss() (fired from
|
|
825
|
+
// agent_end) will detect the new CONTEXT.md and restart auto mode.
|
|
826
|
+
// If the LLM didn't follow the discussion protocol (e.g. started editing
|
|
827
|
+
// files directly for a simple task), we re-derive state and either proceed
|
|
828
|
+
// with what was created or notify the user clearly (#609).
|
|
829
|
+
if (!state.activeMilestone || state.phase === "complete") {
|
|
744
830
|
const { showSmartEntry } = await import("./guided-flow.js");
|
|
745
831
|
await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
|
|
746
|
-
|
|
832
|
+
|
|
833
|
+
// Re-derive state after discussion — the LLM may have created artifacts
|
|
834
|
+
// even if it didn't follow the full protocol.
|
|
835
|
+
invalidateAllCaches();
|
|
836
|
+
const postState = await deriveState(base);
|
|
837
|
+
if (postState.activeMilestone && postState.phase !== "complete" && postState.phase !== "pre-planning") {
|
|
838
|
+
state = postState;
|
|
839
|
+
} else if (postState.activeMilestone && postState.phase === "pre-planning") {
|
|
840
|
+
const contextFile = resolveMilestoneFile(base, postState.activeMilestone.id, "CONTEXT");
|
|
841
|
+
const hasContext = !!(contextFile && await loadFile(contextFile));
|
|
842
|
+
if (hasContext) {
|
|
843
|
+
state = postState;
|
|
844
|
+
} else {
|
|
845
|
+
ctx.ui.notify(
|
|
846
|
+
"Discussion completed but no milestone context was written. Run /gsd to try the discussion again, or /gsd auto after creating the milestone manually.",
|
|
847
|
+
"warning",
|
|
848
|
+
);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
} else {
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
747
854
|
}
|
|
748
|
-
|
|
855
|
+
|
|
856
|
+
// Active milestone exists but has no roadmap — check if context exists.
|
|
857
|
+
// If context was pre-written (multi-milestone planning), auto-mode can
|
|
858
|
+
// research and plan it. If no context either, need user discussion.
|
|
859
|
+
if (state.phase === "pre-planning") {
|
|
860
|
+
const mid = state.activeMilestone!.id;
|
|
861
|
+
const contextFile = resolveMilestoneFile(base, mid, "CONTEXT");
|
|
862
|
+
const hasContext = !!(contextFile && await loadFile(contextFile));
|
|
863
|
+
if (!hasContext) {
|
|
864
|
+
const { showSmartEntry } = await import("./guided-flow.js");
|
|
865
|
+
await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
|
|
866
|
+
|
|
867
|
+
// Same re-derive pattern as above
|
|
868
|
+
invalidateAllCaches();
|
|
869
|
+
const postState = await deriveState(base);
|
|
870
|
+
if (postState.activeMilestone && postState.phase !== "pre-planning") {
|
|
871
|
+
state = postState;
|
|
872
|
+
} else {
|
|
873
|
+
ctx.ui.notify(
|
|
874
|
+
"Discussion completed but milestone context is still missing. Run /gsd to try again.",
|
|
875
|
+
"warning",
|
|
876
|
+
);
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
// Has context, no roadmap — auto-mode will research + plan it
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// At this point activeMilestone is guaranteed non-null: either
|
|
885
|
+
// hasSurvivorBranch is true (which requires activeMilestone) or
|
|
886
|
+
// the !activeMilestone early-return above would have fired.
|
|
887
|
+
if (!state.activeMilestone) {
|
|
888
|
+
// Unreachable — satisfies TypeScript's null check
|
|
889
|
+
const { showSmartEntry } = await import("./guided-flow.js");
|
|
890
|
+
await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
|
|
891
|
+
return;
|
|
749
892
|
}
|
|
750
893
|
|
|
751
894
|
active = true;
|
|
@@ -761,6 +904,7 @@ export async function startAuto(
|
|
|
761
904
|
loadPersistedKeys(base, completedKeySet);
|
|
762
905
|
resetHookState();
|
|
763
906
|
restoreHookState(base);
|
|
907
|
+
resetProactiveHealing();
|
|
764
908
|
autoStartTime = Date.now();
|
|
765
909
|
resourceSyncedAtOnStart = readResourceSyncedAt();
|
|
766
910
|
completedUnits = [];
|
|
@@ -825,12 +969,47 @@ export async function startAuto(
|
|
|
825
969
|
}
|
|
826
970
|
}
|
|
827
971
|
|
|
972
|
+
// ── DB lifecycle: auto-migrate or open existing database ──
|
|
973
|
+
const gsdDbPath = join(basePath, ".gsd", "gsd.db");
|
|
974
|
+
const gsdDirPath = join(basePath, ".gsd");
|
|
975
|
+
if (existsSync(gsdDirPath) && !existsSync(gsdDbPath)) {
|
|
976
|
+
const hasDecisions = existsSync(join(gsdDirPath, "DECISIONS.md"));
|
|
977
|
+
const hasRequirements = existsSync(join(gsdDirPath, "REQUIREMENTS.md"));
|
|
978
|
+
const hasMilestones = existsSync(join(gsdDirPath, "milestones"));
|
|
979
|
+
if (hasDecisions || hasRequirements || hasMilestones) {
|
|
980
|
+
try {
|
|
981
|
+
const { openDatabase: openDb } = await import("./gsd-db.js");
|
|
982
|
+
const { migrateFromMarkdown } = await import("./md-importer.js");
|
|
983
|
+
openDb(gsdDbPath);
|
|
984
|
+
migrateFromMarkdown(basePath);
|
|
985
|
+
} catch (err) {
|
|
986
|
+
process.stderr.write(`gsd-migrate: auto-migration failed: ${(err as Error).message}\n`);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
if (existsSync(gsdDbPath) && !isDbAvailable()) {
|
|
991
|
+
try {
|
|
992
|
+
const { openDatabase: openDb } = await import("./gsd-db.js");
|
|
993
|
+
openDb(gsdDbPath);
|
|
994
|
+
} catch (err) {
|
|
995
|
+
process.stderr.write(`gsd-db: failed to open existing database: ${(err as Error).message}\n`);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
828
999
|
// Initialize metrics — loads existing ledger from disk
|
|
829
1000
|
initMetrics(base);
|
|
830
1001
|
|
|
831
1002
|
// Initialize routing history for adaptive learning
|
|
832
1003
|
initRoutingHistory(base);
|
|
833
1004
|
|
|
1005
|
+
// Capture the session's current model at auto-mode start (#650).
|
|
1006
|
+
// This prevents model bleed when multiple GSD instances share the
|
|
1007
|
+
// same global settings.json — each instance remembers its own model.
|
|
1008
|
+
const currentModel = ctx.model;
|
|
1009
|
+
if (currentModel) {
|
|
1010
|
+
autoModeStartModel = { provider: currentModel.provider, id: currentModel.id };
|
|
1011
|
+
}
|
|
1012
|
+
|
|
834
1013
|
// Snapshot installed skills so we can detect new ones after research
|
|
835
1014
|
if (resolveSkillDiscoveryMode() !== "off") {
|
|
836
1015
|
snapshotSkills();
|
|
@@ -846,7 +1025,7 @@ export async function startAuto(
|
|
|
846
1025
|
ctx.ui.notify(`${modeLabel} started. ${scopeMsg}`, "info");
|
|
847
1026
|
|
|
848
1027
|
// Secrets collection gate — collect pending secrets before first dispatch
|
|
849
|
-
const mid = state.activeMilestone
|
|
1028
|
+
const mid = state.activeMilestone!.id;
|
|
850
1029
|
try {
|
|
851
1030
|
const manifestStatus = await getManifestStatus(base, mid);
|
|
852
1031
|
if (manifestStatus && manifestStatus.pending.length > 0) {
|
|
@@ -965,6 +1144,35 @@ export async function handleAgentEnd(
|
|
|
965
1144
|
if (report.fixesApplied.length > 0) {
|
|
966
1145
|
ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info");
|
|
967
1146
|
}
|
|
1147
|
+
|
|
1148
|
+
// ── Proactive health tracking ──────────────────────────────────────
|
|
1149
|
+
// Record health snapshot for trend analysis and escalation logic.
|
|
1150
|
+
const summary = summarizeDoctorIssues(report.issues);
|
|
1151
|
+
recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length);
|
|
1152
|
+
|
|
1153
|
+
// Check if we should escalate to LLM-assisted heal
|
|
1154
|
+
if (summary.errors > 0) {
|
|
1155
|
+
const unresolvedErrors = report.issues
|
|
1156
|
+
.filter(i => i.severity === "error" && !i.fixable)
|
|
1157
|
+
.map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
|
|
1158
|
+
const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
|
|
1159
|
+
if (escalation.shouldEscalate) {
|
|
1160
|
+
ctx.ui.notify(
|
|
1161
|
+
`Doctor heal escalation: ${escalation.reason}. Dispatching LLM-assisted heal.`,
|
|
1162
|
+
"warning",
|
|
1163
|
+
);
|
|
1164
|
+
try {
|
|
1165
|
+
const { formatDoctorIssuesForPrompt, formatDoctorReport } = await import("./doctor.js");
|
|
1166
|
+
const { dispatchDoctorHeal } = await import("./commands.js");
|
|
1167
|
+
const actionable = report.issues.filter(i => i.severity === "error");
|
|
1168
|
+
const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
|
|
1169
|
+
const structuredIssues = formatDoctorIssuesForPrompt(actionable);
|
|
1170
|
+
dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
|
|
1171
|
+
} catch {
|
|
1172
|
+
// Non-fatal — escalation dispatch failure
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
968
1176
|
} catch {
|
|
969
1177
|
// Non-fatal — doctor failure should never block dispatch
|
|
970
1178
|
}
|
|
@@ -1025,6 +1233,16 @@ export async function handleAgentEnd(
|
|
|
1025
1233
|
}
|
|
1026
1234
|
}
|
|
1027
1235
|
|
|
1236
|
+
// ── DB dual-write: re-import changed markdown files so next unit's prompts use fresh data ──
|
|
1237
|
+
if (isDbAvailable()) {
|
|
1238
|
+
try {
|
|
1239
|
+
const { migrateFromMarkdown } = await import("./md-importer.js");
|
|
1240
|
+
migrateFromMarkdown(basePath);
|
|
1241
|
+
} catch (err) {
|
|
1242
|
+
process.stderr.write(`gsd-db: re-import failed: ${(err as Error).message}\n`);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1028
1246
|
// ── Post-unit hooks: check if a configured hook should run before normal dispatch ──
|
|
1029
1247
|
if (currentUnit && !stepMode) {
|
|
1030
1248
|
const hookUnit = checkPostUnitHooks(currentUnit.type, currentUnit.id, basePath);
|
|
@@ -1033,7 +1251,7 @@ export async function handleAgentEnd(
|
|
|
1033
1251
|
const hookStartedAt = Date.now();
|
|
1034
1252
|
if (currentUnit) {
|
|
1035
1253
|
const modelId = ctx.model?.id ?? "unknown";
|
|
1036
|
-
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, currentUnitRouting ??
|
|
1254
|
+
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, { promptCharCount: lastPromptCharCount, baselineCharCount: lastBaselineCharCount, ...(currentUnitRouting ?? {}) });
|
|
1037
1255
|
saveActivityLog(ctx, basePath, currentUnit.type, currentUnit.id);
|
|
1038
1256
|
}
|
|
1039
1257
|
currentUnit = { type: hookUnit.unitType, id: hookUnit.unitId, startedAt: hookStartedAt };
|
|
@@ -1421,8 +1639,34 @@ async function dispatchNextUnit(
|
|
|
1421
1639
|
// Parse cache is also cleared — doctor may have re-populated it with
|
|
1422
1640
|
// stale data between handleAgentEnd and this dispatch call (Path B fix).
|
|
1423
1641
|
invalidateAllCaches();
|
|
1642
|
+
lastPromptCharCount = undefined;
|
|
1643
|
+
lastBaselineCharCount = undefined;
|
|
1644
|
+
|
|
1645
|
+
// ── Pre-dispatch health gate ──────────────────────────────────────────
|
|
1646
|
+
// Lightweight check for critical issues that would cause the next unit
|
|
1647
|
+
// to fail or corrupt state. Auto-heals what it can, blocks on the rest.
|
|
1648
|
+
try {
|
|
1649
|
+
const healthGate = preDispatchHealthGate(basePath);
|
|
1650
|
+
if (healthGate.fixesApplied.length > 0) {
|
|
1651
|
+
ctx.ui.notify(`Pre-dispatch: ${healthGate.fixesApplied.join(", ")}`, "info");
|
|
1652
|
+
}
|
|
1653
|
+
if (!healthGate.proceed) {
|
|
1654
|
+
ctx.ui.notify(healthGate.reason ?? "Pre-dispatch health check failed.", "error");
|
|
1655
|
+
await pauseAuto(ctx, pi);
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
} catch {
|
|
1659
|
+
// Non-fatal — health gate failure should never block dispatch
|
|
1660
|
+
}
|
|
1424
1661
|
|
|
1662
|
+
const stopDeriveTimer = debugTime("derive-state");
|
|
1425
1663
|
let state = await deriveState(basePath);
|
|
1664
|
+
stopDeriveTimer({
|
|
1665
|
+
phase: state.phase,
|
|
1666
|
+
milestone: state.activeMilestone?.id,
|
|
1667
|
+
slice: state.activeSlice?.id,
|
|
1668
|
+
task: state.activeTask?.id,
|
|
1669
|
+
});
|
|
1426
1670
|
let mid = state.activeMilestone?.id;
|
|
1427
1671
|
let midTitle = state.activeMilestone?.title;
|
|
1428
1672
|
|
|
@@ -1527,7 +1771,7 @@ async function dispatchNextUnit(
|
|
|
1527
1771
|
// Save final session before stopping
|
|
1528
1772
|
if (currentUnit) {
|
|
1529
1773
|
const modelId = ctx.model?.id ?? "unknown";
|
|
1530
|
-
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, currentUnitRouting ??
|
|
1774
|
+
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, { promptCharCount: lastPromptCharCount, baselineCharCount: lastBaselineCharCount, ...(currentUnitRouting ?? {}) });
|
|
1531
1775
|
saveActivityLog(ctx, basePath, currentUnit.type, currentUnit.id);
|
|
1532
1776
|
}
|
|
1533
1777
|
sendDesktopNotification("GSD", "All milestones complete!", "success", "milestone");
|
|
@@ -1555,7 +1799,7 @@ async function dispatchNextUnit(
|
|
|
1555
1799
|
if (!mid || !midTitle) {
|
|
1556
1800
|
if (currentUnit) {
|
|
1557
1801
|
const modelId = ctx.model?.id ?? "unknown";
|
|
1558
|
-
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, currentUnitRouting ??
|
|
1802
|
+
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, { promptCharCount: lastPromptCharCount, baselineCharCount: lastBaselineCharCount, ...(currentUnitRouting ?? {}) });
|
|
1559
1803
|
saveActivityLog(ctx, basePath, currentUnit.type, currentUnit.id);
|
|
1560
1804
|
}
|
|
1561
1805
|
await stopAuto(ctx, pi);
|
|
@@ -1570,7 +1814,7 @@ async function dispatchNextUnit(
|
|
|
1570
1814
|
if (state.phase === "complete") {
|
|
1571
1815
|
if (currentUnit) {
|
|
1572
1816
|
const modelId = ctx.model?.id ?? "unknown";
|
|
1573
|
-
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, currentUnitRouting ??
|
|
1817
|
+
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, { promptCharCount: lastPromptCharCount, baselineCharCount: lastBaselineCharCount, ...(currentUnitRouting ?? {}) });
|
|
1574
1818
|
saveActivityLog(ctx, basePath, currentUnit.type, currentUnit.id);
|
|
1575
1819
|
}
|
|
1576
1820
|
// Clear completed-units.json for the finished milestone so it doesn't grow unbounded.
|
|
@@ -1640,7 +1884,7 @@ async function dispatchNextUnit(
|
|
|
1640
1884
|
if (state.phase === "blocked") {
|
|
1641
1885
|
if (currentUnit) {
|
|
1642
1886
|
const modelId = ctx.model?.id ?? "unknown";
|
|
1643
|
-
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, currentUnitRouting ??
|
|
1887
|
+
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, { promptCharCount: lastPromptCharCount, baselineCharCount: lastBaselineCharCount, ...(currentUnitRouting ?? {}) });
|
|
1644
1888
|
saveActivityLog(ctx, basePath, currentUnit.type, currentUnit.id);
|
|
1645
1889
|
}
|
|
1646
1890
|
await stopAuto(ctx, pi);
|
|
@@ -1748,7 +1992,7 @@ async function dispatchNextUnit(
|
|
|
1748
1992
|
if (dispatchResult.action === "stop") {
|
|
1749
1993
|
if (currentUnit) {
|
|
1750
1994
|
const modelId = ctx.model?.id ?? "unknown";
|
|
1751
|
-
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, currentUnitRouting ??
|
|
1995
|
+
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, { promptCharCount: lastPromptCharCount, baselineCharCount: lastBaselineCharCount, ...(currentUnitRouting ?? {}) });
|
|
1752
1996
|
saveActivityLog(ctx, basePath, currentUnit.type, currentUnit.id);
|
|
1753
1997
|
}
|
|
1754
1998
|
await stopAuto(ctx, pi);
|
|
@@ -1850,6 +2094,14 @@ async function dispatchNextUnit(
|
|
|
1850
2094
|
const dispatchKey = `${unitType}/${unitId}`;
|
|
1851
2095
|
const prevCount = unitDispatchCount.get(dispatchKey) ?? 0;
|
|
1852
2096
|
|
|
2097
|
+
debugLog("dispatch-unit", {
|
|
2098
|
+
type: unitType,
|
|
2099
|
+
id: unitId,
|
|
2100
|
+
cycle: prevCount + 1,
|
|
2101
|
+
lifetime: (unitLifetimeDispatches.get(dispatchKey) ?? 0) + 1,
|
|
2102
|
+
});
|
|
2103
|
+
debugCount("dispatches");
|
|
2104
|
+
|
|
1853
2105
|
// Hard lifetime cap — survives counter resets from loop-recovery/self-repair.
|
|
1854
2106
|
// Catches the case where reconciliation "succeeds" (artifacts exist) but
|
|
1855
2107
|
// deriveState keeps returning the same unit, creating an infinite cycle.
|
|
@@ -1858,7 +2110,7 @@ async function dispatchNextUnit(
|
|
|
1858
2110
|
if (lifetimeCount > MAX_LIFETIME_DISPATCHES) {
|
|
1859
2111
|
if (currentUnit) {
|
|
1860
2112
|
const modelId = ctx.model?.id ?? "unknown";
|
|
1861
|
-
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, currentUnitRouting ??
|
|
2113
|
+
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, { promptCharCount: lastPromptCharCount, baselineCharCount: lastBaselineCharCount, ...(currentUnitRouting ?? {}) });
|
|
1862
2114
|
}
|
|
1863
2115
|
saveActivityLog(ctx, basePath, unitType, unitId);
|
|
1864
2116
|
const expected = diagnoseExpectedArtifact(unitType, unitId, basePath);
|
|
@@ -1872,7 +2124,7 @@ async function dispatchNextUnit(
|
|
|
1872
2124
|
if (prevCount >= MAX_UNIT_DISPATCHES) {
|
|
1873
2125
|
if (currentUnit) {
|
|
1874
2126
|
const modelId = ctx.model?.id ?? "unknown";
|
|
1875
|
-
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, currentUnitRouting ??
|
|
2127
|
+
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, { promptCharCount: lastPromptCharCount, baselineCharCount: lastBaselineCharCount, ...(currentUnitRouting ?? {}) });
|
|
1876
2128
|
}
|
|
1877
2129
|
saveActivityLog(ctx, basePath, unitType, unitId);
|
|
1878
2130
|
|
|
@@ -2030,7 +2282,7 @@ async function dispatchNextUnit(
|
|
|
2030
2282
|
// The session still holds the previous unit's data (newSession hasn't fired yet).
|
|
2031
2283
|
if (currentUnit) {
|
|
2032
2284
|
const modelId = ctx.model?.id ?? "unknown";
|
|
2033
|
-
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, currentUnitRouting ??
|
|
2285
|
+
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, { promptCharCount: lastPromptCharCount, baselineCharCount: lastBaselineCharCount, ...(currentUnitRouting ?? {}) });
|
|
2034
2286
|
saveActivityLog(ctx, basePath, currentUnit.type, currentUnit.id);
|
|
2035
2287
|
|
|
2036
2288
|
// Record routing outcome for adaptive learning
|
|
@@ -2076,6 +2328,7 @@ async function dispatchNextUnit(
|
|
|
2076
2328
|
}
|
|
2077
2329
|
}
|
|
2078
2330
|
currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
|
|
2331
|
+
captureAvailableSkills(); // Capture skill telemetry at dispatch time (#599)
|
|
2079
2332
|
writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnit.startedAt, {
|
|
2080
2333
|
phase: "dispatched",
|
|
2081
2334
|
wrapupWarningSent: false,
|
|
@@ -2140,6 +2393,26 @@ async function dispatchNextUnit(
|
|
|
2140
2393
|
finalPrompt = `${finalPrompt}${repairBlock}`;
|
|
2141
2394
|
}
|
|
2142
2395
|
|
|
2396
|
+
// ── Prompt char measurement (R051) ──
|
|
2397
|
+
lastPromptCharCount = finalPrompt.length;
|
|
2398
|
+
lastBaselineCharCount = undefined;
|
|
2399
|
+
if (isDbAvailable()) {
|
|
2400
|
+
try {
|
|
2401
|
+
const { inlineGsdRootFile } = await import("./auto-prompts.js");
|
|
2402
|
+
const [decisionsContent, requirementsContent, projectContent] = await Promise.all([
|
|
2403
|
+
inlineGsdRootFile(basePath, "decisions.md", "Decisions"),
|
|
2404
|
+
inlineGsdRootFile(basePath, "requirements.md", "Requirements"),
|
|
2405
|
+
inlineGsdRootFile(basePath, "project.md", "Project"),
|
|
2406
|
+
]);
|
|
2407
|
+
lastBaselineCharCount =
|
|
2408
|
+
(decisionsContent?.length ?? 0) +
|
|
2409
|
+
(requirementsContent?.length ?? 0) +
|
|
2410
|
+
(projectContent?.length ?? 0);
|
|
2411
|
+
} catch {
|
|
2412
|
+
// Non-fatal — baseline measurement is best-effort
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2143
2416
|
// Switch model if preferences specify one for this unit type
|
|
2144
2417
|
// Try primary model, then fallbacks in order if setting fails
|
|
2145
2418
|
const modelConfig = resolveModelWithFallbacksForUnit(unitType);
|
|
@@ -2275,6 +2548,22 @@ async function dispatchNextUnit(
|
|
|
2275
2548
|
}
|
|
2276
2549
|
|
|
2277
2550
|
// modelSet=false is already handled by the "all fallbacks exhausted" warning above
|
|
2551
|
+
} else if (autoModeStartModel) {
|
|
2552
|
+
// No model preference for this unit type — re-apply the model captured
|
|
2553
|
+
// at auto-mode start to prevent bleed from the shared global settings.json
|
|
2554
|
+
// when multiple GSD instances run concurrently (#650).
|
|
2555
|
+
const availableModels = ctx.modelRegistry.getAvailable();
|
|
2556
|
+
const startModel = availableModels.find(
|
|
2557
|
+
m => m.provider === autoModeStartModel!.provider && m.id === autoModeStartModel!.id,
|
|
2558
|
+
);
|
|
2559
|
+
if (startModel) {
|
|
2560
|
+
const ok = await pi.setModel(startModel, { persist: false });
|
|
2561
|
+
if (!ok) {
|
|
2562
|
+
// Fallback: try matching just by ID across providers
|
|
2563
|
+
const byId = availableModels.find(m => m.id === autoModeStartModel!.id);
|
|
2564
|
+
if (byId) await pi.setModel(byId, { persist: false });
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2278
2567
|
}
|
|
2279
2568
|
|
|
2280
2569
|
// Start progress-aware supervision: a soft warning, an idle watchdog, and
|
|
@@ -2340,7 +2629,7 @@ async function dispatchNextUnit(
|
|
|
2340
2629
|
|
|
2341
2630
|
if (currentUnit) {
|
|
2342
2631
|
const modelId = ctx.model?.id ?? "unknown";
|
|
2343
|
-
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, currentUnitRouting ??
|
|
2632
|
+
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, { promptCharCount: lastPromptCharCount, baselineCharCount: lastBaselineCharCount, ...(currentUnitRouting ?? {}) });
|
|
2344
2633
|
}
|
|
2345
2634
|
saveActivityLog(ctx, basePath, unitType, unitId);
|
|
2346
2635
|
|
|
@@ -2366,7 +2655,7 @@ async function dispatchNextUnit(
|
|
|
2366
2655
|
timeoutAt: Date.now(),
|
|
2367
2656
|
});
|
|
2368
2657
|
const modelId = ctx.model?.id ?? "unknown";
|
|
2369
|
-
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, currentUnitRouting ??
|
|
2658
|
+
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId, { promptCharCount: lastPromptCharCount, baselineCharCount: lastBaselineCharCount, ...(currentUnitRouting ?? {}) });
|
|
2370
2659
|
}
|
|
2371
2660
|
saveActivityLog(ctx, basePath, unitType, unitId);
|
|
2372
2661
|
|
|
@@ -2748,3 +3037,108 @@ export {
|
|
|
2748
3037
|
skipExecuteTask,
|
|
2749
3038
|
buildLoopRemediationSteps,
|
|
2750
3039
|
} from "./auto-recovery.js";
|
|
3040
|
+
|
|
3041
|
+
/**
|
|
3042
|
+
* Dispatch a hook unit directly, bypassing normal pre-dispatch hooks.
|
|
3043
|
+
* Used for manual hook triggers via /gsd run-hook.
|
|
3044
|
+
*/
|
|
3045
|
+
export async function dispatchHookUnit(
|
|
3046
|
+
ctx: ExtensionContext,
|
|
3047
|
+
pi: ExtensionAPI,
|
|
3048
|
+
hookName: string,
|
|
3049
|
+
triggerUnitType: string,
|
|
3050
|
+
triggerUnitId: string,
|
|
3051
|
+
hookPrompt: string,
|
|
3052
|
+
hookModel: string | undefined,
|
|
3053
|
+
targetBasePath: string,
|
|
3054
|
+
): Promise<boolean> {
|
|
3055
|
+
// Ensure auto-mode is active
|
|
3056
|
+
if (!active) {
|
|
3057
|
+
// Initialize auto-mode state minimally
|
|
3058
|
+
active = true;
|
|
3059
|
+
stepMode = true;
|
|
3060
|
+
cmdCtx = ctx as ExtensionCommandContext;
|
|
3061
|
+
basePath = targetBasePath;
|
|
3062
|
+
autoStartTime = Date.now();
|
|
3063
|
+
currentUnit = null;
|
|
3064
|
+
completedUnits = [];
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
const hookUnitType = `hook/${hookName}`;
|
|
3068
|
+
const hookStartedAt = Date.now();
|
|
3069
|
+
|
|
3070
|
+
// Set up the trigger unit as the "current" unit so post-unit hooks can reference it
|
|
3071
|
+
currentUnit = { type: triggerUnitType, id: triggerUnitId, startedAt: hookStartedAt };
|
|
3072
|
+
|
|
3073
|
+
// Create a new session for the hook
|
|
3074
|
+
const result = await cmdCtx!.newSession();
|
|
3075
|
+
if (result.cancelled) {
|
|
3076
|
+
await stopAuto(ctx, pi);
|
|
3077
|
+
return false;
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
// Update current unit to the hook unit
|
|
3081
|
+
currentUnit = { type: hookUnitType, id: triggerUnitId, startedAt: hookStartedAt };
|
|
3082
|
+
|
|
3083
|
+
// Write runtime record
|
|
3084
|
+
writeUnitRuntimeRecord(basePath, hookUnitType, triggerUnitId, hookStartedAt, {
|
|
3085
|
+
phase: "dispatched",
|
|
3086
|
+
wrapupWarningSent: false,
|
|
3087
|
+
timeoutAt: null,
|
|
3088
|
+
lastProgressAt: hookStartedAt,
|
|
3089
|
+
progressCount: 0,
|
|
3090
|
+
lastProgressKind: "dispatch",
|
|
3091
|
+
});
|
|
3092
|
+
|
|
3093
|
+
// Switch model if specified
|
|
3094
|
+
if (hookModel) {
|
|
3095
|
+
const availableModels = ctx.modelRegistry.getAvailable();
|
|
3096
|
+
const match = availableModels.find(m =>
|
|
3097
|
+
m.id === hookModel || `${m.provider}/${m.id}` === hookModel,
|
|
3098
|
+
);
|
|
3099
|
+
if (match) {
|
|
3100
|
+
try {
|
|
3101
|
+
await pi.setModel(match);
|
|
3102
|
+
} catch { /* non-fatal — use current model */ }
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
// Write lock
|
|
3107
|
+
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
3108
|
+
writeLock(lockBase(), hookUnitType, triggerUnitId, completedUnits.length, sessionFile);
|
|
3109
|
+
|
|
3110
|
+
// Set up timeout
|
|
3111
|
+
clearUnitTimeout();
|
|
3112
|
+
const supervisor = resolveAutoSupervisorConfig();
|
|
3113
|
+
const hookHardTimeoutMs = (supervisor.hard_timeout_minutes ?? 30) * 60 * 1000;
|
|
3114
|
+
unitTimeoutHandle = setTimeout(async () => {
|
|
3115
|
+
unitTimeoutHandle = null;
|
|
3116
|
+
if (!active) return;
|
|
3117
|
+
if (currentUnit) {
|
|
3118
|
+
writeUnitRuntimeRecord(basePath, hookUnitType, triggerUnitId, hookStartedAt, {
|
|
3119
|
+
phase: "timeout",
|
|
3120
|
+
timeoutAt: Date.now(),
|
|
3121
|
+
});
|
|
3122
|
+
}
|
|
3123
|
+
ctx.ui.notify(
|
|
3124
|
+
`Hook ${hookName} exceeded ${supervisor.hard_timeout_minutes ?? 30}min timeout. Pausing auto-mode.`,
|
|
3125
|
+
"warning",
|
|
3126
|
+
);
|
|
3127
|
+
resetHookState();
|
|
3128
|
+
await pauseAuto(ctx, pi);
|
|
3129
|
+
}, hookHardTimeoutMs);
|
|
3130
|
+
|
|
3131
|
+
// Update status
|
|
3132
|
+
ctx.ui.setStatus("gsd-auto", stepMode ? "next" : "auto");
|
|
3133
|
+
ctx.ui.notify(`Running post-unit hook: ${hookName}`, "info");
|
|
3134
|
+
|
|
3135
|
+
// Send the hook prompt
|
|
3136
|
+
console.log(`[dispatchHookUnit] Sending prompt of length ${hookPrompt.length}`);
|
|
3137
|
+
console.log(`[dispatchHookUnit] Prompt preview: ${hookPrompt.substring(0, 200)}...`);
|
|
3138
|
+
pi.sendMessage(
|
|
3139
|
+
{ customType: "gsd-auto", content: hookPrompt, display: true },
|
|
3140
|
+
{ triggerTurn: true },
|
|
3141
|
+
);
|
|
3142
|
+
|
|
3143
|
+
return true;
|
|
3144
|
+
}
|