gsd-pi 2.33.1-dev.ee47f1b → 2.34.0-dev.bbb5216
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/dist/bundled-resource-path.d.ts +8 -0
- package/dist/bundled-resource-path.js +14 -0
- package/dist/headless-query.js +6 -6
- package/dist/resources/extensions/gsd/auto/session.js +27 -32
- package/dist/resources/extensions/gsd/auto-dashboard.js +29 -109
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +6 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +52 -81
- package/dist/resources/extensions/gsd/auto-loop.js +956 -0
- package/dist/resources/extensions/gsd/auto-observability.js +4 -2
- package/dist/resources/extensions/gsd/auto-post-unit.js +75 -185
- package/dist/resources/extensions/gsd/auto-prompts.js +133 -101
- package/dist/resources/extensions/gsd/auto-recovery.js +59 -97
- package/dist/resources/extensions/gsd/auto-start.js +330 -309
- package/dist/resources/extensions/gsd/auto-supervisor.js +5 -11
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +7 -7
- package/dist/resources/extensions/gsd/auto-timers.js +3 -4
- package/dist/resources/extensions/gsd/auto-verification.js +35 -73
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +167 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +291 -126
- package/dist/resources/extensions/gsd/auto.js +283 -1013
- package/dist/resources/extensions/gsd/captures.js +10 -4
- package/dist/resources/extensions/gsd/dispatch-guard.js +7 -8
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -18
- package/dist/resources/extensions/gsd/doctor-checks.js +3 -4
- package/dist/resources/extensions/gsd/git-service.js +1 -1
- package/dist/resources/extensions/gsd/gsd-db.js +296 -151
- package/dist/resources/extensions/gsd/index.js +92 -228
- package/dist/resources/extensions/gsd/post-unit-hooks.js +13 -13
- package/dist/resources/extensions/gsd/progress-score.js +61 -156
- package/dist/resources/extensions/gsd/quick.js +98 -122
- package/dist/resources/extensions/gsd/session-lock.js +13 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
- package/dist/resources/extensions/gsd/undo.js +43 -48
- package/dist/resources/extensions/gsd/unit-runtime.js +16 -15
- package/dist/resources/extensions/gsd/verification-evidence.js +0 -1
- package/dist/resources/extensions/gsd/verification-gate.js +6 -35
- package/dist/resources/extensions/gsd/worktree-command.js +30 -24
- package/dist/resources/extensions/gsd/worktree-manager.js +2 -3
- package/dist/resources/extensions/gsd/worktree-resolver.js +344 -0
- package/dist/resources/extensions/gsd/worktree.js +7 -44
- package/dist/tool-bootstrap.js +59 -11
- package/dist/worktree-cli.js +7 -7
- package/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +3630 -5483
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +735 -2588
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/src/models.generated.ts +1039 -2892
- package/packages/pi-coding-agent/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/session.ts +47 -30
- package/src/resources/extensions/gsd/auto-dashboard.ts +28 -131
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +6 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +135 -91
- package/src/resources/extensions/gsd/auto-loop.ts +1665 -0
- package/src/resources/extensions/gsd/auto-observability.ts +4 -2
- package/src/resources/extensions/gsd/auto-post-unit.ts +85 -228
- package/src/resources/extensions/gsd/auto-prompts.ts +138 -109
- package/src/resources/extensions/gsd/auto-recovery.ts +124 -118
- package/src/resources/extensions/gsd/auto-start.ts +440 -354
- package/src/resources/extensions/gsd/auto-supervisor.ts +5 -12
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +8 -8
- package/src/resources/extensions/gsd/auto-timers.ts +3 -4
- package/src/resources/extensions/gsd/auto-verification.ts +76 -90
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +204 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +389 -141
- package/src/resources/extensions/gsd/auto.ts +515 -1199
- package/src/resources/extensions/gsd/captures.ts +10 -4
- package/src/resources/extensions/gsd/dispatch-guard.ts +13 -9
- package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -18
- package/src/resources/extensions/gsd/doctor-checks.ts +3 -4
- package/src/resources/extensions/gsd/git-service.ts +8 -1
- package/src/resources/extensions/gsd/gitignore.ts +4 -2
- package/src/resources/extensions/gsd/gsd-db.ts +375 -180
- package/src/resources/extensions/gsd/index.ts +104 -263
- package/src/resources/extensions/gsd/post-unit-hooks.ts +13 -13
- package/src/resources/extensions/gsd/progress-score.ts +65 -200
- package/src/resources/extensions/gsd/quick.ts +121 -125
- package/src/resources/extensions/gsd/session-lock.ts +11 -0
- package/src/resources/extensions/gsd/templates/preferences.md +1 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +32 -59
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +75 -27
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1458 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +8 -162
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -108
- package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +1 -3
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -55
- package/src/resources/extensions/gsd/tests/headless-query.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +8 -11
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +4 -6
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/undo.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +24 -26
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +7 -201
- 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-e2e.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +705 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +57 -106
- package/src/resources/extensions/gsd/tests/worktree.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +43 -132
- package/src/resources/extensions/gsd/types.ts +90 -81
- package/src/resources/extensions/gsd/undo.ts +42 -46
- package/src/resources/extensions/gsd/unit-runtime.ts +14 -18
- package/src/resources/extensions/gsd/verification-evidence.ts +1 -3
- package/src/resources/extensions/gsd/verification-gate.ts +6 -39
- package/src/resources/extensions/gsd/worktree-command.ts +36 -24
- package/src/resources/extensions/gsd/worktree-manager.ts +2 -3
- package/src/resources/extensions/gsd/worktree-resolver.ts +485 -0
- package/src/resources/extensions/gsd/worktree.ts +7 -44
- package/dist/resources/extensions/gsd/auto-constants.js +0 -5
- package/dist/resources/extensions/gsd/auto-idempotency.js +0 -106
- package/dist/resources/extensions/gsd/auto-stuck-detection.js +0 -165
- package/dist/resources/extensions/gsd/mechanical-completion.js +0 -351
- package/src/resources/extensions/gsd/auto-constants.ts +0 -6
- package/src/resources/extensions/gsd/auto-idempotency.ts +0 -151
- package/src/resources/extensions/gsd/auto-stuck-detection.ts +0 -221
- package/src/resources/extensions/gsd/mechanical-completion.ts +0 -430
- package/src/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +0 -691
- package/src/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +0 -127
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +0 -123
- package/src/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +0 -126
- package/src/resources/extensions/gsd/tests/loop-regression.test.ts +0 -874
- package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +0 -356
- package/src/resources/extensions/gsd/tests/progress-score.test.ts +0 -206
- package/src/resources/extensions/gsd/tests/session-lock.test.ts +0 -434
|
@@ -15,61 +15,73 @@ import type {
|
|
|
15
15
|
} from "@gsd/pi-coding-agent";
|
|
16
16
|
import { deriveState } from "./state.js";
|
|
17
17
|
import { loadFile, getManifestStatus } from "./files.js";
|
|
18
|
-
import { loadEffectiveGSDPreferences, resolveSkillDiscoveryMode, getIsolationMode } from "./preferences.js";
|
|
19
|
-
import { isInsideWorktree, ensureGsdSymlink } from "./repo-identity.js";
|
|
20
|
-
import { migrateToExternalState, recoverFailedMigration } from "./migrate-external.js";
|
|
21
|
-
import { sendDesktopNotification } from "./notifications.js";
|
|
22
|
-
import { sendRemoteNotification } from "../remote-questions/notify.js";
|
|
23
18
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
} from "./
|
|
19
|
+
loadEffectiveGSDPreferences,
|
|
20
|
+
resolveSkillDiscoveryMode,
|
|
21
|
+
getIsolationMode,
|
|
22
|
+
} from "./preferences.js";
|
|
23
|
+
import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
|
|
24
|
+
import { gsdRoot, resolveMilestoneFile, milestonesDir } from "./paths.js";
|
|
28
25
|
import { invalidateAllCaches } from "./cache.js";
|
|
29
26
|
import { synthesizeCrashRecovery } from "./session-forensics.js";
|
|
30
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
writeLock,
|
|
29
|
+
clearLock,
|
|
30
|
+
readCrashLock,
|
|
31
|
+
formatCrashInfo,
|
|
32
|
+
isLockProcessAlive,
|
|
33
|
+
} from "./crash-recovery.js";
|
|
31
34
|
import {
|
|
32
35
|
acquireSessionLock,
|
|
33
|
-
updateSessionLock,
|
|
34
36
|
releaseSessionLock,
|
|
35
|
-
|
|
36
|
-
isSessionLockProcessAlive,
|
|
37
|
+
updateSessionLock,
|
|
37
38
|
} from "./session-lock.js";
|
|
38
|
-
import { selfHealRuntimeRecords } from "./auto-recovery.js";
|
|
39
39
|
import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
|
40
|
-
import {
|
|
41
|
-
|
|
40
|
+
import {
|
|
41
|
+
nativeIsRepo,
|
|
42
|
+
nativeInit,
|
|
43
|
+
nativeAddAll,
|
|
44
|
+
nativeCommit,
|
|
45
|
+
} from "./native-git-bridge.js";
|
|
46
|
+
import { GitServiceImpl } from "./git-service.js";
|
|
42
47
|
import {
|
|
43
48
|
captureIntegrationBranch,
|
|
44
49
|
detectWorktreeName,
|
|
45
50
|
setActiveMilestoneId,
|
|
46
51
|
} from "./worktree.js";
|
|
47
|
-
import {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
getAutoWorktreePath,
|
|
51
|
-
isInAutoWorktree,
|
|
52
|
-
} from "./auto-worktree.js";
|
|
53
|
-
import { readResourceVersion } from "./resource-version.js";
|
|
54
|
-
import { initMetrics, getLedger } from "./metrics.js";
|
|
52
|
+
import { getAutoWorktreePath, isInAutoWorktree } from "./auto-worktree.js";
|
|
53
|
+
import { readResourceVersion } from "./auto-worktree-sync.js";
|
|
54
|
+
import { initMetrics } from "./metrics.js";
|
|
55
55
|
import { initRoutingHistory } from "./routing-history.js";
|
|
56
|
-
import { restoreHookState, resetHookState
|
|
56
|
+
import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
|
|
57
57
|
import { resetProactiveHealing } from "./doctor-proactive.js";
|
|
58
58
|
import { snapshotSkills } from "./skill-discovery.js";
|
|
59
59
|
import { isDbAvailable } from "./gsd-db.js";
|
|
60
|
-
import { loadPersistedKeys } from "./auto-recovery.js";
|
|
61
60
|
import { hideFooter } from "./auto-dashboard.js";
|
|
62
|
-
import {
|
|
61
|
+
import {
|
|
62
|
+
debugLog,
|
|
63
|
+
enableDebug,
|
|
64
|
+
isDebugEnabled,
|
|
65
|
+
getDebugLogPath,
|
|
66
|
+
} from "./debug-logger.js";
|
|
63
67
|
import type { AutoSession } from "./auto/session.js";
|
|
64
|
-
import {
|
|
68
|
+
import {
|
|
69
|
+
existsSync,
|
|
70
|
+
mkdirSync,
|
|
71
|
+
readdirSync,
|
|
72
|
+
statSync,
|
|
73
|
+
unlinkSync,
|
|
74
|
+
} from "node:fs";
|
|
65
75
|
import { join } from "node:path";
|
|
66
|
-
import {
|
|
67
|
-
|
|
76
|
+
import { sep as pathSep } from "node:path";
|
|
77
|
+
|
|
78
|
+
import type { WorktreeResolver } from "./worktree-resolver.js";
|
|
68
79
|
|
|
69
80
|
export interface BootstrapDeps {
|
|
70
81
|
shouldUseWorktreeIsolation: () => boolean;
|
|
71
82
|
registerSigtermHandler: (basePath: string) => void;
|
|
72
83
|
lockBase: () => string;
|
|
84
|
+
buildResolver: () => WorktreeResolver;
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
/**
|
|
@@ -89,395 +101,469 @@ export async function bootstrapAutoSession(
|
|
|
89
101
|
requestedStepMode: boolean,
|
|
90
102
|
deps: BootstrapDeps,
|
|
91
103
|
): Promise<boolean> {
|
|
92
|
-
const {
|
|
104
|
+
const {
|
|
105
|
+
shouldUseWorktreeIsolation,
|
|
106
|
+
registerSigtermHandler,
|
|
107
|
+
lockBase,
|
|
108
|
+
buildResolver,
|
|
109
|
+
} = deps;
|
|
93
110
|
|
|
94
|
-
// ── Session lock: acquire FIRST, before any state mutation ──────────────
|
|
95
|
-
// This is the primary guard against concurrent sessions on the same project.
|
|
96
|
-
// Uses OS-level file locking (proper-lockfile) to prevent TOCTOU races.
|
|
97
111
|
const lockResult = acquireSessionLock(base);
|
|
98
112
|
if (!lockResult.acquired) {
|
|
99
|
-
ctx.ui.notify(
|
|
100
|
-
`${lockResult.reason}\nStop it with \`kill ${lockResult.existingPid ?? "the other process"}\` before starting a new session.`,
|
|
101
|
-
"error",
|
|
102
|
-
);
|
|
113
|
+
ctx.ui.notify(lockResult.reason, "error");
|
|
103
114
|
return false;
|
|
104
115
|
}
|
|
105
116
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
117
|
+
function releaseLockAndReturn(): false {
|
|
118
|
+
releaseSessionLock(base);
|
|
119
|
+
clearLock(base);
|
|
120
|
+
return false;
|
|
110
121
|
}
|
|
111
122
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
recoverFailedMigration(base);
|
|
120
|
-
const migration = migrateToExternalState(base);
|
|
121
|
-
if (migration.error) {
|
|
122
|
-
ctx.ui.notify(`External state migration warning: ${migration.error}`, "warning");
|
|
123
|
-
}
|
|
124
|
-
// Ensure symlink exists (handles fresh projects and post-migration)
|
|
125
|
-
ensureGsdSymlink(base);
|
|
123
|
+
try {
|
|
124
|
+
// Ensure git repo exists
|
|
125
|
+
if (!nativeIsRepo(base)) {
|
|
126
|
+
const mainBranch =
|
|
127
|
+
loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
|
|
128
|
+
nativeInit(base, mainBranch);
|
|
129
|
+
}
|
|
126
130
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
// Ensure .gitignore has baseline patterns
|
|
132
|
+
const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
133
|
+
const commitDocs = gitPrefs?.commit_docs;
|
|
134
|
+
const manageGitignore = gitPrefs?.manage_gitignore;
|
|
135
|
+
ensureGitignore(base, { commitDocs, manageGitignore });
|
|
136
|
+
if (manageGitignore !== false) untrackRuntimeFiles(base);
|
|
137
|
+
|
|
138
|
+
// Bootstrap .gsd/ if it doesn't exist
|
|
139
|
+
const gsdDir = join(base, ".gsd");
|
|
140
|
+
if (!existsSync(gsdDir)) {
|
|
141
|
+
mkdirSync(join(gsdDir, "milestones"), { recursive: true });
|
|
142
|
+
if (commitDocs !== false) {
|
|
143
|
+
try {
|
|
144
|
+
nativeAddAll(base);
|
|
145
|
+
nativeCommit(base, "chore: init gsd");
|
|
146
|
+
} catch {
|
|
147
|
+
/* nothing to commit */
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
132
151
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const recoveredMid = parseUnitId(crashLock.unitId).milestone;
|
|
144
|
-
const milestoneAlreadyComplete = recoveredMid
|
|
145
|
-
? !!resolveMilestoneFile(base, recoveredMid, "SUMMARY")
|
|
146
|
-
: false;
|
|
147
|
-
|
|
148
|
-
if (milestoneAlreadyComplete) {
|
|
149
|
-
ctx.ui.notify(
|
|
150
|
-
`Crash recovery: discarding stale context for ${crashLock.unitId} — milestone ${recoveredMid} is already complete.`,
|
|
151
|
-
"info",
|
|
152
|
-
);
|
|
153
|
-
} else {
|
|
154
|
-
const activityDir = join(gsdRoot(base), "activity");
|
|
155
|
-
const recovery = synthesizeCrashRecovery(
|
|
156
|
-
base, crashLock.unitType, crashLock.unitId,
|
|
157
|
-
crashLock.sessionFile, activityDir,
|
|
158
|
-
);
|
|
159
|
-
if (recovery && recovery.trace.toolCallCount > 0) {
|
|
160
|
-
s.pendingCrashRecovery = recovery.prompt;
|
|
152
|
+
// Initialize GitServiceImpl
|
|
153
|
+
s.gitService = new GitServiceImpl(
|
|
154
|
+
s.basePath,
|
|
155
|
+
loadEffectiveGSDPreferences()?.preferences?.git ?? {},
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Check for crash from previous session. Skip our own fresh bootstrap lock.
|
|
159
|
+
const crashLock = readCrashLock(base);
|
|
160
|
+
if (crashLock && crashLock.pid !== process.pid) {
|
|
161
|
+
if (isLockProcessAlive(crashLock)) {
|
|
161
162
|
ctx.ui.notify(
|
|
162
|
-
|
|
163
|
-
"
|
|
163
|
+
`Another auto-mode session (PID ${crashLock.pid}) appears to be running.\nStop it with \`kill ${crashLock.pid}\` before starting a new session.`,
|
|
164
|
+
"error",
|
|
164
165
|
);
|
|
165
|
-
|
|
166
|
+
return releaseLockAndReturn();
|
|
167
|
+
}
|
|
168
|
+
const recoveredMid = crashLock.unitId.split("/")[0];
|
|
169
|
+
const milestoneAlreadyComplete = recoveredMid
|
|
170
|
+
? !!resolveMilestoneFile(base, recoveredMid, "SUMMARY")
|
|
171
|
+
: false;
|
|
172
|
+
|
|
173
|
+
if (milestoneAlreadyComplete) {
|
|
166
174
|
ctx.ui.notify(
|
|
167
|
-
|
|
168
|
-
"
|
|
175
|
+
`Crash recovery: discarding stale context for ${crashLock.unitId} — milestone ${recoveredMid} is already complete.`,
|
|
176
|
+
"info",
|
|
177
|
+
);
|
|
178
|
+
} else {
|
|
179
|
+
const activityDir = join(gsdRoot(base), "activity");
|
|
180
|
+
const recovery = synthesizeCrashRecovery(
|
|
181
|
+
base,
|
|
182
|
+
crashLock.unitType,
|
|
183
|
+
crashLock.unitId,
|
|
184
|
+
crashLock.sessionFile,
|
|
185
|
+
activityDir,
|
|
169
186
|
);
|
|
187
|
+
if (recovery && recovery.trace.toolCallCount > 0) {
|
|
188
|
+
s.pendingCrashRecovery = recovery.prompt;
|
|
189
|
+
ctx.ui.notify(
|
|
190
|
+
`${formatCrashInfo(crashLock)}\nRecovered ${recovery.trace.toolCallCount} tool calls from crashed session. Resuming with full context.`,
|
|
191
|
+
"warning",
|
|
192
|
+
);
|
|
193
|
+
} else {
|
|
194
|
+
ctx.ui.notify(
|
|
195
|
+
`${formatCrashInfo(crashLock)}\nNo session data recovered. Resuming from disk state.`,
|
|
196
|
+
"warning",
|
|
197
|
+
);
|
|
198
|
+
}
|
|
170
199
|
}
|
|
200
|
+
clearLock(base);
|
|
171
201
|
}
|
|
172
|
-
clearLock(base);
|
|
173
|
-
}
|
|
174
202
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
203
|
+
// ── Debug mode ──
|
|
204
|
+
if (!isDebugEnabled() && process.env.GSD_DEBUG === "1") {
|
|
205
|
+
enableDebug(base);
|
|
206
|
+
}
|
|
207
|
+
if (isDebugEnabled()) {
|
|
208
|
+
const { isNativeParserAvailable } =
|
|
209
|
+
await import("./native-parser-bridge.js");
|
|
210
|
+
debugLog("debug-start", {
|
|
211
|
+
platform: process.platform,
|
|
212
|
+
arch: process.arch,
|
|
213
|
+
node: process.version,
|
|
214
|
+
model: ctx.model?.id ?? "unknown",
|
|
215
|
+
provider: ctx.model?.provider ?? "unknown",
|
|
216
|
+
nativeParser: isNativeParserAvailable(),
|
|
217
|
+
cwd: base,
|
|
218
|
+
});
|
|
219
|
+
ctx.ui.notify(`Debug logging enabled → ${getDebugLogPath()}`, "info");
|
|
220
|
+
}
|
|
192
221
|
|
|
193
|
-
|
|
194
|
-
|
|
222
|
+
// Invalidate caches before initial state derivation
|
|
223
|
+
invalidateAllCaches();
|
|
195
224
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
225
|
+
// Clean stale runtime unit files for completed milestones (#887)
|
|
226
|
+
try {
|
|
227
|
+
const runtimeUnitsDir = join(gsdRoot(base), "runtime", "units");
|
|
228
|
+
if (existsSync(runtimeUnitsDir)) {
|
|
229
|
+
for (const file of readdirSync(runtimeUnitsDir)) {
|
|
230
|
+
if (!file.endsWith(".json")) continue;
|
|
231
|
+
const midMatch = file.match(/(M\d+(?:-[a-z0-9]{6})?)/);
|
|
232
|
+
if (!midMatch) continue;
|
|
233
|
+
const mid = midMatch[1];
|
|
234
|
+
if (resolveMilestoneFile(base, mid, "SUMMARY")) {
|
|
235
|
+
try {
|
|
236
|
+
unlinkSync(join(runtimeUnitsDir, file));
|
|
237
|
+
} catch (e) {
|
|
238
|
+
debugLog("stale-unit-cleanup-failed", {
|
|
239
|
+
file,
|
|
240
|
+
error: e instanceof Error ? e.message : String(e),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
207
244
|
}
|
|
208
245
|
}
|
|
246
|
+
} catch (e) {
|
|
247
|
+
debugLog("stale-unit-dir-cleanup-failed", {
|
|
248
|
+
error: e instanceof Error ? e.message : String(e),
|
|
249
|
+
});
|
|
209
250
|
}
|
|
210
|
-
} catch (e) { debugLog("stale-unit-dir-cleanup-failed", { error: getErrorMessage(e) }); }
|
|
211
|
-
|
|
212
|
-
let state = await deriveState(base);
|
|
213
|
-
|
|
214
|
-
// Milestone branch recovery (#601)
|
|
215
|
-
let hasSurvivorBranch = false;
|
|
216
|
-
if (
|
|
217
|
-
state.activeMilestone &&
|
|
218
|
-
(state.phase === "pre-planning" || state.phase === "needs-discussion") &&
|
|
219
|
-
shouldUseWorktreeIsolation() &&
|
|
220
|
-
!detectWorktreeName(base) &&
|
|
221
|
-
!isInsideWorktree(base)
|
|
222
|
-
) {
|
|
223
|
-
const milestoneBranch = `milestone/${state.activeMilestone.id}`;
|
|
224
|
-
const { nativeBranchExists } = await import("./native-git-bridge.js");
|
|
225
|
-
hasSurvivorBranch = nativeBranchExists(base, milestoneBranch);
|
|
226
|
-
if (hasSurvivorBranch) {
|
|
227
|
-
ctx.ui.notify(
|
|
228
|
-
`Found prior session branch ${milestoneBranch}. Resuming.`,
|
|
229
|
-
"info",
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
251
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
252
|
+
let state = await deriveState(base);
|
|
253
|
+
|
|
254
|
+
// Stale worktree state recovery (#654)
|
|
255
|
+
if (
|
|
256
|
+
state.activeMilestone &&
|
|
257
|
+
shouldUseWorktreeIsolation() &&
|
|
258
|
+
!detectWorktreeName(base)
|
|
259
|
+
) {
|
|
260
|
+
const wtPath = getAutoWorktreePath(base, state.activeMilestone.id);
|
|
261
|
+
if (wtPath) {
|
|
262
|
+
state = await deriveState(wtPath);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
239
265
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
return false;
|
|
266
|
+
// Milestone branch recovery (#601)
|
|
267
|
+
let hasSurvivorBranch = false;
|
|
268
|
+
if (
|
|
269
|
+
state.activeMilestone &&
|
|
270
|
+
(state.phase === "pre-planning" || state.phase === "needs-discussion") &&
|
|
271
|
+
shouldUseWorktreeIsolation() &&
|
|
272
|
+
!detectWorktreeName(base) &&
|
|
273
|
+
!base.includes(`${pathSep}.gsd${pathSep}worktrees${pathSep}`)
|
|
274
|
+
) {
|
|
275
|
+
const milestoneBranch = `milestone/${state.activeMilestone.id}`;
|
|
276
|
+
const { nativeBranchExists } = await import("./native-git-bridge.js");
|
|
277
|
+
hasSurvivorBranch = nativeBranchExists(base, milestoneBranch);
|
|
278
|
+
if (hasSurvivorBranch) {
|
|
279
|
+
ctx.ui.notify(
|
|
280
|
+
`Found prior session branch ${milestoneBranch}. Resuming.`,
|
|
281
|
+
"info",
|
|
282
|
+
);
|
|
258
283
|
}
|
|
259
284
|
}
|
|
260
285
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const contextFile = resolveMilestoneFile(base, mid, "CONTEXT");
|
|
265
|
-
const hasContext = !!(contextFile && await loadFile(contextFile));
|
|
266
|
-
if (!hasContext) {
|
|
286
|
+
if (!hasSurvivorBranch) {
|
|
287
|
+
// No active work — start a new milestone via discuss flow
|
|
288
|
+
if (!state.activeMilestone || state.phase === "complete") {
|
|
267
289
|
const { showSmartEntry } = await import("./guided-flow.js");
|
|
268
290
|
await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
|
|
269
291
|
|
|
270
292
|
invalidateAllCaches();
|
|
271
293
|
const postState = await deriveState(base);
|
|
272
|
-
if (
|
|
294
|
+
if (
|
|
295
|
+
postState.activeMilestone &&
|
|
296
|
+
postState.phase !== "complete" &&
|
|
297
|
+
postState.phase !== "pre-planning"
|
|
298
|
+
) {
|
|
273
299
|
state = postState;
|
|
274
|
-
} else
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
300
|
+
} else if (
|
|
301
|
+
postState.activeMilestone &&
|
|
302
|
+
postState.phase === "pre-planning"
|
|
303
|
+
) {
|
|
304
|
+
const contextFile = resolveMilestoneFile(
|
|
305
|
+
base,
|
|
306
|
+
postState.activeMilestone.id,
|
|
307
|
+
"CONTEXT",
|
|
278
308
|
);
|
|
279
|
-
|
|
309
|
+
const hasContext = !!(contextFile && (await loadFile(contextFile)));
|
|
310
|
+
if (hasContext) {
|
|
311
|
+
state = postState;
|
|
312
|
+
} else {
|
|
313
|
+
ctx.ui.notify(
|
|
314
|
+
"Discussion completed but no milestone context was written. Run /gsd to try the discussion again, or /gsd auto after creating the milestone manually.",
|
|
315
|
+
"warning",
|
|
316
|
+
);
|
|
317
|
+
return releaseLockAndReturn();
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
return releaseLockAndReturn();
|
|
280
321
|
}
|
|
281
322
|
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
323
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
324
|
+
// Active milestone exists but has no roadmap
|
|
325
|
+
if (state.phase === "pre-planning") {
|
|
326
|
+
const mid = state.activeMilestone!.id;
|
|
327
|
+
const contextFile = resolveMilestoneFile(base, mid, "CONTEXT");
|
|
328
|
+
const hasContext = !!(contextFile && (await loadFile(contextFile)));
|
|
329
|
+
if (!hasContext) {
|
|
330
|
+
const { showSmartEntry } = await import("./guided-flow.js");
|
|
331
|
+
await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
|
|
332
|
+
|
|
333
|
+
invalidateAllCaches();
|
|
334
|
+
const postState = await deriveState(base);
|
|
335
|
+
if (postState.activeMilestone && postState.phase !== "pre-planning") {
|
|
336
|
+
state = postState;
|
|
337
|
+
} else {
|
|
338
|
+
ctx.ui.notify(
|
|
339
|
+
"Discussion completed but milestone context is still missing. Run /gsd to try again.",
|
|
340
|
+
"warning",
|
|
341
|
+
);
|
|
342
|
+
return releaseLockAndReturn();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
291
347
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
s.basePath = base;
|
|
298
|
-
s.unitDispatchCount.clear();
|
|
299
|
-
s.unitRecoveryCount.clear();
|
|
300
|
-
s.unitConsecutiveSkips.clear();
|
|
301
|
-
s.lastBudgetAlertLevel = 0;
|
|
302
|
-
s.unitLifetimeDispatches.clear();
|
|
303
|
-
s.completedKeySet.clear();
|
|
304
|
-
loadPersistedKeys(base, s.completedKeySet);
|
|
305
|
-
resetHookState();
|
|
306
|
-
restoreHookState(base);
|
|
307
|
-
resetProactiveHealing();
|
|
308
|
-
s.autoStartTime = Date.now();
|
|
309
|
-
s.resourceVersionOnStart = readResourceVersion();
|
|
310
|
-
s.completedUnits = [];
|
|
311
|
-
s.pendingQuickTasks = [];
|
|
312
|
-
s.currentUnit = null;
|
|
313
|
-
s.currentMilestoneId = state.activeMilestone?.id ?? null;
|
|
314
|
-
s.originalModelId = ctx.model?.id ?? null;
|
|
315
|
-
s.originalModelProvider = ctx.model?.provider ?? null;
|
|
316
|
-
|
|
317
|
-
// Register SIGTERM handler
|
|
318
|
-
registerSigtermHandler(base);
|
|
319
|
-
|
|
320
|
-
// Capture integration branch
|
|
321
|
-
if (s.currentMilestoneId) {
|
|
322
|
-
if (getIsolationMode() !== "none") {
|
|
323
|
-
captureIntegrationBranch(base, s.currentMilestoneId);
|
|
348
|
+
// Unreachable safety check
|
|
349
|
+
if (!state.activeMilestone) {
|
|
350
|
+
const { showSmartEntry } = await import("./guided-flow.js");
|
|
351
|
+
await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
|
|
352
|
+
return releaseLockAndReturn();
|
|
324
353
|
}
|
|
325
|
-
setActiveMilestoneId(base, s.currentMilestoneId);
|
|
326
|
-
}
|
|
327
354
|
|
|
328
|
-
|
|
329
|
-
|
|
355
|
+
// ── Initialize session state ──
|
|
356
|
+
s.active = true;
|
|
357
|
+
s.stepMode = requestedStepMode;
|
|
358
|
+
s.verbose = verboseMode;
|
|
359
|
+
s.cmdCtx = ctx;
|
|
360
|
+
s.basePath = base;
|
|
361
|
+
s.unitDispatchCount.clear();
|
|
362
|
+
s.unitRecoveryCount.clear();
|
|
363
|
+
s.lastBudgetAlertLevel = 0;
|
|
364
|
+
s.unitLifetimeDispatches.clear();
|
|
365
|
+
resetHookState();
|
|
366
|
+
restoreHookState(base);
|
|
367
|
+
resetProactiveHealing();
|
|
368
|
+
s.autoStartTime = Date.now();
|
|
369
|
+
s.resourceVersionOnStart = readResourceVersion();
|
|
370
|
+
s.completedUnits = [];
|
|
371
|
+
s.pendingQuickTasks = [];
|
|
372
|
+
s.currentUnit = null;
|
|
373
|
+
s.currentMilestoneId = state.activeMilestone?.id ?? null;
|
|
374
|
+
s.originalModelId = ctx.model?.id ?? null;
|
|
375
|
+
s.originalModelProvider = ctx.model?.provider ?? null;
|
|
376
|
+
|
|
377
|
+
// Register SIGTERM handler
|
|
378
|
+
registerSigtermHandler(base);
|
|
379
|
+
|
|
380
|
+
// Capture integration branch
|
|
381
|
+
if (s.currentMilestoneId) {
|
|
382
|
+
if (getIsolationMode() !== "none") {
|
|
383
|
+
captureIntegrationBranch(base, s.currentMilestoneId, { commitDocs });
|
|
384
|
+
}
|
|
385
|
+
setActiveMilestoneId(base, s.currentMilestoneId);
|
|
386
|
+
}
|
|
330
387
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
388
|
+
// ── Auto-worktree setup ──
|
|
389
|
+
s.originalBasePath = base;
|
|
390
|
+
|
|
391
|
+
const isUnderGsdWorktrees = (p: string): boolean => {
|
|
392
|
+
const marker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
|
|
393
|
+
if (p.includes(marker)) return true;
|
|
394
|
+
const worktreesSuffix = `${pathSep}.gsd${pathSep}worktrees`;
|
|
395
|
+
return p.endsWith(worktreesSuffix);
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
if (
|
|
399
|
+
s.currentMilestoneId &&
|
|
400
|
+
shouldUseWorktreeIsolation() &&
|
|
401
|
+
!detectWorktreeName(base) &&
|
|
402
|
+
!isUnderGsdWorktrees(base)
|
|
403
|
+
) {
|
|
404
|
+
buildResolver().enterMilestone(s.currentMilestoneId, {
|
|
405
|
+
notify: ctx.ui.notify.bind(ctx.ui),
|
|
406
|
+
});
|
|
407
|
+
if (s.basePath !== base) {
|
|
408
|
+
// Successfully entered worktree — re-register SIGTERM handler at original base
|
|
409
|
+
registerSigtermHandler(s.originalBasePath);
|
|
344
410
|
}
|
|
345
|
-
registerSigtermHandler(s.originalBasePath);
|
|
346
|
-
} catch (err) {
|
|
347
|
-
ctx.ui.notify(
|
|
348
|
-
`Auto-worktree setup failed: ${getErrorMessage(err)}. Continuing in project root.`,
|
|
349
|
-
"warning",
|
|
350
|
-
);
|
|
351
411
|
}
|
|
352
|
-
}
|
|
353
412
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
413
|
+
// ── DB lifecycle ──
|
|
414
|
+
const gsdDbPath = join(s.basePath, ".gsd", "gsd.db");
|
|
415
|
+
const gsdDirPath = join(s.basePath, ".gsd");
|
|
416
|
+
if (existsSync(gsdDirPath) && !existsSync(gsdDbPath)) {
|
|
417
|
+
const hasDecisions = existsSync(join(gsdDirPath, "DECISIONS.md"));
|
|
418
|
+
const hasRequirements = existsSync(join(gsdDirPath, "REQUIREMENTS.md"));
|
|
419
|
+
const hasMilestones = existsSync(join(gsdDirPath, "milestones"));
|
|
420
|
+
if (hasDecisions || hasRequirements || hasMilestones) {
|
|
421
|
+
try {
|
|
422
|
+
const { openDatabase: openDb } = await import("./gsd-db.js");
|
|
423
|
+
const { migrateFromMarkdown } = await import("./md-importer.js");
|
|
424
|
+
openDb(gsdDbPath);
|
|
425
|
+
migrateFromMarkdown(s.basePath);
|
|
426
|
+
} catch (err) {
|
|
427
|
+
process.stderr.write(
|
|
428
|
+
`gsd-migrate: auto-migration failed: ${(err as Error).message}\n`,
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (existsSync(gsdDbPath) && !isDbAvailable()) {
|
|
362
434
|
try {
|
|
363
435
|
const { openDatabase: openDb } = await import("./gsd-db.js");
|
|
364
|
-
const { migrateFromMarkdown } = await import("./md-importer.js");
|
|
365
436
|
openDb(gsdDbPath);
|
|
366
|
-
migrateFromMarkdown(s.basePath);
|
|
367
437
|
} catch (err) {
|
|
368
|
-
process.stderr.write(
|
|
438
|
+
process.stderr.write(
|
|
439
|
+
`gsd-db: failed to open existing database: ${(err as Error).message}\n`,
|
|
440
|
+
);
|
|
369
441
|
}
|
|
370
442
|
}
|
|
371
|
-
}
|
|
372
|
-
if (existsSync(gsdDbPath) && !isDbAvailable()) {
|
|
373
|
-
try {
|
|
374
|
-
const { openDatabase: openDb } = await import("./gsd-db.js");
|
|
375
|
-
openDb(gsdDbPath);
|
|
376
|
-
} catch (err) {
|
|
377
|
-
process.stderr.write(`gsd-db: failed to open existing database: ${(err as Error).message}\n`);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
443
|
|
|
381
|
-
|
|
382
|
-
|
|
444
|
+
// Initialize metrics
|
|
445
|
+
initMetrics(s.basePath);
|
|
383
446
|
|
|
384
|
-
|
|
385
|
-
|
|
447
|
+
// Initialize routing history
|
|
448
|
+
initRoutingHistory(s.basePath);
|
|
386
449
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
450
|
+
// Capture session's model at auto-mode start (#650)
|
|
451
|
+
const currentModel = ctx.model;
|
|
452
|
+
if (currentModel) {
|
|
453
|
+
s.autoModeStartModel = {
|
|
454
|
+
provider: currentModel.provider,
|
|
455
|
+
id: currentModel.id,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
392
458
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
459
|
+
// Snapshot installed skills
|
|
460
|
+
if (resolveSkillDiscoveryMode() !== "off") {
|
|
461
|
+
snapshotSkills();
|
|
462
|
+
}
|
|
397
463
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
464
|
+
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
465
|
+
ctx.ui.setFooter(hideFooter);
|
|
466
|
+
const modeLabel = s.stepMode ? "Step-mode" : "Auto-mode";
|
|
467
|
+
const pendingCount = (state.registry ?? []).filter(
|
|
468
|
+
(m) => m.status !== "complete" && m.status !== "parked",
|
|
469
|
+
).length;
|
|
470
|
+
const scopeMsg =
|
|
471
|
+
pendingCount > 1
|
|
472
|
+
? `Will loop through ${pendingCount} milestones.`
|
|
473
|
+
: "Will loop until milestone complete.";
|
|
474
|
+
ctx.ui.notify(`${modeLabel} started. ${scopeMsg}`, "info");
|
|
475
|
+
|
|
476
|
+
updateSessionLock(
|
|
477
|
+
lockBase(),
|
|
478
|
+
"starting",
|
|
479
|
+
s.currentMilestoneId ?? "unknown",
|
|
480
|
+
0,
|
|
481
|
+
);
|
|
482
|
+
writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown", 0);
|
|
483
|
+
|
|
484
|
+
// Secrets collection gate
|
|
485
|
+
const mid = state.activeMilestone!.id;
|
|
486
|
+
try {
|
|
487
|
+
const manifestStatus = await getManifestStatus(base, mid);
|
|
488
|
+
if (manifestStatus && manifestStatus.pending.length > 0) {
|
|
489
|
+
const result = await collectSecretsFromManifest(base, mid, ctx);
|
|
490
|
+
if (
|
|
491
|
+
result &&
|
|
492
|
+
result.applied &&
|
|
493
|
+
result.skipped &&
|
|
494
|
+
result.existingSkipped
|
|
495
|
+
) {
|
|
496
|
+
ctx.ui.notify(
|
|
497
|
+
`Secrets collected: ${result.applied.length} applied, ${result.skipped.length} skipped, ${result.existingSkipped.length} already set.`,
|
|
498
|
+
"info",
|
|
499
|
+
);
|
|
500
|
+
} else {
|
|
501
|
+
ctx.ui.notify("Secrets collection skipped.", "info");
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
} catch (err) {
|
|
420
505
|
ctx.ui.notify(
|
|
421
|
-
`
|
|
422
|
-
"warning",
|
|
423
|
-
);
|
|
424
|
-
ctx.ui.setStatus("gsd-auto", "paused");
|
|
425
|
-
sendDesktopNotification(
|
|
426
|
-
"GSD — Secrets Required",
|
|
427
|
-
`${pendingKeys.length} env variable(s) needed for ${mid}. Run /gsd secrets to provide them.`,
|
|
506
|
+
`Secrets collection error: ${err instanceof Error ? err.message : String(err)}. Continuing with next task.`,
|
|
428
507
|
"warning",
|
|
429
|
-
"attention",
|
|
430
508
|
);
|
|
431
|
-
// Notify remote channel if configured (one-way — never collect secrets via remote)
|
|
432
|
-
sendRemoteNotification(
|
|
433
|
-
"GSD — Secrets Required",
|
|
434
|
-
`Auto-mode paused: ${pendingKeys.length} env variable(s) needed for ${mid}.\n${keyList}\n\nReturn to the terminal and run /gsd secrets to provide them securely.`,
|
|
435
|
-
).catch(() => {}); // fire-and-forget
|
|
436
|
-
return false;
|
|
437
509
|
}
|
|
438
|
-
} catch (err) {
|
|
439
|
-
ctx.ui.notify(
|
|
440
|
-
`Secrets check error: ${getErrorMessage(err)}. Continuing without secrets.`,
|
|
441
|
-
"warning",
|
|
442
|
-
);
|
|
443
|
-
}
|
|
444
510
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
511
|
+
// Self-heal: remove stale .git/index.lock
|
|
512
|
+
try {
|
|
513
|
+
const gitLockFile = join(base, ".git", "index.lock");
|
|
514
|
+
if (existsSync(gitLockFile)) {
|
|
515
|
+
const lockAge = Date.now() - statSync(gitLockFile).mtimeMs;
|
|
516
|
+
if (lockAge > 60_000) {
|
|
517
|
+
unlinkSync(gitLockFile);
|
|
518
|
+
ctx.ui.notify(
|
|
519
|
+
"Removed stale .git/index.lock from prior crash.",
|
|
520
|
+
"info",
|
|
521
|
+
);
|
|
522
|
+
}
|
|
456
523
|
}
|
|
524
|
+
} catch (e) {
|
|
525
|
+
debugLog("git-lock-cleanup-failed", {
|
|
526
|
+
error: e instanceof Error ? e.message : String(e),
|
|
527
|
+
});
|
|
457
528
|
}
|
|
458
|
-
} catch (e) { debugLog("git-lock-cleanup-failed", { error: getErrorMessage(e) }); }
|
|
459
529
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
530
|
+
// Pre-flight: validate milestone queue
|
|
531
|
+
try {
|
|
532
|
+
const msDir = join(base, ".gsd", "milestones");
|
|
533
|
+
if (existsSync(msDir)) {
|
|
534
|
+
const milestoneIds = readdirSync(msDir, { withFileTypes: true })
|
|
535
|
+
.filter((d) => d.isDirectory() && /^M\d{3}/.test(d.name))
|
|
536
|
+
.map((d) => d.name.match(/^(M\d{3})/)?.[1] ?? d.name);
|
|
537
|
+
if (milestoneIds.length > 1) {
|
|
538
|
+
const issues: string[] = [];
|
|
539
|
+
for (const id of milestoneIds) {
|
|
540
|
+
const draft = resolveMilestoneFile(base, id, "CONTEXT-DRAFT");
|
|
541
|
+
if (draft)
|
|
542
|
+
issues.push(
|
|
543
|
+
`${id}: has CONTEXT-DRAFT.md (will pause for discussion)`,
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
if (issues.length > 0) {
|
|
547
|
+
ctx.ui.notify(
|
|
548
|
+
`Pre-flight: ${milestoneIds.length} milestones queued.\n${issues.map((i) => ` ⚠ ${i}`).join("\n")}`,
|
|
549
|
+
"warning",
|
|
550
|
+
);
|
|
551
|
+
} else {
|
|
552
|
+
ctx.ui.notify(
|
|
553
|
+
`Pre-flight: ${milestoneIds.length} milestones queued. All have full context.`,
|
|
554
|
+
"info",
|
|
555
|
+
);
|
|
556
|
+
}
|
|
477
557
|
}
|
|
478
558
|
}
|
|
559
|
+
} catch {
|
|
560
|
+
/* non-fatal */
|
|
479
561
|
}
|
|
480
|
-
} catch { /* non-fatal */ }
|
|
481
562
|
|
|
482
|
-
|
|
563
|
+
return true;
|
|
564
|
+
} catch (err) {
|
|
565
|
+
releaseSessionLock(base);
|
|
566
|
+
clearLock(base);
|
|
567
|
+
throw err;
|
|
568
|
+
}
|
|
483
569
|
}
|