gsd-pi 2.28.0-dev.e19bf89 → 2.29.0-dev.2ccf3fb
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 +24 -17
- package/dist/cli.js +15 -9
- package/dist/resource-loader.js +80 -8
- package/dist/resources/extensions/bg-shell/process-manager.ts +13 -0
- package/dist/resources/extensions/gsd/auto-dashboard.ts +186 -65
- package/dist/resources/extensions/gsd/auto-post-unit.ts +14 -6
- package/dist/resources/extensions/gsd/auto-recovery.ts +33 -23
- package/dist/resources/extensions/gsd/auto-start.ts +25 -10
- package/dist/resources/extensions/gsd/auto-verification.ts +41 -7
- package/dist/resources/extensions/gsd/auto-worktree-sync.ts +21 -6
- package/dist/resources/extensions/gsd/auto.ts +67 -22
- package/dist/resources/extensions/gsd/commands-handlers.ts +3 -11
- package/dist/resources/extensions/gsd/commands-logs.ts +536 -0
- package/dist/resources/extensions/gsd/commands-prefs-wizard.ts +90 -47
- package/dist/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
- package/dist/resources/extensions/gsd/commands.ts +75 -29
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +2 -1
- package/dist/resources/extensions/gsd/doctor-types.ts +13 -0
- package/dist/resources/extensions/gsd/doctor.ts +2 -6
- package/dist/resources/extensions/gsd/export.ts +28 -2
- package/dist/resources/extensions/gsd/gsd-db.ts +19 -0
- package/dist/resources/extensions/gsd/index.ts +2 -1
- package/dist/resources/extensions/gsd/json-persistence.ts +67 -0
- package/dist/resources/extensions/gsd/metrics.ts +17 -31
- package/dist/resources/extensions/gsd/paths.ts +0 -8
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +28 -0
- package/dist/resources/extensions/gsd/queue-order.ts +10 -11
- package/dist/resources/extensions/gsd/routing-history.ts +13 -17
- package/dist/resources/extensions/gsd/session-lock.ts +284 -0
- package/dist/resources/extensions/gsd/session-status-io.ts +23 -41
- package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
- package/dist/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
- package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
- package/dist/resources/extensions/gsd/tests/verification-evidence.test.ts +26 -24
- package/dist/resources/extensions/gsd/tests/verification-gate.test.ts +136 -7
- package/dist/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
- package/dist/resources/extensions/gsd/types.ts +1 -0
- package/dist/resources/extensions/gsd/unit-runtime.ts +16 -13
- package/dist/resources/extensions/gsd/verification-evidence.ts +2 -0
- package/dist/resources/extensions/gsd/verification-gate.ts +13 -2
- package/dist/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
- package/dist/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
- package/dist/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
- package/dist/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
- package/dist/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
- package/dist/resources/extensions/gsd/workflow-templates/registry.json +85 -0
- package/dist/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
- package/dist/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
- package/dist/resources/extensions/gsd/workflow-templates/spike.md +69 -0
- package/dist/resources/extensions/gsd/workflow-templates.ts +241 -0
- package/dist/resources/extensions/mcp-client/index.ts +459 -0
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +9 -20
- package/dist/resources/extensions/remote-questions/http-client.ts +76 -0
- package/dist/resources/extensions/remote-questions/notify.ts +1 -2
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +11 -18
- package/dist/resources/extensions/remote-questions/telegram-adapter.ts +8 -20
- package/dist/resources/extensions/remote-questions/types.ts +3 -0
- package/dist/resources/extensions/shared/mod.ts +3 -0
- package/dist/resources/skills/create-gsd-extension/SKILL.md +87 -0
- package/dist/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
- package/dist/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
- package/dist/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
- package/dist/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
- package/dist/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
- package/dist/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
- package/dist/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
- package/dist/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
- package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
- package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
- package/dist/resources/skills/create-gsd-extension/references/state-management.md +70 -0
- package/dist/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
- package/dist/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
- package/dist/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
- package/dist/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
- package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
- package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
- package/dist/resources/skills/create-skill/SKILL.md +184 -0
- package/dist/resources/skills/create-skill/references/api-security.md +226 -0
- package/dist/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
- package/dist/resources/skills/create-skill/references/common-patterns.md +595 -0
- package/dist/resources/skills/create-skill/references/core-principles.md +437 -0
- package/dist/resources/skills/create-skill/references/executable-code.md +175 -0
- package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
- package/dist/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
- package/dist/resources/skills/create-skill/references/recommended-structure.md +168 -0
- package/dist/resources/skills/create-skill/references/skill-structure.md +372 -0
- package/dist/resources/skills/create-skill/references/use-xml-tags.md +466 -0
- package/dist/resources/skills/create-skill/references/using-scripts.md +113 -0
- package/dist/resources/skills/create-skill/references/using-templates.md +112 -0
- package/dist/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
- package/dist/resources/skills/create-skill/templates/router-skill.md +73 -0
- package/dist/resources/skills/create-skill/templates/simple-skill.md +33 -0
- package/dist/resources/skills/create-skill/workflows/add-reference.md +96 -0
- package/dist/resources/skills/create-skill/workflows/add-script.md +93 -0
- package/dist/resources/skills/create-skill/workflows/add-template.md +74 -0
- package/dist/resources/skills/create-skill/workflows/add-workflow.md +120 -0
- package/dist/resources/skills/create-skill/workflows/audit-skill.md +148 -0
- package/dist/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
- package/dist/resources/skills/create-skill/workflows/get-guidance.md +121 -0
- package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
- package/dist/resources/skills/create-skill/workflows/verify-skill.md +204 -0
- package/package.json +6 -3
- package/packages/native/dist/native.d.ts +2 -0
- package/packages/native/dist/native.js +19 -5
- package/packages/native/src/native.ts +23 -9
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +13 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -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 +8 -0
- 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 +10 -0
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/scripts/copy-assets.cjs +39 -8
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +13 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +11 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +11 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -1
- package/packages/pi-tui/dist/autocomplete.d.ts +3 -0
- package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/packages/pi-tui/dist/autocomplete.js +14 -0
- package/packages/pi-tui/dist/autocomplete.js.map +1 -1
- package/packages/pi-tui/src/autocomplete.ts +19 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/bg-shell/process-manager.ts +13 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +186 -65
- package/src/resources/extensions/gsd/auto-post-unit.ts +14 -6
- package/src/resources/extensions/gsd/auto-recovery.ts +33 -23
- package/src/resources/extensions/gsd/auto-start.ts +25 -10
- package/src/resources/extensions/gsd/auto-verification.ts +41 -7
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +21 -6
- package/src/resources/extensions/gsd/auto.ts +67 -22
- package/src/resources/extensions/gsd/commands-handlers.ts +3 -11
- package/src/resources/extensions/gsd/commands-logs.ts +536 -0
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +90 -47
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
- package/src/resources/extensions/gsd/commands.ts +75 -29
- package/src/resources/extensions/gsd/dashboard-overlay.ts +2 -1
- package/src/resources/extensions/gsd/doctor-types.ts +13 -0
- package/src/resources/extensions/gsd/doctor.ts +2 -6
- package/src/resources/extensions/gsd/export.ts +28 -2
- package/src/resources/extensions/gsd/gsd-db.ts +19 -0
- package/src/resources/extensions/gsd/index.ts +2 -1
- package/src/resources/extensions/gsd/json-persistence.ts +67 -0
- package/src/resources/extensions/gsd/metrics.ts +17 -31
- package/src/resources/extensions/gsd/paths.ts +0 -8
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/workflow-start.md +28 -0
- package/src/resources/extensions/gsd/queue-order.ts +10 -11
- package/src/resources/extensions/gsd/routing-history.ts +13 -17
- package/src/resources/extensions/gsd/session-lock.ts +284 -0
- package/src/resources/extensions/gsd/session-status-io.ts +23 -41
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
- package/src/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +26 -24
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +136 -7
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
- package/src/resources/extensions/gsd/types.ts +1 -0
- package/src/resources/extensions/gsd/unit-runtime.ts +16 -13
- package/src/resources/extensions/gsd/verification-evidence.ts +2 -0
- package/src/resources/extensions/gsd/verification-gate.ts +13 -2
- package/src/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
- package/src/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
- package/src/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
- package/src/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
- package/src/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
- package/src/resources/extensions/gsd/workflow-templates/registry.json +85 -0
- package/src/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
- package/src/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
- package/src/resources/extensions/gsd/workflow-templates/spike.md +69 -0
- package/src/resources/extensions/gsd/workflow-templates.ts +241 -0
- package/src/resources/extensions/mcp-client/index.ts +459 -0
- package/src/resources/extensions/remote-questions/discord-adapter.ts +9 -20
- package/src/resources/extensions/remote-questions/http-client.ts +76 -0
- package/src/resources/extensions/remote-questions/notify.ts +1 -2
- package/src/resources/extensions/remote-questions/slack-adapter.ts +11 -18
- package/src/resources/extensions/remote-questions/telegram-adapter.ts +8 -20
- package/src/resources/extensions/remote-questions/types.ts +3 -0
- package/src/resources/extensions/shared/mod.ts +3 -0
- package/src/resources/skills/create-gsd-extension/SKILL.md +87 -0
- package/src/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
- package/src/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
- package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
- package/src/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
- package/src/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
- package/src/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
- package/src/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
- package/src/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
- package/src/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
- package/src/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
- package/src/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
- package/src/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
- package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
- package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
- package/src/resources/skills/create-gsd-extension/references/state-management.md +70 -0
- package/src/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
- package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
- package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
- package/src/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
- package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
- package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
- package/src/resources/skills/create-skill/SKILL.md +184 -0
- package/src/resources/skills/create-skill/references/api-security.md +226 -0
- package/src/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
- package/src/resources/skills/create-skill/references/common-patterns.md +595 -0
- package/src/resources/skills/create-skill/references/core-principles.md +437 -0
- package/src/resources/skills/create-skill/references/executable-code.md +175 -0
- package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
- package/src/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
- package/src/resources/skills/create-skill/references/recommended-structure.md +168 -0
- package/src/resources/skills/create-skill/references/skill-structure.md +372 -0
- package/src/resources/skills/create-skill/references/use-xml-tags.md +466 -0
- package/src/resources/skills/create-skill/references/using-scripts.md +113 -0
- package/src/resources/skills/create-skill/references/using-templates.md +112 -0
- package/src/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
- package/src/resources/skills/create-skill/templates/router-skill.md +73 -0
- package/src/resources/skills/create-skill/templates/simple-skill.md +33 -0
- package/src/resources/skills/create-skill/workflows/add-reference.md +96 -0
- package/src/resources/skills/create-skill/workflows/add-script.md +93 -0
- package/src/resources/skills/create-skill/workflows/add-template.md +74 -0
- package/src/resources/skills/create-skill/workflows/add-workflow.md +120 -0
- package/src/resources/skills/create-skill/workflows/audit-skill.md +148 -0
- package/src/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
- package/src/resources/skills/create-skill/workflows/get-guidance.md +121 -0
- package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
- package/src/resources/skills/create-skill/workflows/verify-skill.md +204 -0
- package/dist/resources/extensions/gsd/preferences-hooks.ts +0 -10
- package/dist/resources/extensions/mcporter/index.ts +0 -525
- package/dist/resources/extensions/shared/progress-widget.ts +0 -282
- package/dist/resources/extensions/shared/thinking-widget.ts +0 -107
- package/src/resources/extensions/gsd/preferences-hooks.ts +0 -10
- package/src/resources/extensions/mcporter/index.ts +0 -525
- package/src/resources/extensions/shared/progress-widget.ts +0 -282
- package/src/resources/extensions/shared/thinking-widget.ts +0 -107
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
import { writeUnitRuntimeRecord, clearUnitRuntimeRecord } from "./unit-runtime.js";
|
|
36
36
|
import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences } from "./preferences.js";
|
|
37
37
|
import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
|
|
38
|
+
import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
|
|
38
39
|
import { recordHealthSnapshot, checkHealEscalation } from "./doctor-proactive.js";
|
|
39
40
|
import { syncStateToProjectRoot } from "./auto-worktree-sync.js";
|
|
40
41
|
import { resetRewriteCircuitBreaker } from "./auto-dispatch.js";
|
|
@@ -154,13 +155,17 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
154
155
|
ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info");
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
// Proactive health tracking
|
|
158
|
-
|
|
158
|
+
// Proactive health tracking — exclude completion-transition codes at task level
|
|
159
|
+
// since they are expected after the last task and resolved by complete-slice
|
|
160
|
+
const issuesForHealth = effectiveFixLevel === "task"
|
|
161
|
+
? report.issues.filter(i => !COMPLETION_TRANSITION_CODES.has(i.code))
|
|
162
|
+
: report.issues;
|
|
163
|
+
const summary = summarizeDoctorIssues(issuesForHealth);
|
|
159
164
|
recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length);
|
|
160
165
|
|
|
161
166
|
// Check if we should escalate to LLM-assisted heal
|
|
162
167
|
if (summary.errors > 0) {
|
|
163
|
-
const unresolvedErrors =
|
|
168
|
+
const unresolvedErrors = issuesForHealth
|
|
164
169
|
.filter(i => i.severity === "error" && !i.fixable)
|
|
165
170
|
.map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
|
|
166
171
|
const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
|
|
@@ -171,7 +176,7 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
171
176
|
);
|
|
172
177
|
try {
|
|
173
178
|
const { formatDoctorIssuesForPrompt, formatDoctorReport } = await import("./doctor.js");
|
|
174
|
-
const { dispatchDoctorHeal } = await import("./commands.js");
|
|
179
|
+
const { dispatchDoctorHeal } = await import("./commands-handlers.js");
|
|
175
180
|
const actionable = report.issues.filter(i => i.severity === "error");
|
|
176
181
|
const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
|
|
177
182
|
const structuredIssues = formatDoctorIssuesForPrompt(actionable);
|
|
@@ -197,10 +202,13 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
197
202
|
}
|
|
198
203
|
}
|
|
199
204
|
|
|
200
|
-
// Prune dead bg-shell processes
|
|
205
|
+
// Prune dead bg-shell processes and kill non-persistent live ones.
|
|
206
|
+
// Without killing live processes between units, dev servers spawned during
|
|
207
|
+
// one task keep ports bound, causing conflicts in subsequent tasks (#1209).
|
|
201
208
|
try {
|
|
202
|
-
const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js");
|
|
209
|
+
const { pruneDeadProcesses, killSessionProcesses } = await import("../bg-shell/process-manager.js");
|
|
203
210
|
pruneDeadProcesses();
|
|
211
|
+
killSessionProcesses();
|
|
204
212
|
} catch {
|
|
205
213
|
// Non-fatal
|
|
206
214
|
}
|
|
@@ -36,8 +36,10 @@ import {
|
|
|
36
36
|
clearPathCache,
|
|
37
37
|
resolveGsdRootFile,
|
|
38
38
|
} from "./paths.js";
|
|
39
|
+
import { isValidationTerminal } from "./state.js";
|
|
39
40
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
40
41
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
42
|
+
import { loadJsonFileOrNull } from "./json-persistence.js";
|
|
41
43
|
import { dirname, join } from "node:path";
|
|
42
44
|
|
|
43
45
|
// ─── Artifact Resolution & Verification ───────────────────────────────────────
|
|
@@ -137,6 +139,21 @@ export function verifyExpectedArtifact(unitType: string, unitId: string, base: s
|
|
|
137
139
|
if (!absPath) return false;
|
|
138
140
|
if (!existsSync(absPath)) return false;
|
|
139
141
|
|
|
142
|
+
// validate-milestone must have a VALIDATION file with a terminal verdict
|
|
143
|
+
// (pass, needs-attention, or needs-remediation). Without this check, a
|
|
144
|
+
// VALIDATION file with missing/malformed frontmatter or an unrecognized
|
|
145
|
+
// verdict is treated as "complete" by the artifact check but deriveState
|
|
146
|
+
// still returns phase:"validating-milestone" (because isValidationTerminal
|
|
147
|
+
// returns false), creating an infinite skip loop that hits the lifetime cap.
|
|
148
|
+
if (unitType === "validate-milestone") {
|
|
149
|
+
try {
|
|
150
|
+
const validationContent = readFileSync(absPath, "utf-8");
|
|
151
|
+
if (!isValidationTerminal(validationContent)) return false;
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
140
157
|
// plan-slice must produce a plan with actual task entries, not just a scaffold.
|
|
141
158
|
// The plan file may exist from a prior discussion/context step with only headings
|
|
142
159
|
// but no tasks. Without this check the artifact is considered "complete" and the
|
|
@@ -211,7 +228,7 @@ export function verifyExpectedArtifact(unitType: string, unitId: string, base: s
|
|
|
211
228
|
try {
|
|
212
229
|
const roadmapContent = readFileSync(roadmapFile, "utf-8");
|
|
213
230
|
const roadmap = parseRoadmap(roadmapContent);
|
|
214
|
-
const slice = roadmap.slices.find(s => s.id === sid);
|
|
231
|
+
const slice = (roadmap.slices ?? []).find(s => s.id === sid);
|
|
215
232
|
if (slice && !slice.done) return false;
|
|
216
233
|
} catch {
|
|
217
234
|
// Corrupt/unparseable roadmap — fail verification so the unit
|
|
@@ -338,6 +355,10 @@ export function skipExecuteTask(
|
|
|
338
355
|
|
|
339
356
|
// ─── Disk-backed completed-unit helpers ───────────────────────────────────────
|
|
340
357
|
|
|
358
|
+
function isStringArray(data: unknown): data is string[] {
|
|
359
|
+
return Array.isArray(data) && data.every(item => typeof item === "string");
|
|
360
|
+
}
|
|
361
|
+
|
|
341
362
|
/** Path to the persisted completed-unit keys file. */
|
|
342
363
|
export function completedKeysPath(base: string): string {
|
|
343
364
|
return join(base, ".gsd", "completed-units.json");
|
|
@@ -346,12 +367,7 @@ export function completedKeysPath(base: string): string {
|
|
|
346
367
|
/** Write a completed unit key to disk (read-modify-write append to set). */
|
|
347
368
|
export function persistCompletedKey(base: string, key: string): void {
|
|
348
369
|
const file = completedKeysPath(base);
|
|
349
|
-
|
|
350
|
-
try {
|
|
351
|
-
if (existsSync(file)) {
|
|
352
|
-
keys = JSON.parse(readFileSync(file, "utf-8"));
|
|
353
|
-
}
|
|
354
|
-
} catch (e) { /* corrupt file — start fresh */ void e; }
|
|
370
|
+
const keys = loadJsonFileOrNull(file, isStringArray) ?? [];
|
|
355
371
|
const keySet = new Set(keys);
|
|
356
372
|
if (!keySet.has(key)) {
|
|
357
373
|
keys.push(key);
|
|
@@ -362,27 +378,21 @@ export function persistCompletedKey(base: string, key: string): void {
|
|
|
362
378
|
/** Remove a stale completed unit key from disk. */
|
|
363
379
|
export function removePersistedKey(base: string, key: string): void {
|
|
364
380
|
const file = completedKeysPath(base);
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
atomicWriteSync(file, JSON.stringify(filtered));
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
} catch (e) { /* non-fatal: removePersistedKey failure */ void e; }
|
|
381
|
+
const keys = loadJsonFileOrNull(file, isStringArray);
|
|
382
|
+
if (!keys) return;
|
|
383
|
+
const filtered = keys.filter(k => k !== key);
|
|
384
|
+
if (filtered.length !== keys.length) {
|
|
385
|
+
atomicWriteSync(file, JSON.stringify(filtered));
|
|
386
|
+
}
|
|
375
387
|
}
|
|
376
388
|
|
|
377
389
|
/** Load all completed unit keys from disk into the in-memory set. */
|
|
378
390
|
export function loadPersistedKeys(base: string, target: Set<string>): void {
|
|
379
391
|
const file = completedKeysPath(base);
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
} catch (e) { /* non-fatal: loadPersistedKeys failure */ void e; }
|
|
392
|
+
const keys = loadJsonFileOrNull(file, isStringArray);
|
|
393
|
+
if (keys) {
|
|
394
|
+
for (const k of keys) target.add(k);
|
|
395
|
+
}
|
|
386
396
|
}
|
|
387
397
|
|
|
388
398
|
// ─── Merge State Reconciliation ───────────────────────────────────────────────
|
|
@@ -26,6 +26,13 @@ import {
|
|
|
26
26
|
import { invalidateAllCaches } from "./cache.js";
|
|
27
27
|
import { synthesizeCrashRecovery } from "./session-forensics.js";
|
|
28
28
|
import { writeLock, clearLock, readCrashLock, formatCrashInfo, isLockProcessAlive } from "./crash-recovery.js";
|
|
29
|
+
import {
|
|
30
|
+
acquireSessionLock,
|
|
31
|
+
updateSessionLock,
|
|
32
|
+
releaseSessionLock,
|
|
33
|
+
readSessionLockData,
|
|
34
|
+
isSessionLockProcessAlive,
|
|
35
|
+
} from "./session-lock.js";
|
|
29
36
|
import { selfHealRuntimeRecords } from "./auto-recovery.js";
|
|
30
37
|
import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
|
31
38
|
import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit } from "./native-git-bridge.js";
|
|
@@ -81,6 +88,18 @@ export async function bootstrapAutoSession(
|
|
|
81
88
|
): Promise<boolean> {
|
|
82
89
|
const { shouldUseWorktreeIsolation, registerSigtermHandler, lockBase } = deps;
|
|
83
90
|
|
|
91
|
+
// ── Session lock: acquire FIRST, before any state mutation ──────────────
|
|
92
|
+
// This is the primary guard against concurrent sessions on the same project.
|
|
93
|
+
// Uses OS-level file locking (proper-lockfile) to prevent TOCTOU races.
|
|
94
|
+
const lockResult = acquireSessionLock(base);
|
|
95
|
+
if (!lockResult.acquired) {
|
|
96
|
+
ctx.ui.notify(
|
|
97
|
+
`${lockResult.reason}\nStop it with \`kill ${lockResult.existingPid ?? "the other process"}\` before starting a new session.`,
|
|
98
|
+
"error",
|
|
99
|
+
);
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
84
103
|
// Ensure git repo exists
|
|
85
104
|
if (!nativeIsRepo(base)) {
|
|
86
105
|
const mainBranch = loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
|
|
@@ -109,16 +128,11 @@ export async function bootstrapAutoSession(
|
|
|
109
128
|
// Initialize GitServiceImpl
|
|
110
129
|
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
111
130
|
|
|
112
|
-
// Check for crash from previous session
|
|
131
|
+
// Check for crash from previous session (use both old and new lock data)
|
|
113
132
|
const crashLock = readCrashLock(base);
|
|
114
133
|
if (crashLock) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
`Another auto-mode session (PID ${crashLock.pid}) appears to be running.\nStop it with \`kill ${crashLock.pid}\` before starting a new session.`,
|
|
118
|
-
"error",
|
|
119
|
-
);
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
134
|
+
// We already hold the session lock, so no concurrent session is running.
|
|
135
|
+
// The crash lock is from a dead process — recover context from it.
|
|
122
136
|
const recoveredMid = crashLock.unitId.split("/")[0];
|
|
123
137
|
const milestoneAlreadyComplete = recoveredMid
|
|
124
138
|
? !!resolveMilestoneFile(base, recoveredMid, "SUMMARY")
|
|
@@ -401,13 +415,14 @@ export async function bootstrapAutoSession(
|
|
|
401
415
|
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
402
416
|
ctx.ui.setFooter(hideFooter);
|
|
403
417
|
const modeLabel = s.stepMode ? "Step-mode" : "Auto-mode";
|
|
404
|
-
const pendingCount = state.registry.filter(m => m.status !== 'complete' && m.status !== 'parked').length;
|
|
418
|
+
const pendingCount = (state.registry ?? []).filter(m => m.status !== 'complete' && m.status !== 'parked').length;
|
|
405
419
|
const scopeMsg = pendingCount > 1
|
|
406
420
|
? `Will loop through ${pendingCount} milestones.`
|
|
407
421
|
: "Will loop until milestone complete.";
|
|
408
422
|
ctx.ui.notify(`${modeLabel} started. ${scopeMsg}`, "info");
|
|
409
423
|
|
|
410
|
-
//
|
|
424
|
+
// Update lock file with milestone info (OS lock already acquired at bootstrap start)
|
|
425
|
+
updateSessionLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown", 0);
|
|
411
426
|
writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown", 0);
|
|
412
427
|
|
|
413
428
|
// Secrets collection gate — pause instead of blocking (#1146)
|
|
@@ -105,19 +105,39 @@ export async function runPostUnitVerification(
|
|
|
105
105
|
const completionKey = `${s.currentUnit.type}/${s.currentUnit.id}`;
|
|
106
106
|
|
|
107
107
|
if (result.checks.length > 0) {
|
|
108
|
-
const
|
|
109
|
-
const
|
|
108
|
+
const blockingChecks = result.checks.filter(c => c.blocking);
|
|
109
|
+
const advisoryChecks = result.checks.filter(c => !c.blocking);
|
|
110
|
+
const blockingPassCount = blockingChecks.filter(c => c.exitCode === 0).length;
|
|
111
|
+
const advisoryFailCount = advisoryChecks.filter(c => c.exitCode !== 0).length;
|
|
112
|
+
|
|
110
113
|
if (result.passed) {
|
|
111
|
-
|
|
114
|
+
let msg = blockingChecks.length > 0
|
|
115
|
+
? `Verification gate: ${blockingPassCount}/${blockingChecks.length} blocking checks passed`
|
|
116
|
+
: `Verification gate: passed (no blocking checks)`;
|
|
117
|
+
if (advisoryFailCount > 0) {
|
|
118
|
+
msg += ` (${advisoryFailCount} advisory warning${advisoryFailCount > 1 ? "s" : ""})`;
|
|
119
|
+
}
|
|
120
|
+
ctx.ui.notify(msg);
|
|
121
|
+
// Log advisory warnings to stderr for visibility
|
|
122
|
+
if (advisoryFailCount > 0) {
|
|
123
|
+
const advisoryFailures = advisoryChecks.filter(c => c.exitCode !== 0);
|
|
124
|
+
process.stderr.write(`verification-gate: ${advisoryFailCount} advisory (non-blocking) failure(s)\n`);
|
|
125
|
+
for (const f of advisoryFailures) {
|
|
126
|
+
process.stderr.write(` [advisory] ${f.command} exited ${f.exitCode}\n`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
112
129
|
} else {
|
|
113
|
-
const
|
|
114
|
-
const failNames =
|
|
130
|
+
const blockingFailures = blockingChecks.filter(c => c.exitCode !== 0);
|
|
131
|
+
const failNames = blockingFailures.map(f => f.command).join(", ");
|
|
115
132
|
ctx.ui.notify(`Verification gate: FAILED — ${failNames}`);
|
|
116
|
-
process.stderr.write(`verification-gate: ${
|
|
117
|
-
for (const f of
|
|
133
|
+
process.stderr.write(`verification-gate: ${blockingFailures.length}/${blockingChecks.length} blocking checks failed\n`);
|
|
134
|
+
for (const f of blockingFailures) {
|
|
118
135
|
process.stderr.write(` ${f.command} exited ${f.exitCode}\n`);
|
|
119
136
|
if (f.stderr) process.stderr.write(` stderr: ${f.stderr.slice(0, 500)}\n`);
|
|
120
137
|
}
|
|
138
|
+
if (advisoryFailCount > 0) {
|
|
139
|
+
process.stderr.write(`verification-gate: ${advisoryFailCount} additional advisory (non-blocking) failure(s)\n`);
|
|
140
|
+
}
|
|
121
141
|
}
|
|
122
142
|
}
|
|
123
143
|
|
|
@@ -155,6 +175,20 @@ export async function runPostUnitVerification(
|
|
|
155
175
|
s.verificationRetryCount.delete(s.currentUnit.id);
|
|
156
176
|
s.pendingVerificationRetry = null;
|
|
157
177
|
return "continue";
|
|
178
|
+
} else if (result.discoverySource === "package-json") {
|
|
179
|
+
// Auto-discovered checks from package.json may fail on pre-existing errors
|
|
180
|
+
// that the current task didn't introduce. Don't trigger the retry loop —
|
|
181
|
+
// log a warning and let the task proceed (#1186).
|
|
182
|
+
process.stderr.write(
|
|
183
|
+
`verification-gate: auto-discovered checks failed (source: package-json) — treating as advisory, not blocking\n`,
|
|
184
|
+
);
|
|
185
|
+
ctx.ui.notify(
|
|
186
|
+
`Verification: auto-discovered checks failed (pre-existing errors likely). Continuing without retry.`,
|
|
187
|
+
"warning",
|
|
188
|
+
);
|
|
189
|
+
s.verificationRetryCount.delete(s.currentUnit.id);
|
|
190
|
+
s.pendingVerificationRetry = null;
|
|
191
|
+
return "continue";
|
|
158
192
|
} else if (autoFixEnabled && attempt + 1 <= maxRetries) {
|
|
159
193
|
const nextAttempt = attempt + 1;
|
|
160
194
|
s.verificationRetryCount.set(s.currentUnit.id, nextAttempt);
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { existsSync, mkdirSync, readFileSync, cpSync, unlinkSync, readdirSync } from "node:fs";
|
|
14
|
+
import { loadJsonFileOrNull } from "./json-persistence.js";
|
|
14
15
|
import { join, sep as pathSep } from "node:path";
|
|
15
16
|
import { homedir } from "node:os";
|
|
16
17
|
import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
|
|
@@ -36,6 +37,12 @@ export function syncProjectRootToWorktree(projectRoot: string, worktreePath: str
|
|
|
36
37
|
// has newer artifacts (e.g. slices that don't exist in the worktree yet)
|
|
37
38
|
safeCopyRecursive(join(prGsd, "milestones", milestoneId), join(wtGsd, "milestones", milestoneId))
|
|
38
39
|
|
|
40
|
+
// Copy living documents from project root to worktree so agents have the
|
|
41
|
+
// latest decisions, requirements, project state, and knowledge.
|
|
42
|
+
for (const doc of ["DECISIONS.md", "REQUIREMENTS.md", "PROJECT.md", "KNOWLEDGE.md"]) {
|
|
43
|
+
safeCopy(join(prGsd, doc), join(wtGsd, doc), { force: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
39
46
|
// Delete worktree gsd.db so it rebuilds from the freshly synced files.
|
|
40
47
|
// Stale DB rows are the root cause of the infinite skip loop (#853).
|
|
41
48
|
try {
|
|
@@ -89,6 +96,14 @@ export function syncStateToProjectRoot(worktreePath: string, projectRoot: string
|
|
|
89
96
|
// worktree. If the next session resolves basePath before worktree re-entry,
|
|
90
97
|
// selfHeal can't find or clear the stale record (#769).
|
|
91
98
|
safeCopyRecursive(join(wtGsd, "runtime", "units"), join(prGsd, "runtime", "units"), { force: true })
|
|
99
|
+
|
|
100
|
+
// 5. Living documents — decisions, requirements, project description, knowledge.
|
|
101
|
+
// Agents update these during slice execution. Without syncing, a new session
|
|
102
|
+
// reads stale copies from the project root, losing architectural decisions,
|
|
103
|
+
// requirement status updates, and accumulated knowledge (#1168).
|
|
104
|
+
for (const doc of ["DECISIONS.md", "REQUIREMENTS.md", "PROJECT.md", "KNOWLEDGE.md"]) {
|
|
105
|
+
safeCopy(join(wtGsd, doc), join(prGsd, doc), { force: true });
|
|
106
|
+
}
|
|
92
107
|
}
|
|
93
108
|
|
|
94
109
|
// ─── Resource Staleness ───────────────────────────────────────────────────
|
|
@@ -98,15 +113,15 @@ export function syncStateToProjectRoot(worktreePath: string, projectRoot: string
|
|
|
98
113
|
* Uses gsdVersion instead of syncedAt so that launching a second session
|
|
99
114
|
* doesn't falsely trigger staleness (#804).
|
|
100
115
|
*/
|
|
116
|
+
function isManifestWithVersion(data: unknown): data is { gsdVersion: string } {
|
|
117
|
+
return data !== null && typeof data === "object" && "gsdVersion" in data! && typeof (data as Record<string, unknown>).gsdVersion === "string";
|
|
118
|
+
}
|
|
119
|
+
|
|
101
120
|
export function readResourceVersion(): string | null {
|
|
102
121
|
const agentDir = process.env.GSD_CODING_AGENT_DIR || join(homedir(), ".gsd", "agent");
|
|
103
122
|
const manifestPath = join(agentDir, "managed-resources.json");
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return typeof manifest?.gsdVersion === "string" ? manifest.gsdVersion : null;
|
|
107
|
-
} catch {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
123
|
+
const manifest = loadJsonFileOrNull(manifestPath, isManifestWithVersion);
|
|
124
|
+
return manifest?.gsdVersion ?? null;
|
|
110
125
|
}
|
|
111
126
|
|
|
112
127
|
/**
|
|
@@ -22,7 +22,6 @@ import { loadFile, getManifestStatus, resolveAllOverrides, parsePlan, parseSumma
|
|
|
22
22
|
import { loadPrompt } from "./prompt-loader.js";
|
|
23
23
|
import { runVerificationGate, formatFailureContext, captureRuntimeErrors, runDependencyAudit } from "./verification-gate.js";
|
|
24
24
|
import { writeVerificationJSON } from "./verification-evidence.js";
|
|
25
|
-
export { inlinePriorMilestoneSummary } from "./files.js";
|
|
26
25
|
import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
|
|
27
26
|
import {
|
|
28
27
|
gsdRoot, resolveMilestoneFile, resolveSliceFile, resolveSlicePath,
|
|
@@ -33,6 +32,12 @@ import { invalidateAllCaches } from "./cache.js";
|
|
|
33
32
|
import { saveActivityLog, clearActivityLogState } from "./activity-log.js";
|
|
34
33
|
import { synthesizeCrashRecovery, getDeepDiagnostic } from "./session-forensics.js";
|
|
35
34
|
import { writeLock, clearLock, readCrashLock, formatCrashInfo, isLockProcessAlive } from "./crash-recovery.js";
|
|
35
|
+
import {
|
|
36
|
+
acquireSessionLock,
|
|
37
|
+
validateSessionLock,
|
|
38
|
+
releaseSessionLock,
|
|
39
|
+
updateSessionLock,
|
|
40
|
+
} from "./session-lock.js";
|
|
36
41
|
import {
|
|
37
42
|
clearUnitRuntimeRecord,
|
|
38
43
|
inspectExecuteTaskDurability,
|
|
@@ -186,12 +191,6 @@ import {
|
|
|
186
191
|
NEW_SESSION_TIMEOUT_MS, DISPATCH_HANG_TIMEOUT_MS,
|
|
187
192
|
} from "./auto/session.js";
|
|
188
193
|
import type { CompletedUnit, CurrentUnit, UnitRouting, StartModel, PendingVerificationRetry } from "./auto/session.js";
|
|
189
|
-
export {
|
|
190
|
-
MAX_UNIT_DISPATCHES, STUB_RECOVERY_THRESHOLD, MAX_LIFETIME_DISPATCHES,
|
|
191
|
-
MAX_CONSECUTIVE_SKIPS, DISPATCH_GAP_TIMEOUT_MS, MAX_SKIP_DEPTH,
|
|
192
|
-
NEW_SESSION_TIMEOUT_MS, DISPATCH_HANG_TIMEOUT_MS,
|
|
193
|
-
} from "./auto/session.js";
|
|
194
|
-
export type { CompletedUnit, CurrentUnit, UnitRouting, StartModel } from "./auto/session.js";
|
|
195
194
|
|
|
196
195
|
// ── ENCAPSULATION INVARIANT ─────────────────────────────────────────────────
|
|
197
196
|
// ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts).
|
|
@@ -262,8 +261,6 @@ export function shouldUseWorktreeIsolation(): boolean {
|
|
|
262
261
|
* Maps toolCallId → start timestamp (ms) so the idle watchdog can detect tools that have been
|
|
263
262
|
* running suspiciously long (e.g., a Bash command hung because `&` kept stdout open).
|
|
264
263
|
*/
|
|
265
|
-
// Re-export budget utilities for external consumers
|
|
266
|
-
export { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction } from "./auto-budget.js";
|
|
267
264
|
|
|
268
265
|
/** Wrapper: register SIGTERM handler and store reference. */
|
|
269
266
|
function registerSigtermHandler(currentBasePath: string): void {
|
|
@@ -276,8 +273,6 @@ function deregisterSigtermHandler(): void {
|
|
|
276
273
|
s.sigtermHandler = null;
|
|
277
274
|
}
|
|
278
275
|
|
|
279
|
-
export { type AutoDashboardData } from "./auto-dashboard.js";
|
|
280
|
-
|
|
281
276
|
export function getAutoDashboardData(): AutoDashboardData {
|
|
282
277
|
const ledger = getLedger();
|
|
283
278
|
const totals = ledger ? getProjectTotals(ledger.units) : null;
|
|
@@ -451,7 +446,10 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
451
446
|
if (!s.active && !s.paused) return;
|
|
452
447
|
const reasonSuffix = reason ? ` — ${reason}` : "";
|
|
453
448
|
clearUnitTimeout();
|
|
454
|
-
if (lockBase())
|
|
449
|
+
if (lockBase()) {
|
|
450
|
+
releaseSessionLock(lockBase());
|
|
451
|
+
clearLock(lockBase());
|
|
452
|
+
}
|
|
455
453
|
clearSkillSnapshot();
|
|
456
454
|
resetSkillTelemetry();
|
|
457
455
|
s.dispatching = false;
|
|
@@ -565,7 +563,10 @@ export async function pauseAuto(ctx?: ExtensionContext, _pi?: ExtensionAPI): Pro
|
|
|
565
563
|
|
|
566
564
|
s.pausedSessionFile = ctx?.sessionManager?.getSessionFile() ?? null;
|
|
567
565
|
|
|
568
|
-
if (lockBase())
|
|
566
|
+
if (lockBase()) {
|
|
567
|
+
releaseSessionLock(lockBase());
|
|
568
|
+
clearLock(lockBase());
|
|
569
|
+
}
|
|
569
570
|
|
|
570
571
|
deregisterSigtermHandler();
|
|
571
572
|
|
|
@@ -598,6 +599,16 @@ export async function startAuto(
|
|
|
598
599
|
|
|
599
600
|
// If resuming from paused state, just re-activate and dispatch next unit.
|
|
600
601
|
if (s.paused) {
|
|
602
|
+
// Re-acquire session lock before resuming
|
|
603
|
+
const resumeLock = acquireSessionLock(base);
|
|
604
|
+
if (!resumeLock.acquired) {
|
|
605
|
+
ctx.ui.notify(
|
|
606
|
+
`Cannot resume: ${resumeLock.reason}`,
|
|
607
|
+
"error",
|
|
608
|
+
);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
|
|
601
612
|
s.paused = false;
|
|
602
613
|
s.active = true;
|
|
603
614
|
s.verbose = verboseMode;
|
|
@@ -699,6 +710,7 @@ export async function startAuto(
|
|
|
699
710
|
s.pausedForSecrets = false;
|
|
700
711
|
}
|
|
701
712
|
|
|
713
|
+
updateSessionLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
|
|
702
714
|
writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
|
|
703
715
|
|
|
704
716
|
await dispatchNextUnit(ctx, pi);
|
|
@@ -854,7 +866,7 @@ async function showStepWizard(
|
|
|
854
866
|
: "previous unit";
|
|
855
867
|
|
|
856
868
|
if (!mid || state.phase === "complete") {
|
|
857
|
-
const incomplete = state.registry.filter(m => m.status !== "complete" && m.status !== "parked");
|
|
869
|
+
const incomplete = (state.registry ?? []).filter(m => m.status !== "complete" && m.status !== "parked");
|
|
858
870
|
if (incomplete.length > 0 && state.phase !== "complete" && state.phase !== "blocked" && state.phase !== "pre-planning") {
|
|
859
871
|
const ids = incomplete.map(m => m.id).join(", ");
|
|
860
872
|
const diag = `basePath=${s.basePath}, milestones=[${state.registry.map(m => `${m.id}:${m.status}`).join(", ")}], phase=${state.phase}`;
|
|
@@ -911,8 +923,6 @@ async function showStepWizard(
|
|
|
911
923
|
}
|
|
912
924
|
}
|
|
913
925
|
|
|
914
|
-
// describeNextUnit is imported from auto-dashboard.ts and re-exported
|
|
915
|
-
export { describeNextUnit } from "./auto-dashboard.js";
|
|
916
926
|
|
|
917
927
|
/** Thin wrapper: delegates to auto-dashboard.ts, passing state accessors. */
|
|
918
928
|
function updateProgressWidget(
|
|
@@ -950,6 +960,24 @@ async function dispatchNextUnit(
|
|
|
950
960
|
return;
|
|
951
961
|
}
|
|
952
962
|
|
|
963
|
+
// ── Session lock validation: detect if another process has taken over ──
|
|
964
|
+
if (lockBase() && !validateSessionLock(lockBase())) {
|
|
965
|
+
debugLog("dispatchNextUnit session-lock-lost — another process may have taken over");
|
|
966
|
+
ctx.ui.notify(
|
|
967
|
+
"Session lock lost — another GSD process appears to have taken over. Stopping gracefully.",
|
|
968
|
+
"error",
|
|
969
|
+
);
|
|
970
|
+
// Don't call stopAuto here to avoid releasing the lock we don't own
|
|
971
|
+
s.active = false;
|
|
972
|
+
s.paused = false;
|
|
973
|
+
clearUnitTimeout();
|
|
974
|
+
deregisterSigtermHandler();
|
|
975
|
+
ctx.ui.setStatus("gsd-auto", undefined);
|
|
976
|
+
ctx.ui.setWidget("gsd-progress", undefined);
|
|
977
|
+
ctx.ui.setFooter(undefined);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
|
|
953
981
|
// Reentrancy guard
|
|
954
982
|
if (s.dispatching && s.skipDepth === 0) {
|
|
955
983
|
debugLog("dispatchNextUnit reentrancy guard — another dispatch in progress, bailing");
|
|
@@ -1130,7 +1158,7 @@ async function dispatchNextUnit(
|
|
|
1130
1158
|
}
|
|
1131
1159
|
}
|
|
1132
1160
|
|
|
1133
|
-
const pendingIds = state.registry
|
|
1161
|
+
const pendingIds = (state.registry ?? [])
|
|
1134
1162
|
.filter(m => m.status !== "complete")
|
|
1135
1163
|
.map(m => m.id);
|
|
1136
1164
|
pruneQueueOrder(s.basePath, pendingIds);
|
|
@@ -1145,7 +1173,7 @@ async function dispatchNextUnit(
|
|
|
1145
1173
|
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
1146
1174
|
}
|
|
1147
1175
|
|
|
1148
|
-
const incomplete = state.registry.filter(m => m.status !== "complete" && m.status !== "parked");
|
|
1176
|
+
const incomplete = (state.registry ?? []).filter(m => m.status !== "complete" && m.status !== "parked");
|
|
1149
1177
|
if (incomplete.length === 0) {
|
|
1150
1178
|
// Genuinely all complete (parked milestones excluded) — merge milestone branch to main before stopping (#962)
|
|
1151
1179
|
if (s.currentMilestoneId && isInAutoWorktree(s.basePath) && s.originalBasePath) {
|
|
@@ -1171,7 +1199,7 @@ async function dispatchNextUnit(
|
|
|
1171
1199
|
try { process.chdir(s.basePath); } catch { /* best-effort */ }
|
|
1172
1200
|
}
|
|
1173
1201
|
}
|
|
1174
|
-
} else if (s.currentMilestoneId && !isInAutoWorktree(s.basePath) && getIsolationMode()
|
|
1202
|
+
} else if (s.currentMilestoneId && !isInAutoWorktree(s.basePath) && getIsolationMode() === "branch") {
|
|
1175
1203
|
try {
|
|
1176
1204
|
const currentBranch = getCurrentBranch(s.basePath);
|
|
1177
1205
|
const milestoneBranch = autoWorktreeBranch(s.currentMilestoneId);
|
|
@@ -1273,7 +1301,7 @@ async function dispatchNextUnit(
|
|
|
1273
1301
|
try { process.chdir(s.basePath); } catch { /* best-effort */ }
|
|
1274
1302
|
}
|
|
1275
1303
|
}
|
|
1276
|
-
} else if (s.currentMilestoneId && !isInAutoWorktree(s.basePath) && getIsolationMode()
|
|
1304
|
+
} else if (s.currentMilestoneId && !isInAutoWorktree(s.basePath) && getIsolationMode() === "branch") {
|
|
1277
1305
|
try {
|
|
1278
1306
|
const currentBranch = getCurrentBranch(s.basePath);
|
|
1279
1307
|
const milestoneBranch = autoWorktreeBranch(s.currentMilestoneId);
|
|
@@ -1398,6 +1426,23 @@ async function dispatchNextUnit(
|
|
|
1398
1426
|
|
|
1399
1427
|
await runSecretsGate();
|
|
1400
1428
|
|
|
1429
|
+
// ── Interactive discussion gate ──
|
|
1430
|
+
// If the active milestone needs discussion (has CONTEXT-DRAFT.md but no roadmap),
|
|
1431
|
+
// stop auto-mode and route to the interactive discussion flow. The guided-flow
|
|
1432
|
+
// handles needs-discussion correctly — it just needs to be called instead of
|
|
1433
|
+
// letting the dispatch table fire "needs-discussion → stop" (#1170).
|
|
1434
|
+
if (state.phase === "needs-discussion") {
|
|
1435
|
+
if (s.currentUnit) {
|
|
1436
|
+
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
1437
|
+
}
|
|
1438
|
+
const cmdCtx = s.cmdCtx!;
|
|
1439
|
+
const basePath = s.basePath;
|
|
1440
|
+
await stopAuto(ctx, pi, `${mid}: ${midTitle} needs discussion before planning.`);
|
|
1441
|
+
const { showSmartEntry } = await import("./guided-flow.js");
|
|
1442
|
+
await showSmartEntry(cmdCtx, pi, basePath);
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1401
1446
|
// ── Dispatch table ──
|
|
1402
1447
|
const dispatchResult = await resolveDispatch({ basePath: s.basePath, mid, midTitle: midTitle!, state, prefs,
|
|
1403
1448
|
});
|
|
@@ -1583,6 +1628,7 @@ async function dispatchNextUnit(
|
|
|
1583
1628
|
}
|
|
1584
1629
|
|
|
1585
1630
|
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
1631
|
+
updateSessionLock(lockBase(), unitType, unitId, s.completedUnits.length, sessionFile);
|
|
1586
1632
|
writeLock(lockBase(), unitType, unitId, s.completedUnits.length, sessionFile);
|
|
1587
1633
|
|
|
1588
1634
|
// Prompt injection
|
|
@@ -1809,6 +1855,7 @@ export async function dispatchHookUnit(
|
|
|
1809
1855
|
}
|
|
1810
1856
|
|
|
1811
1857
|
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
1858
|
+
updateSessionLock(lockBase(), hookUnitType, triggerUnitId, s.completedUnits.length, sessionFile);
|
|
1812
1859
|
writeLock(lockBase(), hookUnitType, triggerUnitId, s.completedUnits.length, sessionFile);
|
|
1813
1860
|
|
|
1814
1861
|
clearUnitTimeout();
|
|
@@ -1845,5 +1892,3 @@ export async function dispatchHookUnit(
|
|
|
1845
1892
|
}
|
|
1846
1893
|
|
|
1847
1894
|
|
|
1848
|
-
// Direct phase dispatch → auto-direct-dispatch.ts
|
|
1849
|
-
export { dispatchDirectPhase } from "./auto-direct-dispatch.js";
|
|
@@ -18,19 +18,11 @@ import {
|
|
|
18
18
|
selectDoctorScope,
|
|
19
19
|
filterDoctorIssues,
|
|
20
20
|
} from "./doctor.js";
|
|
21
|
-
import { loadPrompt } from "./prompt-loader.js";
|
|
22
21
|
import { isAutoActive } from "./auto.js";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
/** Resolve the effective project root, accounting for worktree paths. */
|
|
27
|
-
function projectRoot(): string {
|
|
28
|
-
const root = resolveProjectRoot(process.cwd());
|
|
29
|
-
assertSafeDirectory(root);
|
|
30
|
-
return root;
|
|
31
|
-
}
|
|
22
|
+
import { projectRoot } from "./commands.js";
|
|
23
|
+
import { loadPrompt } from "./prompt-loader.js";
|
|
32
24
|
|
|
33
|
-
function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined, reportText: string, structuredIssues: string): void {
|
|
25
|
+
export function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined, reportText: string, structuredIssues: string): void {
|
|
34
26
|
const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".pi", "GSD-WORKFLOW.md");
|
|
35
27
|
const workflow = readFileSync(workflowPath, "utf-8");
|
|
36
28
|
const prompt = loadPrompt("doctor-heal", {
|