gsd-pi 2.32.0-dev.1e39869 → 2.32.0-dev.3d7932c
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 +27 -20
- package/dist/resource-loader.js +13 -3
- package/dist/resources/extensions/gsd/auto-dashboard.ts +3 -1
- package/dist/resources/extensions/gsd/auto-idempotency.ts +3 -2
- package/dist/resources/extensions/gsd/auto-observability.ts +2 -4
- package/dist/resources/extensions/gsd/auto-post-unit.ts +5 -5
- package/dist/resources/extensions/gsd/auto-prompts.ts +46 -44
- package/dist/resources/extensions/gsd/auto-recovery.ts +8 -22
- package/dist/resources/extensions/gsd/auto-start.ts +8 -6
- package/dist/resources/extensions/gsd/auto-stuck-detection.ts +3 -2
- package/dist/resources/extensions/gsd/auto-timeout-recovery.ts +2 -1
- package/dist/resources/extensions/gsd/auto-timers.ts +3 -2
- package/dist/resources/extensions/gsd/auto-verification.ts +6 -6
- package/dist/resources/extensions/gsd/auto-worktree.ts +5 -4
- package/dist/resources/extensions/gsd/auto.ts +28 -27
- package/dist/resources/extensions/gsd/commands-inspect.ts +2 -1
- package/dist/resources/extensions/gsd/commands-workflow-templates.ts +2 -1
- package/dist/resources/extensions/gsd/complexity-classifier.ts +5 -7
- package/dist/resources/extensions/gsd/crash-recovery.ts +15 -2
- package/dist/resources/extensions/gsd/dispatch-guard.ts +2 -1
- package/dist/resources/extensions/gsd/error-utils.ts +6 -0
- package/dist/resources/extensions/gsd/export.ts +2 -1
- package/dist/resources/extensions/gsd/git-service.ts +3 -2
- package/dist/resources/extensions/gsd/guided-flow.ts +3 -2
- package/dist/resources/extensions/gsd/index.ts +12 -5
- package/dist/resources/extensions/gsd/key-manager.ts +2 -1
- package/dist/resources/extensions/gsd/marketplace-discovery.ts +4 -3
- package/dist/resources/extensions/gsd/metrics.ts +3 -3
- package/dist/resources/extensions/gsd/migrate-external.ts +21 -4
- package/dist/resources/extensions/gsd/milestone-ids.ts +2 -1
- package/dist/resources/extensions/gsd/native-git-bridge.ts +2 -1
- package/dist/resources/extensions/gsd/parallel-merge.ts +2 -1
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +2 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +8 -9
- package/dist/resources/extensions/gsd/quick.ts +58 -3
- package/dist/resources/extensions/gsd/repo-identity.ts +22 -1
- package/dist/resources/extensions/gsd/session-lock.ts +12 -1
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/dist/resources/extensions/gsd/undo.ts +5 -7
- package/dist/resources/extensions/gsd/unit-id.ts +14 -0
- package/dist/resources/extensions/gsd/unit-runtime.ts +2 -1
- package/dist/resources/extensions/gsd/worktree-command.ts +8 -7
- package/package.json +1 -1
- package/src/resources/extensions/gsd/auto-dashboard.ts +3 -1
- package/src/resources/extensions/gsd/auto-idempotency.ts +3 -2
- package/src/resources/extensions/gsd/auto-observability.ts +2 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +5 -5
- package/src/resources/extensions/gsd/auto-prompts.ts +46 -44
- package/src/resources/extensions/gsd/auto-recovery.ts +8 -22
- package/src/resources/extensions/gsd/auto-start.ts +8 -6
- package/src/resources/extensions/gsd/auto-stuck-detection.ts +3 -2
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -1
- package/src/resources/extensions/gsd/auto-timers.ts +3 -2
- package/src/resources/extensions/gsd/auto-verification.ts +6 -6
- package/src/resources/extensions/gsd/auto-worktree.ts +5 -4
- package/src/resources/extensions/gsd/auto.ts +28 -27
- package/src/resources/extensions/gsd/commands-inspect.ts +2 -1
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +2 -1
- package/src/resources/extensions/gsd/complexity-classifier.ts +5 -7
- package/src/resources/extensions/gsd/crash-recovery.ts +15 -2
- package/src/resources/extensions/gsd/dispatch-guard.ts +2 -1
- package/src/resources/extensions/gsd/error-utils.ts +6 -0
- package/src/resources/extensions/gsd/export.ts +2 -1
- package/src/resources/extensions/gsd/git-service.ts +3 -2
- package/src/resources/extensions/gsd/guided-flow.ts +3 -2
- package/src/resources/extensions/gsd/index.ts +12 -5
- package/src/resources/extensions/gsd/key-manager.ts +2 -1
- package/src/resources/extensions/gsd/marketplace-discovery.ts +4 -3
- package/src/resources/extensions/gsd/metrics.ts +3 -3
- package/src/resources/extensions/gsd/migrate-external.ts +21 -4
- package/src/resources/extensions/gsd/milestone-ids.ts +2 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +2 -1
- package/src/resources/extensions/gsd/parallel-merge.ts +2 -1
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +2 -1
- package/src/resources/extensions/gsd/post-unit-hooks.ts +8 -9
- package/src/resources/extensions/gsd/quick.ts +58 -3
- package/src/resources/extensions/gsd/repo-identity.ts +22 -1
- package/src/resources/extensions/gsd/session-lock.ts +12 -1
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/src/resources/extensions/gsd/undo.ts +5 -7
- package/src/resources/extensions/gsd/unit-id.ts +14 -0
- package/src/resources/extensions/gsd/unit-runtime.ts +2 -1
- package/src/resources/extensions/gsd/worktree-command.ts +8 -7
|
@@ -189,30 +189,52 @@ export async function inlineGsdRootFile(
|
|
|
189
189
|
// ─── DB-Aware Inline Helpers ──────────────────────────────────────────────
|
|
190
190
|
|
|
191
191
|
/**
|
|
192
|
-
*
|
|
193
|
-
*
|
|
192
|
+
* Shared DB-fallback pattern: attempt a DB query via the context-store, format
|
|
193
|
+
* the result, and fall back to the filesystem file when the DB is unavailable
|
|
194
|
+
* or the query yields no results.
|
|
195
|
+
*
|
|
196
|
+
* @param base Project root for filesystem fallback
|
|
197
|
+
* @param label Section heading (e.g. "Decisions")
|
|
198
|
+
* @param filename Filesystem fallback file (e.g. "decisions.md")
|
|
199
|
+
* @param queryDb Async callback receiving the dynamically-imported
|
|
200
|
+
* context-store module. Returns formatted markdown or null.
|
|
194
201
|
*/
|
|
195
|
-
|
|
196
|
-
base: string,
|
|
202
|
+
async function inlineFromDbOrFile(
|
|
203
|
+
base: string,
|
|
204
|
+
label: string,
|
|
205
|
+
filename: string,
|
|
206
|
+
queryDb: (cs: typeof import("./context-store.js")) => string | null,
|
|
197
207
|
): Promise<string | null> {
|
|
198
|
-
const inlineLevel = level ?? resolveInlineLevel();
|
|
199
208
|
try {
|
|
200
209
|
const { isDbAvailable } = await import("./gsd-db.js");
|
|
201
210
|
if (isDbAvailable()) {
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
const formatted = inlineLevel !== "full"
|
|
207
|
-
? formatDecisionsCompact(decisions)
|
|
208
|
-
: formatDecisionsForPrompt(decisions);
|
|
209
|
-
return `### Decisions\nSource: \`.gsd/DECISIONS.md\`\n\n${formatted}`;
|
|
211
|
+
const contextStore = await import("./context-store.js");
|
|
212
|
+
const content = queryDb(contextStore);
|
|
213
|
+
if (content) {
|
|
214
|
+
return `### ${label}\nSource: \`.gsd/${filename.toUpperCase().replace(/\.MD$/i, "")}.md\`\n\n${content}`;
|
|
210
215
|
}
|
|
211
216
|
}
|
|
212
217
|
} catch {
|
|
213
218
|
// DB not available — fall through to filesystem
|
|
214
219
|
}
|
|
215
|
-
return inlineGsdRootFile(base,
|
|
220
|
+
return inlineGsdRootFile(base, filename, label);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Inline decisions with optional milestone scoping from the DB.
|
|
225
|
+
* Falls back to filesystem via inlineGsdRootFile when DB unavailable or empty.
|
|
226
|
+
*/
|
|
227
|
+
export async function inlineDecisionsFromDb(
|
|
228
|
+
base: string, milestoneId?: string, scope?: string, level?: InlineLevel,
|
|
229
|
+
): Promise<string | null> {
|
|
230
|
+
const inlineLevel = level ?? resolveInlineLevel();
|
|
231
|
+
return inlineFromDbOrFile(base, "Decisions", "decisions.md", (cs) => {
|
|
232
|
+
const decisions = cs.queryDecisions({ milestoneId, scope });
|
|
233
|
+
if (decisions.length === 0) return null;
|
|
234
|
+
return inlineLevel !== "full"
|
|
235
|
+
? formatDecisionsCompact(decisions)
|
|
236
|
+
: cs.formatDecisionsForPrompt(decisions);
|
|
237
|
+
});
|
|
216
238
|
}
|
|
217
239
|
|
|
218
240
|
/**
|
|
@@ -223,23 +245,13 @@ export async function inlineRequirementsFromDb(
|
|
|
223
245
|
base: string, sliceId?: string, level?: InlineLevel,
|
|
224
246
|
): Promise<string | null> {
|
|
225
247
|
const inlineLevel = level ?? resolveInlineLevel();
|
|
226
|
-
|
|
227
|
-
const {
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const formatted = inlineLevel !== "full"
|
|
234
|
-
? formatRequirementsCompact(requirements)
|
|
235
|
-
: formatRequirementsForPrompt(requirements);
|
|
236
|
-
return `### Requirements\nSource: \`.gsd/REQUIREMENTS.md\`\n\n${formatted}`;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
} catch {
|
|
240
|
-
// DB not available — fall through to filesystem
|
|
241
|
-
}
|
|
242
|
-
return inlineGsdRootFile(base, "requirements.md", "Requirements");
|
|
248
|
+
return inlineFromDbOrFile(base, "Requirements", "requirements.md", (cs) => {
|
|
249
|
+
const requirements = cs.queryRequirements({ sliceId });
|
|
250
|
+
if (requirements.length === 0) return null;
|
|
251
|
+
return inlineLevel !== "full"
|
|
252
|
+
? formatRequirementsCompact(requirements)
|
|
253
|
+
: cs.formatRequirementsForPrompt(requirements);
|
|
254
|
+
});
|
|
243
255
|
}
|
|
244
256
|
|
|
245
257
|
/**
|
|
@@ -249,19 +261,9 @@ export async function inlineRequirementsFromDb(
|
|
|
249
261
|
export async function inlineProjectFromDb(
|
|
250
262
|
base: string,
|
|
251
263
|
): Promise<string | null> {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const { queryProject } = await import("./context-store.js");
|
|
256
|
-
const content = queryProject();
|
|
257
|
-
if (content) {
|
|
258
|
-
return `### Project\nSource: \`.gsd/PROJECT.md\`\n\n${content}`;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
} catch {
|
|
262
|
-
// DB not available — fall through to filesystem
|
|
263
|
-
}
|
|
264
|
-
return inlineGsdRootFile(base, "project.md", "Project");
|
|
264
|
+
return inlineFromDbOrFile(base, "Project", "project.md", (cs) => {
|
|
265
|
+
return cs.queryProject();
|
|
266
|
+
});
|
|
265
267
|
}
|
|
266
268
|
|
|
267
269
|
// ─── Skill Discovery ──────────────────────────────────────────────────────
|
|
@@ -42,6 +42,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "
|
|
|
42
42
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
43
43
|
import { loadJsonFileOrNull } from "./json-persistence.js";
|
|
44
44
|
import { dirname, join } from "node:path";
|
|
45
|
+
import { parseUnitId } from "./unit-id.js";
|
|
45
46
|
|
|
46
47
|
// ─── Artifact Resolution & Verification ───────────────────────────────────────
|
|
47
48
|
|
|
@@ -49,9 +50,7 @@ import { dirname, join } from "node:path";
|
|
|
49
50
|
* Resolve the expected artifact for a unit to an absolute path.
|
|
50
51
|
*/
|
|
51
52
|
export function resolveExpectedArtifactPath(unitType: string, unitId: string, base: string): string | null {
|
|
52
|
-
const
|
|
53
|
-
const mid = parts[0]!;
|
|
54
|
-
const sid = parts[1];
|
|
53
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
55
54
|
switch (unitType) {
|
|
56
55
|
case "research-milestone": {
|
|
57
56
|
const dir = resolveMilestonePath(base, mid);
|
|
@@ -78,7 +77,6 @@ export function resolveExpectedArtifactPath(unitType: string, unitId: string, ba
|
|
|
78
77
|
return dir ? join(dir, buildSliceFileName(sid!, "UAT-RESULT")) : null;
|
|
79
78
|
}
|
|
80
79
|
case "execute-task": {
|
|
81
|
-
const tid = parts[2];
|
|
82
80
|
const dir = resolveSlicePath(base, mid, sid!);
|
|
83
81
|
return dir && tid ? join(dir, "tasks", buildTaskFileName(tid, "SUMMARY")) : null;
|
|
84
82
|
}
|
|
@@ -167,10 +165,7 @@ export function verifyExpectedArtifact(unitType: string, unitId: string, base: s
|
|
|
167
165
|
|
|
168
166
|
// execute-task must also have its checkbox marked [x] in the slice plan
|
|
169
167
|
if (unitType === "execute-task") {
|
|
170
|
-
const
|
|
171
|
-
const mid = parts[0];
|
|
172
|
-
const sid = parts[1];
|
|
173
|
-
const tid = parts[2];
|
|
168
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
174
169
|
if (mid && sid && tid) {
|
|
175
170
|
const planAbs = resolveSliceFile(base, mid, sid, "PLAN");
|
|
176
171
|
if (planAbs && existsSync(planAbs)) {
|
|
@@ -187,9 +182,7 @@ export function verifyExpectedArtifact(unitType: string, unitId: string, base: s
|
|
|
187
182
|
// but omitted T{tid}-PLAN.md files would be marked complete, causing execute-task
|
|
188
183
|
// to dispatch with a missing task plan (see issue #739).
|
|
189
184
|
if (unitType === "plan-slice") {
|
|
190
|
-
const
|
|
191
|
-
const mid = parts[0];
|
|
192
|
-
const sid = parts[1];
|
|
185
|
+
const { milestone: mid, slice: sid } = parseUnitId(unitId);
|
|
193
186
|
if (mid && sid) {
|
|
194
187
|
try {
|
|
195
188
|
const planContent = readFileSync(absPath, "utf-8");
|
|
@@ -213,9 +206,8 @@ export function verifyExpectedArtifact(unitType: string, unitId: string, base: s
|
|
|
213
206
|
// state machine keeps returning the same complete-slice unit (roadmap still shows
|
|
214
207
|
// the slice incomplete), so dispatchNextUnit recurses forever.
|
|
215
208
|
if (unitType === "complete-slice") {
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
const sid = parts[1];
|
|
209
|
+
const { milestone: mid, slice: sid } = parseUnitId(unitId);
|
|
210
|
+
|
|
219
211
|
if (mid && sid) {
|
|
220
212
|
const dir = resolveSlicePath(base, mid, sid);
|
|
221
213
|
if (dir) {
|
|
@@ -268,9 +260,7 @@ export function writeBlockerPlaceholder(unitType: string, unitId: string, base:
|
|
|
268
260
|
}
|
|
269
261
|
|
|
270
262
|
export function diagnoseExpectedArtifact(unitType: string, unitId: string, base: string): string | null {
|
|
271
|
-
const
|
|
272
|
-
const mid = parts[0];
|
|
273
|
-
const sid = parts[1];
|
|
263
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
274
264
|
switch (unitType) {
|
|
275
265
|
case "research-milestone":
|
|
276
266
|
return `${relMilestoneFile(base, mid!, "RESEARCH")} (milestone research)`;
|
|
@@ -281,7 +271,6 @@ export function diagnoseExpectedArtifact(unitType: string, unitId: string, base:
|
|
|
281
271
|
case "plan-slice":
|
|
282
272
|
return `${relSliceFile(base, mid!, sid!, "PLAN")} (slice plan)`;
|
|
283
273
|
case "execute-task": {
|
|
284
|
-
const tid = parts[2];
|
|
285
274
|
return `Task ${tid} marked [x] in ${relSliceFile(base, mid!, sid!, "PLAN")} + summary written`;
|
|
286
275
|
}
|
|
287
276
|
case "complete-slice":
|
|
@@ -539,10 +528,7 @@ export async function selfHealRuntimeRecords(
|
|
|
539
528
|
* These are shown when automatic reconciliation is not possible.
|
|
540
529
|
*/
|
|
541
530
|
export function buildLoopRemediationSteps(unitType: string, unitId: string, base: string): string | null {
|
|
542
|
-
const
|
|
543
|
-
const mid = parts[0];
|
|
544
|
-
const sid = parts[1];
|
|
545
|
-
const tid = parts[2];
|
|
531
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
546
532
|
switch (unitType) {
|
|
547
533
|
case "execute-task": {
|
|
548
534
|
if (!mid || !sid || !tid) break;
|
|
@@ -63,6 +63,8 @@ import { debugLog, enableDebug, isDebugEnabled, getDebugLogPath } from "./debug-
|
|
|
63
63
|
import type { AutoSession } from "./auto/session.js";
|
|
64
64
|
import { existsSync, mkdirSync, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
65
65
|
import { join } from "node:path";
|
|
66
|
+
import { getErrorMessage } from "./error-utils.js";
|
|
67
|
+
import { parseUnitId } from "./unit-id.js";
|
|
66
68
|
|
|
67
69
|
export interface BootstrapDeps {
|
|
68
70
|
shouldUseWorktreeIsolation: () => boolean;
|
|
@@ -138,7 +140,7 @@ export async function bootstrapAutoSession(
|
|
|
138
140
|
if (crashLock && crashLock.pid !== process.pid) {
|
|
139
141
|
// We already hold the session lock, so no concurrent session is running.
|
|
140
142
|
// The crash lock is from a dead process — recover context from it.
|
|
141
|
-
const recoveredMid = crashLock.unitId.
|
|
143
|
+
const recoveredMid = parseUnitId(crashLock.unitId).milestone;
|
|
142
144
|
const milestoneAlreadyComplete = recoveredMid
|
|
143
145
|
? !!resolveMilestoneFile(base, recoveredMid, "SUMMARY")
|
|
144
146
|
: false;
|
|
@@ -201,11 +203,11 @@ export async function bootstrapAutoSession(
|
|
|
201
203
|
if (!midMatch) continue;
|
|
202
204
|
const mid = midMatch[1];
|
|
203
205
|
if (resolveMilestoneFile(base, mid, "SUMMARY")) {
|
|
204
|
-
try { unlinkSync(join(runtimeUnitsDir, file)); } catch (e) { debugLog("stale-unit-cleanup-failed", { file, error:
|
|
206
|
+
try { unlinkSync(join(runtimeUnitsDir, file)); } catch (e) { debugLog("stale-unit-cleanup-failed", { file, error: getErrorMessage(e) }); }
|
|
205
207
|
}
|
|
206
208
|
}
|
|
207
209
|
}
|
|
208
|
-
} catch (e) { debugLog("stale-unit-dir-cleanup-failed", { error:
|
|
210
|
+
} catch (e) { debugLog("stale-unit-dir-cleanup-failed", { error: getErrorMessage(e) }); }
|
|
209
211
|
|
|
210
212
|
let state = await deriveState(base);
|
|
211
213
|
|
|
@@ -343,7 +345,7 @@ export async function bootstrapAutoSession(
|
|
|
343
345
|
registerSigtermHandler(s.originalBasePath);
|
|
344
346
|
} catch (err) {
|
|
345
347
|
ctx.ui.notify(
|
|
346
|
-
`Auto-worktree setup failed: ${
|
|
348
|
+
`Auto-worktree setup failed: ${getErrorMessage(err)}. Continuing in project root.`,
|
|
347
349
|
"warning",
|
|
348
350
|
);
|
|
349
351
|
}
|
|
@@ -435,7 +437,7 @@ export async function bootstrapAutoSession(
|
|
|
435
437
|
}
|
|
436
438
|
} catch (err) {
|
|
437
439
|
ctx.ui.notify(
|
|
438
|
-
`Secrets check error: ${
|
|
440
|
+
`Secrets check error: ${getErrorMessage(err)}. Continuing without secrets.`,
|
|
439
441
|
"warning",
|
|
440
442
|
);
|
|
441
443
|
}
|
|
@@ -453,7 +455,7 @@ export async function bootstrapAutoSession(
|
|
|
453
455
|
ctx.ui.notify("Removed stale .git/index.lock from prior crash.", "info");
|
|
454
456
|
}
|
|
455
457
|
}
|
|
456
|
-
} catch (e) { debugLog("git-lock-cleanup-failed", { error:
|
|
458
|
+
} catch (e) { debugLog("git-lock-cleanup-failed", { error: getErrorMessage(e) }); }
|
|
457
459
|
|
|
458
460
|
// Pre-flight: validate milestone queue
|
|
459
461
|
try {
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
import type { AutoSession } from "./auto/session.js";
|
|
40
40
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
41
41
|
import { join } from "node:path";
|
|
42
|
+
import { parseUnitId } from "./unit-id.js";
|
|
42
43
|
|
|
43
44
|
export interface StuckContext {
|
|
44
45
|
s: AutoSession;
|
|
@@ -99,7 +100,7 @@ export async function checkStuckAndRecover(sctx: StuckContext): Promise<StuckRes
|
|
|
99
100
|
|
|
100
101
|
// Final reconciliation pass for execute-task
|
|
101
102
|
if (unitType === "execute-task") {
|
|
102
|
-
const
|
|
103
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
103
104
|
if (mid && sid && tid) {
|
|
104
105
|
const status = await inspectExecuteTaskDurability(basePath, unitId);
|
|
105
106
|
if (status) {
|
|
@@ -168,7 +169,7 @@ export async function checkStuckAndRecover(sctx: StuckContext): Promise<StuckRes
|
|
|
168
169
|
// Adaptive self-repair: each retry attempts a different remediation step.
|
|
169
170
|
if (unitType === "execute-task") {
|
|
170
171
|
const status = await inspectExecuteTaskDurability(basePath, unitId);
|
|
171
|
-
const
|
|
172
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
172
173
|
if (status && mid && sid && tid) {
|
|
173
174
|
if (status.summaryExists && !status.taskChecked) {
|
|
174
175
|
const repaired = skipExecuteTask(basePath, mid, sid, tid, status, "self-repair", 0);
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
writeBlockerPlaceholder,
|
|
19
19
|
} from "./auto-recovery.js";
|
|
20
20
|
import { existsSync } from "node:fs";
|
|
21
|
+
import { parseUnitId } from "./unit-id.js";
|
|
21
22
|
|
|
22
23
|
export interface RecoveryContext {
|
|
23
24
|
basePath: string;
|
|
@@ -128,7 +129,7 @@ export async function recoverTimedOutUnit(
|
|
|
128
129
|
|
|
129
130
|
// Retries exhausted — write missing durable artifacts and advance.
|
|
130
131
|
const diagnostic = formatExecuteTaskRecoveryStatus(status);
|
|
131
|
-
const
|
|
132
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
132
133
|
const skipped = mid && sid && tid
|
|
133
134
|
? skipExecuteTask(basePath, mid, sid, tid, status, reason, maxRecoveryAttempts)
|
|
134
135
|
: false;
|
|
@@ -20,6 +20,7 @@ import { closeoutUnit, type CloseoutOptions } from "./auto-unit-closeout.js";
|
|
|
20
20
|
import { saveActivityLog } from "./activity-log.js";
|
|
21
21
|
import { recoverTimedOutUnit, type RecoveryContext } from "./auto-timeout-recovery.js";
|
|
22
22
|
import type { AutoSession } from "./auto/session.js";
|
|
23
|
+
import { getErrorMessage } from "./error-utils.js";
|
|
23
24
|
|
|
24
25
|
export interface SupervisionContext {
|
|
25
26
|
s: AutoSession;
|
|
@@ -127,7 +128,7 @@ export function startUnitSupervision(sctx: SupervisionContext): void {
|
|
|
127
128
|
);
|
|
128
129
|
await pauseAuto(ctx, pi);
|
|
129
130
|
} catch (err) {
|
|
130
|
-
const message =
|
|
131
|
+
const message = getErrorMessage(err);
|
|
131
132
|
console.error(`[idle-watchdog] Unhandled error: ${message}`);
|
|
132
133
|
try {
|
|
133
134
|
ctx.ui.notify(`Idle watchdog error: ${message}`, "warning");
|
|
@@ -159,7 +160,7 @@ export function startUnitSupervision(sctx: SupervisionContext): void {
|
|
|
159
160
|
);
|
|
160
161
|
await pauseAuto(ctx, pi);
|
|
161
162
|
} catch (err) {
|
|
162
|
-
const message =
|
|
163
|
+
const message = getErrorMessage(err);
|
|
163
164
|
console.error(`[hard-timeout] Unhandled error: ${message}`);
|
|
164
165
|
try {
|
|
165
166
|
ctx.ui.notify(`Hard timeout error: ${message}`, "warning");
|
|
@@ -24,6 +24,8 @@ import { writeVerificationJSON } from "./verification-evidence.js";
|
|
|
24
24
|
import { removePersistedKey } from "./auto-recovery.js";
|
|
25
25
|
import type { AutoSession, PendingVerificationRetry } from "./auto/session.js";
|
|
26
26
|
import { join } from "node:path";
|
|
27
|
+
import { getErrorMessage } from "./error-utils.js";
|
|
28
|
+
import { parseUnitId } from "./unit-id.js";
|
|
27
29
|
|
|
28
30
|
export interface VerificationContext {
|
|
29
31
|
s: AutoSession;
|
|
@@ -57,10 +59,9 @@ export async function runPostUnitVerification(
|
|
|
57
59
|
const prefs = effectivePrefs?.preferences;
|
|
58
60
|
|
|
59
61
|
// Read task plan verify field
|
|
60
|
-
const
|
|
62
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(s.currentUnit.id);
|
|
61
63
|
let taskPlanVerify: string | undefined;
|
|
62
|
-
if (
|
|
63
|
-
const [mid, sid, tid] = parts;
|
|
64
|
+
if (mid && sid && tid) {
|
|
64
65
|
const planFile = resolveSliceFile(s.basePath, mid, sid, "PLAN");
|
|
65
66
|
if (planFile) {
|
|
66
67
|
const planContent = await loadFile(planFile);
|
|
@@ -152,9 +153,8 @@ export async function runPostUnitVerification(
|
|
|
152
153
|
|
|
153
154
|
// Write verification evidence JSON
|
|
154
155
|
const attempt = s.verificationRetryCount.get(s.currentUnit.id) ?? 0;
|
|
155
|
-
if (
|
|
156
|
+
if (mid && sid && tid) {
|
|
156
157
|
try {
|
|
157
|
-
const [mid, sid, tid] = parts;
|
|
158
158
|
const sDir = resolveSlicePath(s.basePath, mid, sid);
|
|
159
159
|
if (sDir) {
|
|
160
160
|
const tasksDir = join(sDir, "tasks");
|
|
@@ -204,7 +204,7 @@ export async function runPostUnitVerification(
|
|
|
204
204
|
try {
|
|
205
205
|
await dispatchNextUnit(ctx, pi);
|
|
206
206
|
} catch (retryDispatchErr) {
|
|
207
|
-
const msg =
|
|
207
|
+
const msg = getErrorMessage(retryDispatchErr);
|
|
208
208
|
ctx.ui.notify(`Verification retry dispatch error: ${msg}`, "error");
|
|
209
209
|
startDispatchGapWatchdog(ctx, pi);
|
|
210
210
|
}
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
nativeBranchDelete,
|
|
39
39
|
nativeBranchExists,
|
|
40
40
|
} from "./native-git-bridge.js";
|
|
41
|
+
import { getErrorMessage } from "./error-utils.js";
|
|
41
42
|
|
|
42
43
|
// ─── Module State ──────────────────────────────────────────────────────────
|
|
43
44
|
|
|
@@ -81,7 +82,7 @@ export function runWorktreePostCreateHook(sourceDir: string, worktreeDir: string
|
|
|
81
82
|
});
|
|
82
83
|
return null;
|
|
83
84
|
} catch (err) {
|
|
84
|
-
const msg =
|
|
85
|
+
const msg = getErrorMessage(err);
|
|
85
86
|
return `Worktree post-create hook failed: ${msg}`;
|
|
86
87
|
}
|
|
87
88
|
}
|
|
@@ -141,7 +142,7 @@ export function createAutoWorktree(basePath: string, milestoneId: string): strin
|
|
|
141
142
|
// Don't store originalBase -- caller can retry or clean up.
|
|
142
143
|
throw new GSDError(
|
|
143
144
|
GSD_IO_ERROR,
|
|
144
|
-
`Auto-worktree created at ${info.path} but chdir failed: ${
|
|
145
|
+
`Auto-worktree created at ${info.path} but chdir failed: ${getErrorMessage(err)}`,
|
|
145
146
|
);
|
|
146
147
|
}
|
|
147
148
|
|
|
@@ -168,7 +169,7 @@ export function teardownAutoWorktree(
|
|
|
168
169
|
} catch (err) {
|
|
169
170
|
throw new GSDError(
|
|
170
171
|
GSD_IO_ERROR,
|
|
171
|
-
`Failed to chdir back to ${originalBasePath} during teardown: ${
|
|
172
|
+
`Failed to chdir back to ${originalBasePath} during teardown: ${getErrorMessage(err)}`,
|
|
172
173
|
);
|
|
173
174
|
}
|
|
174
175
|
|
|
@@ -274,7 +275,7 @@ export function enterAutoWorktree(basePath: string, milestoneId: string): string
|
|
|
274
275
|
} catch (err) {
|
|
275
276
|
throw new GSDError(
|
|
276
277
|
GSD_IO_ERROR,
|
|
277
|
-
`Failed to enter auto-worktree at ${p}: ${
|
|
278
|
+
`Failed to enter auto-worktree at ${p}: ${getErrorMessage(err)}`,
|
|
278
279
|
);
|
|
279
280
|
}
|
|
280
281
|
|
|
@@ -105,6 +105,7 @@ import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.j
|
|
|
105
105
|
import { GSDError, GSD_ARTIFACT_MISSING } from "./errors.js";
|
|
106
106
|
import { join } from "node:path";
|
|
107
107
|
import { sep as pathSep } from "node:path";
|
|
108
|
+
import { parseUnitId } from "./unit-id.js";
|
|
108
109
|
import { readdirSync, readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync, statSync } from "node:fs";
|
|
109
110
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
110
111
|
import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit } from "./native-git-bridge.js";
|
|
@@ -189,6 +190,7 @@ import {
|
|
|
189
190
|
NEW_SESSION_TIMEOUT_MS, DISPATCH_HANG_TIMEOUT_MS,
|
|
190
191
|
} from "./auto/session.js";
|
|
191
192
|
import type { CompletedUnit, CurrentUnit, UnitRouting, StartModel, PendingVerificationRetry } from "./auto/session.js";
|
|
193
|
+
import { getErrorMessage } from "./error-utils.js";
|
|
192
194
|
|
|
193
195
|
// ── ENCAPSULATION INVARIANT ─────────────────────────────────────────────────
|
|
194
196
|
// ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts).
|
|
@@ -428,7 +430,7 @@ function startDispatchGapWatchdog(ctx: ExtensionContext, pi: ExtensionAPI): void
|
|
|
428
430
|
try {
|
|
429
431
|
await dispatchNextUnit(ctx, pi);
|
|
430
432
|
} catch (retryErr) {
|
|
431
|
-
const message =
|
|
433
|
+
const message = getErrorMessage(retryErr);
|
|
432
434
|
await stopAuto(ctx, pi, `Dispatch gap recovery failed: ${message}`);
|
|
433
435
|
return;
|
|
434
436
|
}
|
|
@@ -458,14 +460,14 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
458
460
|
// ── Auto-worktree: exit worktree and reset s.basePath on stop ──
|
|
459
461
|
if (s.currentMilestoneId && isInAutoWorktree(s.basePath)) {
|
|
460
462
|
try {
|
|
461
|
-
try { autoCommitCurrentBranch(s.basePath, "stop", s.currentMilestoneId); } catch (e) { debugLog("stop-auto-commit-failed", { error:
|
|
463
|
+
try { autoCommitCurrentBranch(s.basePath, "stop", s.currentMilestoneId); } catch (e) { debugLog("stop-auto-commit-failed", { error: getErrorMessage(e) }); }
|
|
462
464
|
teardownAutoWorktree(s.originalBasePath, s.currentMilestoneId, { preserveBranch: true });
|
|
463
465
|
s.basePath = s.originalBasePath;
|
|
464
466
|
s.gitService = createGitService(s.basePath);
|
|
465
467
|
ctx?.ui.notify("Exited auto-worktree (branch preserved for resume).", "info");
|
|
466
468
|
} catch (err) {
|
|
467
469
|
ctx?.ui.notify(
|
|
468
|
-
`Auto-worktree teardown failed: ${
|
|
470
|
+
`Auto-worktree teardown failed: ${getErrorMessage(err)}`,
|
|
469
471
|
"warning",
|
|
470
472
|
);
|
|
471
473
|
}
|
|
@@ -476,7 +478,7 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
476
478
|
try {
|
|
477
479
|
const { closeDatabase } = await import("./gsd-db.js");
|
|
478
480
|
closeDatabase();
|
|
479
|
-
} catch (e) { debugLog("db-close-failed", { error:
|
|
481
|
+
} catch (e) { debugLog("db-close-failed", { error: getErrorMessage(e) }); }
|
|
480
482
|
}
|
|
481
483
|
|
|
482
484
|
if (s.originalBasePath) {
|
|
@@ -496,7 +498,7 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
496
498
|
}
|
|
497
499
|
|
|
498
500
|
if (s.basePath) {
|
|
499
|
-
try { await rebuildState(s.basePath); } catch (e) { debugLog("stop-rebuild-state-failed", { error:
|
|
501
|
+
try { await rebuildState(s.basePath); } catch (e) { debugLog("stop-rebuild-state-failed", { error: getErrorMessage(e) }); }
|
|
500
502
|
}
|
|
501
503
|
|
|
502
504
|
if (isDebugEnabled()) {
|
|
@@ -635,7 +637,7 @@ export async function startAuto(
|
|
|
635
637
|
}
|
|
636
638
|
} catch (err) {
|
|
637
639
|
ctx.ui.notify(
|
|
638
|
-
`Auto-worktree re-entry failed: ${
|
|
640
|
+
`Auto-worktree re-entry failed: ${getErrorMessage(err)}. Continuing at current path.`,
|
|
639
641
|
"warning",
|
|
640
642
|
);
|
|
641
643
|
}
|
|
@@ -647,13 +649,13 @@ export async function startAuto(
|
|
|
647
649
|
ctx.ui.setFooter(hideFooter);
|
|
648
650
|
ctx.ui.notify(s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "info");
|
|
649
651
|
restoreHookState(s.basePath);
|
|
650
|
-
try { await rebuildState(s.basePath); } catch (e) { debugLog("resume-rebuild-state-failed", { error:
|
|
652
|
+
try { await rebuildState(s.basePath); } catch (e) { debugLog("resume-rebuild-state-failed", { error: getErrorMessage(e) }); }
|
|
651
653
|
try {
|
|
652
654
|
const report = await runGSDDoctor(s.basePath, { fix: true });
|
|
653
655
|
if (report.fixesApplied.length > 0) {
|
|
654
656
|
ctx.ui.notify(`Resume: applied ${report.fixesApplied.length} fix(es) to state.`, "info");
|
|
655
657
|
}
|
|
656
|
-
} catch (e) { debugLog("resume-doctor-failed", { error:
|
|
658
|
+
} catch (e) { debugLog("resume-doctor-failed", { error: getErrorMessage(e) }); }
|
|
657
659
|
await selfHealRuntimeRecords(s.basePath, ctx, s.completedKeySet);
|
|
658
660
|
invalidateAllCaches();
|
|
659
661
|
|
|
@@ -700,7 +702,7 @@ export async function startAuto(
|
|
|
700
702
|
}
|
|
701
703
|
} catch (err) {
|
|
702
704
|
ctx.ui.notify(
|
|
703
|
-
`Secrets check error: ${
|
|
705
|
+
`Secrets check error: ${getErrorMessage(err)}. Continuing without secrets.`,
|
|
704
706
|
"warning",
|
|
705
707
|
);
|
|
706
708
|
}
|
|
@@ -807,7 +809,7 @@ export async function handleAgentEnd(
|
|
|
807
809
|
try {
|
|
808
810
|
await dispatchNextUnit(ctx, pi);
|
|
809
811
|
} catch (dispatchErr) {
|
|
810
|
-
const message =
|
|
812
|
+
const message = getErrorMessage(dispatchErr);
|
|
811
813
|
ctx.ui.notify(
|
|
812
814
|
`Dispatch error after unit completion: ${message}. Retrying in ${DISPATCH_GAP_TIMEOUT_MS / 1000}s.`,
|
|
813
815
|
"error",
|
|
@@ -838,7 +840,7 @@ export async function handleAgentEnd(
|
|
|
838
840
|
clearDispatchGapWatchdog();
|
|
839
841
|
setImmediate(() => {
|
|
840
842
|
handleAgentEnd(ctx, pi).catch((err) => {
|
|
841
|
-
const msg =
|
|
843
|
+
const msg = getErrorMessage(err);
|
|
842
844
|
ctx.ui.notify(`Deferred agent_end retry failed: ${msg}`, "error");
|
|
843
845
|
pauseAuto(ctx, pi).catch(() => {});
|
|
844
846
|
});
|
|
@@ -1086,7 +1088,7 @@ async function dispatchNextUnit(
|
|
|
1086
1088
|
);
|
|
1087
1089
|
} catch (err) {
|
|
1088
1090
|
ctx.ui.notify(
|
|
1089
|
-
`Report generation failed: ${
|
|
1091
|
+
`Report generation failed: ${getErrorMessage(err)}`,
|
|
1090
1092
|
"warning",
|
|
1091
1093
|
);
|
|
1092
1094
|
}
|
|
@@ -1102,7 +1104,7 @@ async function dispatchNextUnit(
|
|
|
1102
1104
|
atomicWriteSync(file, JSON.stringify([]));
|
|
1103
1105
|
}
|
|
1104
1106
|
s.completedKeySet.clear();
|
|
1105
|
-
} catch (e) { debugLog("completed-keys-reset-failed", { error:
|
|
1107
|
+
} catch (e) { debugLog("completed-keys-reset-failed", { error: getErrorMessage(e) }); }
|
|
1106
1108
|
|
|
1107
1109
|
// ── Worktree lifecycle on milestone transition (#616) ──
|
|
1108
1110
|
if (isInAutoWorktree(s.basePath) && s.originalBasePath && shouldUseWorktreeIsolation()) {
|
|
@@ -1121,7 +1123,7 @@ async function dispatchNextUnit(
|
|
|
1121
1123
|
}
|
|
1122
1124
|
} catch (err) {
|
|
1123
1125
|
ctx.ui.notify(
|
|
1124
|
-
`Milestone merge failed during transition: ${
|
|
1126
|
+
`Milestone merge failed during transition: ${getErrorMessage(err)}`,
|
|
1125
1127
|
"warning",
|
|
1126
1128
|
);
|
|
1127
1129
|
if (s.originalBasePath) {
|
|
@@ -1146,7 +1148,7 @@ async function dispatchNextUnit(
|
|
|
1146
1148
|
ctx.ui.notify(`Created auto-worktree for ${mid} at ${wtPath}`, "info");
|
|
1147
1149
|
} catch (err) {
|
|
1148
1150
|
ctx.ui.notify(
|
|
1149
|
-
`Auto-worktree creation for ${mid} failed: ${
|
|
1151
|
+
`Auto-worktree creation for ${mid} failed: ${getErrorMessage(err)}. Continuing in project root.`,
|
|
1150
1152
|
"warning",
|
|
1151
1153
|
);
|
|
1152
1154
|
}
|
|
@@ -1190,7 +1192,7 @@ async function dispatchNextUnit(
|
|
|
1190
1192
|
}
|
|
1191
1193
|
} catch (err) {
|
|
1192
1194
|
ctx.ui.notify(
|
|
1193
|
-
`Milestone merge failed: ${
|
|
1195
|
+
`Milestone merge failed: ${getErrorMessage(err)}`,
|
|
1194
1196
|
"warning",
|
|
1195
1197
|
);
|
|
1196
1198
|
if (s.originalBasePath) {
|
|
@@ -1216,7 +1218,7 @@ async function dispatchNextUnit(
|
|
|
1216
1218
|
}
|
|
1217
1219
|
} catch (err) {
|
|
1218
1220
|
ctx.ui.notify(
|
|
1219
|
-
`Milestone merge failed (branch mode): ${
|
|
1221
|
+
`Milestone merge failed (branch mode): ${getErrorMessage(err)}`,
|
|
1220
1222
|
"warning",
|
|
1221
1223
|
);
|
|
1222
1224
|
}
|
|
@@ -1276,7 +1278,7 @@ async function dispatchNextUnit(
|
|
|
1276
1278
|
atomicWriteSync(file, JSON.stringify([]));
|
|
1277
1279
|
}
|
|
1278
1280
|
s.completedKeySet.clear();
|
|
1279
|
-
} catch (e) { debugLog("completed-keys-reset-failed", { error:
|
|
1281
|
+
} catch (e) { debugLog("completed-keys-reset-failed", { error: getErrorMessage(e) }); }
|
|
1280
1282
|
// ── Milestone merge ──
|
|
1281
1283
|
if (s.currentMilestoneId && isInAutoWorktree(s.basePath) && s.originalBasePath) {
|
|
1282
1284
|
try {
|
|
@@ -1292,7 +1294,7 @@ async function dispatchNextUnit(
|
|
|
1292
1294
|
);
|
|
1293
1295
|
} catch (err) {
|
|
1294
1296
|
ctx.ui.notify(
|
|
1295
|
-
`Milestone merge failed: ${
|
|
1297
|
+
`Milestone merge failed: ${getErrorMessage(err)}`,
|
|
1296
1298
|
"warning",
|
|
1297
1299
|
);
|
|
1298
1300
|
if (s.originalBasePath) {
|
|
@@ -1318,7 +1320,7 @@ async function dispatchNextUnit(
|
|
|
1318
1320
|
}
|
|
1319
1321
|
} catch (err) {
|
|
1320
1322
|
ctx.ui.notify(
|
|
1321
|
-
`Milestone merge failed (branch mode): ${
|
|
1323
|
+
`Milestone merge failed (branch mode): ${getErrorMessage(err)}`,
|
|
1322
1324
|
"warning",
|
|
1323
1325
|
);
|
|
1324
1326
|
}
|
|
@@ -1417,7 +1419,7 @@ async function dispatchNextUnit(
|
|
|
1417
1419
|
}
|
|
1418
1420
|
} catch (err) {
|
|
1419
1421
|
ctx.ui.notify(
|
|
1420
|
-
`Secrets collection error: ${
|
|
1422
|
+
`Secrets collection error: ${getErrorMessage(err)}. Continuing with next task.`,
|
|
1421
1423
|
"warning",
|
|
1422
1424
|
);
|
|
1423
1425
|
}
|
|
@@ -1628,7 +1630,7 @@ async function dispatchNextUnit(
|
|
|
1628
1630
|
);
|
|
1629
1631
|
result = await Promise.race([sessionPromise, timeoutPromise]);
|
|
1630
1632
|
} catch (sessionErr) {
|
|
1631
|
-
const msg =
|
|
1633
|
+
const msg = getErrorMessage(sessionErr);
|
|
1632
1634
|
ctx.ui.notify(`Session creation failed: ${msg}. Retrying via watchdog.`, "error");
|
|
1633
1635
|
throw new Error(`newSession() failed: ${msg}`);
|
|
1634
1636
|
}
|
|
@@ -1704,7 +1706,7 @@ async function dispatchNextUnit(
|
|
|
1704
1706
|
const { reorderForCaching } = await import("./prompt-ordering.js");
|
|
1705
1707
|
finalPrompt = reorderForCaching(finalPrompt);
|
|
1706
1708
|
} catch (reorderErr) {
|
|
1707
|
-
const msg =
|
|
1709
|
+
const msg = getErrorMessage(reorderErr);
|
|
1708
1710
|
process.stderr.write(`[gsd] prompt reorder failed (non-fatal): ${msg}\n`);
|
|
1709
1711
|
}
|
|
1710
1712
|
|
|
@@ -1747,8 +1749,7 @@ async function dispatchNextUnit(
|
|
|
1747
1749
|
function ensurePreconditions(
|
|
1748
1750
|
unitType: string, unitId: string, base: string, state: GSDState,
|
|
1749
1751
|
): void {
|
|
1750
|
-
const
|
|
1751
|
-
const mid = parts[0]!;
|
|
1752
|
+
const { milestone: mid } = parseUnitId(unitId);
|
|
1752
1753
|
|
|
1753
1754
|
const mDir = resolveMilestonePath(base, mid);
|
|
1754
1755
|
if (!mDir) {
|
|
@@ -1756,8 +1757,8 @@ function ensurePreconditions(
|
|
|
1756
1757
|
mkdirSync(join(newDir, "slices"), { recursive: true });
|
|
1757
1758
|
}
|
|
1758
1759
|
|
|
1759
|
-
|
|
1760
|
-
|
|
1760
|
+
const sid = parseUnitId(unitId).slice;
|
|
1761
|
+
if (sid) {
|
|
1761
1762
|
|
|
1762
1763
|
const mDirResolved = resolveMilestonePath(base, mid);
|
|
1763
1764
|
if (mDirResolved) {
|