gsd-pi 2.32.0-dev.d792ba5 → 2.32.0-dev.d9c9e0c
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/resource-loader.js +13 -3
- package/dist/resources/extensions/gsd/auto-dashboard.ts +3 -1
- package/dist/resources/extensions/gsd/auto-dispatch.ts +40 -12
- 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 +108 -182
- 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/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 +6 -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 +3 -2
- 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 +2 -1
- package/dist/resources/extensions/gsd/session-lock.ts +12 -1
- package/dist/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +14 -11
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/loop-regression.test.ts +839 -0
- 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-dispatch.ts +40 -12
- 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 +108 -182
- 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/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 +6 -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 +3 -2
- 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 +2 -1
- package/src/resources/extensions/gsd/session-lock.ts +12 -1
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +14 -11
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/loop-regression.test.ts +839 -0
- 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
|
@@ -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).
|
|
@@ -213,52 +215,7 @@ export function shouldUseWorktreeIsolation(): boolean {
|
|
|
213
215
|
return true; // default: worktree
|
|
214
216
|
}
|
|
215
217
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
/** Pending verification retry — set when gate fails with retries remaining, consumed by dispatchNextUnit */
|
|
219
|
-
|
|
220
|
-
/** Verification retry count per unitId — separate from s.unitDispatchCount which tracks artifact-missing retries */
|
|
221
|
-
|
|
222
|
-
/** Session file path captured at pause — used to synthesize recovery briefing on resume */
|
|
223
|
-
|
|
224
|
-
/** Dashboard tracking */
|
|
225
|
-
|
|
226
|
-
/** Track dynamic routing decision for the current unit (for metrics) */
|
|
227
|
-
|
|
228
|
-
/** Queue of quick-task captures awaiting dispatch after triage resolution */
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Model captured at auto-mode start. Used to prevent model bleed between
|
|
232
|
-
* concurrent GSD instances sharing the same global settings.json (#650).
|
|
233
|
-
* When preferences don't specify a model for a unit type, this ensures
|
|
234
|
-
* the session's original model is re-applied instead of reading from
|
|
235
|
-
* the shared global settings (which another instance may have overwritten).
|
|
236
|
-
*/
|
|
237
|
-
|
|
238
|
-
/** Track current milestone to detect transitions */
|
|
239
|
-
|
|
240
|
-
/** Model the user had selected before auto-mode started */
|
|
241
|
-
|
|
242
|
-
/** Progress-aware timeout supervision */
|
|
243
|
-
|
|
244
|
-
/** Context-pressure continue-here monitor — fires once when context usage >= 70% */
|
|
245
|
-
|
|
246
|
-
/** Dispatch gap watchdog — detects when the state machine stalls between units.
|
|
247
|
-
* After handleAgentEnd completes, if auto-mode is still active but no new unit
|
|
248
|
-
* has been dispatched (sendMessage not called), this timer fires to force a
|
|
249
|
-
* re-evaluation. Covers the case where dispatchNextUnit silently fails or
|
|
250
|
-
* an unhandled error kills the dispatch chain. */
|
|
251
|
-
|
|
252
|
-
/** Prompt character measurement for token savings analysis (R051). */
|
|
253
|
-
|
|
254
|
-
/** SIGTERM handler registered while auto-mode is active — cleared on stop/pause. */
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Tool calls currently being executed — prevents false idle detection during long-running tools.
|
|
258
|
-
* Maps toolCallId → start timestamp (ms) so the idle watchdog can detect tools that have been
|
|
259
|
-
* running suspiciously long (e.g., a Bash command hung because `&` kept stdout open).
|
|
260
|
-
*/
|
|
261
|
-
|
|
218
|
+
// All mutable state lives in AutoSession (auto/session.ts) — see encapsulation invariant above.
|
|
262
219
|
/** Wrapper: register SIGTERM handler and store reference. */
|
|
263
220
|
function registerSigtermHandler(currentBasePath: string): void {
|
|
264
221
|
s.sigtermHandler = _registerSigtermHandler(currentBasePath, s.sigtermHandler);
|
|
@@ -404,6 +361,79 @@ function buildSnapshotOpts(unitType: string, unitId: string): { continueHereFire
|
|
|
404
361
|
};
|
|
405
362
|
}
|
|
406
363
|
|
|
364
|
+
// ─── Extracted Merge Helper ───────────────────────────────────────────────
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Attempt to merge the current milestone branch to main.
|
|
368
|
+
* Handles both worktree and branch isolation modes with a single code path.
|
|
369
|
+
* Returns true if merge succeeded, false on error (non-fatal, logged).
|
|
370
|
+
*
|
|
371
|
+
* Extracted from 4 duplicate merge blocks in dispatchNextUnit to eliminate
|
|
372
|
+
* the bug factory where fixing one copy didn't fix the others (#1308).
|
|
373
|
+
*/
|
|
374
|
+
function tryMergeMilestone(ctx: ExtensionContext, milestoneId: string, mode: "transition" | "complete"): boolean {
|
|
375
|
+
const isolationMode = getIsolationMode();
|
|
376
|
+
|
|
377
|
+
// Worktree merge path
|
|
378
|
+
if (isInAutoWorktree(s.basePath) && s.originalBasePath) {
|
|
379
|
+
try {
|
|
380
|
+
const roadmapPath = resolveMilestoneFile(s.originalBasePath, milestoneId, "ROADMAP");
|
|
381
|
+
if (!roadmapPath) {
|
|
382
|
+
teardownAutoWorktree(s.originalBasePath, milestoneId);
|
|
383
|
+
ctx.ui.notify(`Exited worktree for ${milestoneId} (no roadmap for merge).`, "info");
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
387
|
+
const mergeResult = mergeMilestoneToMain(s.originalBasePath, milestoneId, roadmapContent);
|
|
388
|
+
s.basePath = s.originalBasePath;
|
|
389
|
+
s.gitService = createGitService(s.basePath);
|
|
390
|
+
ctx.ui.notify(
|
|
391
|
+
`Milestone ${milestoneId} merged to main.${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
392
|
+
"info",
|
|
393
|
+
);
|
|
394
|
+
return true;
|
|
395
|
+
} catch (err) {
|
|
396
|
+
ctx.ui.notify(
|
|
397
|
+
`Milestone merge failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
398
|
+
"warning",
|
|
399
|
+
);
|
|
400
|
+
if (s.originalBasePath) {
|
|
401
|
+
s.basePath = s.originalBasePath;
|
|
402
|
+
try { process.chdir(s.basePath); } catch { /* best-effort */ }
|
|
403
|
+
}
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Branch-mode merge path
|
|
409
|
+
if (isolationMode === "branch") {
|
|
410
|
+
try {
|
|
411
|
+
const currentBranch = getCurrentBranch(s.basePath);
|
|
412
|
+
const milestoneBranch = autoWorktreeBranch(milestoneId);
|
|
413
|
+
if (currentBranch === milestoneBranch) {
|
|
414
|
+
const roadmapPath = resolveMilestoneFile(s.basePath, milestoneId, "ROADMAP");
|
|
415
|
+
if (roadmapPath) {
|
|
416
|
+
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
417
|
+
const mergeResult = mergeMilestoneToMain(s.basePath, milestoneId, roadmapContent);
|
|
418
|
+
s.gitService = createGitService(s.basePath);
|
|
419
|
+
ctx.ui.notify(
|
|
420
|
+
`Milestone ${milestoneId} merged (branch mode).${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
421
|
+
"info",
|
|
422
|
+
);
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
} catch (err) {
|
|
427
|
+
ctx.ui.notify(
|
|
428
|
+
`Milestone merge failed (branch mode): ${err instanceof Error ? err.message : String(err)}`,
|
|
429
|
+
"warning",
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
|
|
407
437
|
/**
|
|
408
438
|
* Start a watchdog that fires if no new unit is dispatched within DISPATCH_GAP_TIMEOUT_MS
|
|
409
439
|
* after handleAgentEnd completes. This catches the case where the dispatch chain silently
|
|
@@ -428,7 +458,7 @@ function startDispatchGapWatchdog(ctx: ExtensionContext, pi: ExtensionAPI): void
|
|
|
428
458
|
try {
|
|
429
459
|
await dispatchNextUnit(ctx, pi);
|
|
430
460
|
} catch (retryErr) {
|
|
431
|
-
const message =
|
|
461
|
+
const message = getErrorMessage(retryErr);
|
|
432
462
|
await stopAuto(ctx, pi, `Dispatch gap recovery failed: ${message}`);
|
|
433
463
|
return;
|
|
434
464
|
}
|
|
@@ -458,14 +488,14 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
458
488
|
// ── Auto-worktree: exit worktree and reset s.basePath on stop ──
|
|
459
489
|
if (s.currentMilestoneId && isInAutoWorktree(s.basePath)) {
|
|
460
490
|
try {
|
|
461
|
-
try { autoCommitCurrentBranch(s.basePath, "stop", s.currentMilestoneId); } catch (e) { debugLog("stop-auto-commit-failed", { error:
|
|
491
|
+
try { autoCommitCurrentBranch(s.basePath, "stop", s.currentMilestoneId); } catch (e) { debugLog("stop-auto-commit-failed", { error: getErrorMessage(e) }); }
|
|
462
492
|
teardownAutoWorktree(s.originalBasePath, s.currentMilestoneId, { preserveBranch: true });
|
|
463
493
|
s.basePath = s.originalBasePath;
|
|
464
494
|
s.gitService = createGitService(s.basePath);
|
|
465
495
|
ctx?.ui.notify("Exited auto-worktree (branch preserved for resume).", "info");
|
|
466
496
|
} catch (err) {
|
|
467
497
|
ctx?.ui.notify(
|
|
468
|
-
`Auto-worktree teardown failed: ${
|
|
498
|
+
`Auto-worktree teardown failed: ${getErrorMessage(err)}`,
|
|
469
499
|
"warning",
|
|
470
500
|
);
|
|
471
501
|
}
|
|
@@ -476,7 +506,7 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
476
506
|
try {
|
|
477
507
|
const { closeDatabase } = await import("./gsd-db.js");
|
|
478
508
|
closeDatabase();
|
|
479
|
-
} catch (e) { debugLog("db-close-failed", { error:
|
|
509
|
+
} catch (e) { debugLog("db-close-failed", { error: getErrorMessage(e) }); }
|
|
480
510
|
}
|
|
481
511
|
|
|
482
512
|
if (s.originalBasePath) {
|
|
@@ -496,7 +526,7 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
496
526
|
}
|
|
497
527
|
|
|
498
528
|
if (s.basePath) {
|
|
499
|
-
try { await rebuildState(s.basePath); } catch (e) { debugLog("stop-rebuild-state-failed", { error:
|
|
529
|
+
try { await rebuildState(s.basePath); } catch (e) { debugLog("stop-rebuild-state-failed", { error: getErrorMessage(e) }); }
|
|
500
530
|
}
|
|
501
531
|
|
|
502
532
|
if (isDebugEnabled()) {
|
|
@@ -635,7 +665,7 @@ export async function startAuto(
|
|
|
635
665
|
}
|
|
636
666
|
} catch (err) {
|
|
637
667
|
ctx.ui.notify(
|
|
638
|
-
`Auto-worktree re-entry failed: ${
|
|
668
|
+
`Auto-worktree re-entry failed: ${getErrorMessage(err)}. Continuing at current path.`,
|
|
639
669
|
"warning",
|
|
640
670
|
);
|
|
641
671
|
}
|
|
@@ -647,13 +677,13 @@ export async function startAuto(
|
|
|
647
677
|
ctx.ui.setFooter(hideFooter);
|
|
648
678
|
ctx.ui.notify(s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "info");
|
|
649
679
|
restoreHookState(s.basePath);
|
|
650
|
-
try { await rebuildState(s.basePath); } catch (e) { debugLog("resume-rebuild-state-failed", { error:
|
|
680
|
+
try { await rebuildState(s.basePath); } catch (e) { debugLog("resume-rebuild-state-failed", { error: getErrorMessage(e) }); }
|
|
651
681
|
try {
|
|
652
682
|
const report = await runGSDDoctor(s.basePath, { fix: true });
|
|
653
683
|
if (report.fixesApplied.length > 0) {
|
|
654
684
|
ctx.ui.notify(`Resume: applied ${report.fixesApplied.length} fix(es) to state.`, "info");
|
|
655
685
|
}
|
|
656
|
-
} catch (e) { debugLog("resume-doctor-failed", { error:
|
|
686
|
+
} catch (e) { debugLog("resume-doctor-failed", { error: getErrorMessage(e) }); }
|
|
657
687
|
await selfHealRuntimeRecords(s.basePath, ctx, s.completedKeySet);
|
|
658
688
|
invalidateAllCaches();
|
|
659
689
|
|
|
@@ -700,7 +730,7 @@ export async function startAuto(
|
|
|
700
730
|
}
|
|
701
731
|
} catch (err) {
|
|
702
732
|
ctx.ui.notify(
|
|
703
|
-
`Secrets check error: ${
|
|
733
|
+
`Secrets check error: ${getErrorMessage(err)}. Continuing without secrets.`,
|
|
704
734
|
"warning",
|
|
705
735
|
);
|
|
706
736
|
}
|
|
@@ -807,7 +837,7 @@ export async function handleAgentEnd(
|
|
|
807
837
|
try {
|
|
808
838
|
await dispatchNextUnit(ctx, pi);
|
|
809
839
|
} catch (dispatchErr) {
|
|
810
|
-
const message =
|
|
840
|
+
const message = getErrorMessage(dispatchErr);
|
|
811
841
|
ctx.ui.notify(
|
|
812
842
|
`Dispatch error after unit completion: ${message}. Retrying in ${DISPATCH_GAP_TIMEOUT_MS / 1000}s.`,
|
|
813
843
|
"error",
|
|
@@ -838,7 +868,7 @@ export async function handleAgentEnd(
|
|
|
838
868
|
clearDispatchGapWatchdog();
|
|
839
869
|
setImmediate(() => {
|
|
840
870
|
handleAgentEnd(ctx, pi).catch((err) => {
|
|
841
|
-
const msg =
|
|
871
|
+
const msg = getErrorMessage(err);
|
|
842
872
|
ctx.ui.notify(`Deferred agent_end retry failed: ${msg}`, "error");
|
|
843
873
|
pauseAuto(ctx, pi).catch(() => {});
|
|
844
874
|
});
|
|
@@ -1086,7 +1116,7 @@ async function dispatchNextUnit(
|
|
|
1086
1116
|
);
|
|
1087
1117
|
} catch (err) {
|
|
1088
1118
|
ctx.ui.notify(
|
|
1089
|
-
`Report generation failed: ${
|
|
1119
|
+
`Report generation failed: ${getErrorMessage(err)}`,
|
|
1090
1120
|
"warning",
|
|
1091
1121
|
);
|
|
1092
1122
|
}
|
|
@@ -1102,35 +1132,17 @@ async function dispatchNextUnit(
|
|
|
1102
1132
|
atomicWriteSync(file, JSON.stringify([]));
|
|
1103
1133
|
}
|
|
1104
1134
|
s.completedKeySet.clear();
|
|
1105
|
-
} catch (e) { debugLog("completed-keys-reset-failed", { error:
|
|
1135
|
+
} catch (e) { debugLog("completed-keys-reset-failed", { error: getErrorMessage(e) }); }
|
|
1106
1136
|
|
|
1107
1137
|
// ── Worktree lifecycle on milestone transition (#616) ──
|
|
1108
|
-
if (isInAutoWorktree(s.basePath)
|
|
1109
|
-
|
|
1110
|
-
const roadmapPath = resolveMilestoneFile(s.originalBasePath, s.currentMilestoneId, "ROADMAP");
|
|
1111
|
-
if (roadmapPath) {
|
|
1112
|
-
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
1113
|
-
const mergeResult = mergeMilestoneToMain(s.originalBasePath, s.currentMilestoneId, roadmapContent);
|
|
1114
|
-
ctx.ui.notify(
|
|
1115
|
-
`Milestone ${ s.currentMilestoneId } merged to main.${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
1116
|
-
"info",
|
|
1117
|
-
);
|
|
1118
|
-
} else {
|
|
1119
|
-
teardownAutoWorktree(s.originalBasePath, s.currentMilestoneId);
|
|
1120
|
-
ctx.ui.notify(`Exited worktree for ${ s.currentMilestoneId } (no roadmap for merge).`, "info");
|
|
1121
|
-
}
|
|
1122
|
-
} catch (err) {
|
|
1123
|
-
ctx.ui.notify(
|
|
1124
|
-
`Milestone merge failed during transition: ${err instanceof Error ? err.message : String(err)}`,
|
|
1125
|
-
"warning",
|
|
1126
|
-
);
|
|
1127
|
-
if (s.originalBasePath) {
|
|
1128
|
-
try { process.chdir(s.originalBasePath); } catch { /* best-effort */ }
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1138
|
+
if ((isInAutoWorktree(s.basePath) || getIsolationMode() === "branch") && shouldUseWorktreeIsolation()) {
|
|
1139
|
+
tryMergeMilestone(ctx, s.currentMilestoneId, "transition");
|
|
1131
1140
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1141
|
+
// Reset to project root and re-derive state for the new milestone
|
|
1142
|
+
if (s.originalBasePath) {
|
|
1143
|
+
s.basePath = s.originalBasePath;
|
|
1144
|
+
s.gitService = createGitService(s.basePath);
|
|
1145
|
+
}
|
|
1134
1146
|
invalidateAllCaches();
|
|
1135
1147
|
|
|
1136
1148
|
state = await deriveState(s.basePath);
|
|
@@ -1146,7 +1158,7 @@ async function dispatchNextUnit(
|
|
|
1146
1158
|
ctx.ui.notify(`Created auto-worktree for ${mid} at ${wtPath}`, "info");
|
|
1147
1159
|
} catch (err) {
|
|
1148
1160
|
ctx.ui.notify(
|
|
1149
|
-
`Auto-worktree creation for ${mid} failed: ${
|
|
1161
|
+
`Auto-worktree creation for ${mid} failed: ${getErrorMessage(err)}. Continuing in project root.`,
|
|
1150
1162
|
"warning",
|
|
1151
1163
|
);
|
|
1152
1164
|
}
|
|
@@ -1175,51 +1187,8 @@ async function dispatchNextUnit(
|
|
|
1175
1187
|
const incomplete = (state.registry ?? []).filter(m => m.status !== "complete" && m.status !== "parked");
|
|
1176
1188
|
if (incomplete.length === 0) {
|
|
1177
1189
|
// Genuinely all complete (parked milestones excluded) — merge milestone branch to main before stopping (#962)
|
|
1178
|
-
if (s.currentMilestoneId
|
|
1179
|
-
|
|
1180
|
-
const roadmapPath = resolveMilestoneFile(s.originalBasePath, s.currentMilestoneId, "ROADMAP");
|
|
1181
|
-
if (roadmapPath) {
|
|
1182
|
-
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
1183
|
-
const mergeResult = mergeMilestoneToMain(s.originalBasePath, s.currentMilestoneId, roadmapContent);
|
|
1184
|
-
s.basePath = s.originalBasePath;
|
|
1185
|
-
s.gitService = createGitService(s.basePath);
|
|
1186
|
-
ctx.ui.notify(
|
|
1187
|
-
`Milestone ${ s.currentMilestoneId } merged to main.${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
1188
|
-
"info",
|
|
1189
|
-
);
|
|
1190
|
-
}
|
|
1191
|
-
} catch (err) {
|
|
1192
|
-
ctx.ui.notify(
|
|
1193
|
-
`Milestone merge failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1194
|
-
"warning",
|
|
1195
|
-
);
|
|
1196
|
-
if (s.originalBasePath) {
|
|
1197
|
-
s.basePath = s.originalBasePath;
|
|
1198
|
-
try { process.chdir(s.basePath); } catch { /* best-effort */ }
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
} else if (s.currentMilestoneId && !isInAutoWorktree(s.basePath) && getIsolationMode() === "branch") {
|
|
1202
|
-
try {
|
|
1203
|
-
const currentBranch = getCurrentBranch(s.basePath);
|
|
1204
|
-
const milestoneBranch = autoWorktreeBranch(s.currentMilestoneId);
|
|
1205
|
-
if (currentBranch === milestoneBranch) {
|
|
1206
|
-
const roadmapPath = resolveMilestoneFile(s.basePath, s.currentMilestoneId, "ROADMAP");
|
|
1207
|
-
if (roadmapPath) {
|
|
1208
|
-
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
1209
|
-
const mergeResult = mergeMilestoneToMain(s.basePath, s.currentMilestoneId, roadmapContent);
|
|
1210
|
-
s.gitService = createGitService(s.basePath);
|
|
1211
|
-
ctx.ui.notify(
|
|
1212
|
-
`Milestone ${ s.currentMilestoneId } merged (branch mode).${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
1213
|
-
"info",
|
|
1214
|
-
);
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
} catch (err) {
|
|
1218
|
-
ctx.ui.notify(
|
|
1219
|
-
`Milestone merge failed (branch mode): ${err instanceof Error ? err.message : String(err)}`,
|
|
1220
|
-
"warning",
|
|
1221
|
-
);
|
|
1222
|
-
}
|
|
1190
|
+
if (s.currentMilestoneId) {
|
|
1191
|
+
tryMergeMilestone(ctx, s.currentMilestoneId, "complete");
|
|
1223
1192
|
}
|
|
1224
1193
|
sendDesktopNotification("GSD", "All milestones complete!", "success", "milestone");
|
|
1225
1194
|
await stopAuto(ctx, pi, "All milestones complete");
|
|
@@ -1276,52 +1245,10 @@ async function dispatchNextUnit(
|
|
|
1276
1245
|
atomicWriteSync(file, JSON.stringify([]));
|
|
1277
1246
|
}
|
|
1278
1247
|
s.completedKeySet.clear();
|
|
1279
|
-
} catch (e) { debugLog("completed-keys-reset-failed", { error:
|
|
1248
|
+
} catch (e) { debugLog("completed-keys-reset-failed", { error: getErrorMessage(e) }); }
|
|
1280
1249
|
// ── Milestone merge ──
|
|
1281
|
-
if (s.currentMilestoneId
|
|
1282
|
-
|
|
1283
|
-
const roadmapPath = resolveMilestoneFile(s.originalBasePath, s.currentMilestoneId, "ROADMAP");
|
|
1284
|
-
if (!roadmapPath) throw new GSDError(GSD_ARTIFACT_MISSING, `Cannot resolve ROADMAP file for milestone ${ s.currentMilestoneId }`);
|
|
1285
|
-
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
1286
|
-
const mergeResult = mergeMilestoneToMain(s.originalBasePath, s.currentMilestoneId, roadmapContent);
|
|
1287
|
-
s.basePath = s.originalBasePath;
|
|
1288
|
-
s.gitService = createGitService(s.basePath);
|
|
1289
|
-
ctx.ui.notify(
|
|
1290
|
-
`Milestone ${ s.currentMilestoneId } merged to main.${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
1291
|
-
"info",
|
|
1292
|
-
);
|
|
1293
|
-
} catch (err) {
|
|
1294
|
-
ctx.ui.notify(
|
|
1295
|
-
`Milestone merge failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1296
|
-
"warning",
|
|
1297
|
-
);
|
|
1298
|
-
if (s.originalBasePath) {
|
|
1299
|
-
s.basePath = s.originalBasePath;
|
|
1300
|
-
try { process.chdir(s.basePath); } catch { /* best-effort */ }
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
} else if (s.currentMilestoneId && !isInAutoWorktree(s.basePath) && getIsolationMode() === "branch") {
|
|
1304
|
-
try {
|
|
1305
|
-
const currentBranch = getCurrentBranch(s.basePath);
|
|
1306
|
-
const milestoneBranch = autoWorktreeBranch(s.currentMilestoneId);
|
|
1307
|
-
if (currentBranch === milestoneBranch) {
|
|
1308
|
-
const roadmapPath = resolveMilestoneFile(s.basePath, s.currentMilestoneId, "ROADMAP");
|
|
1309
|
-
if (roadmapPath) {
|
|
1310
|
-
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
1311
|
-
const mergeResult = mergeMilestoneToMain(s.basePath, s.currentMilestoneId, roadmapContent);
|
|
1312
|
-
s.gitService = createGitService(s.basePath);
|
|
1313
|
-
ctx.ui.notify(
|
|
1314
|
-
`Milestone ${ s.currentMilestoneId } merged (branch mode).${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
1315
|
-
"info",
|
|
1316
|
-
);
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
} catch (err) {
|
|
1320
|
-
ctx.ui.notify(
|
|
1321
|
-
`Milestone merge failed (branch mode): ${err instanceof Error ? err.message : String(err)}`,
|
|
1322
|
-
"warning",
|
|
1323
|
-
);
|
|
1324
|
-
}
|
|
1250
|
+
if (s.currentMilestoneId) {
|
|
1251
|
+
tryMergeMilestone(ctx, s.currentMilestoneId, "complete");
|
|
1325
1252
|
}
|
|
1326
1253
|
sendDesktopNotification("GSD", `Milestone ${mid} complete!`, "success", "milestone");
|
|
1327
1254
|
await stopAuto(ctx, pi, `Milestone ${mid} complete`);
|
|
@@ -1417,7 +1344,7 @@ async function dispatchNextUnit(
|
|
|
1417
1344
|
}
|
|
1418
1345
|
} catch (err) {
|
|
1419
1346
|
ctx.ui.notify(
|
|
1420
|
-
`Secrets collection error: ${
|
|
1347
|
+
`Secrets collection error: ${getErrorMessage(err)}. Continuing with next task.`,
|
|
1421
1348
|
"warning",
|
|
1422
1349
|
);
|
|
1423
1350
|
}
|
|
@@ -1628,7 +1555,7 @@ async function dispatchNextUnit(
|
|
|
1628
1555
|
);
|
|
1629
1556
|
result = await Promise.race([sessionPromise, timeoutPromise]);
|
|
1630
1557
|
} catch (sessionErr) {
|
|
1631
|
-
const msg =
|
|
1558
|
+
const msg = getErrorMessage(sessionErr);
|
|
1632
1559
|
ctx.ui.notify(`Session creation failed: ${msg}. Retrying via watchdog.`, "error");
|
|
1633
1560
|
throw new Error(`newSession() failed: ${msg}`);
|
|
1634
1561
|
}
|
|
@@ -1704,7 +1631,7 @@ async function dispatchNextUnit(
|
|
|
1704
1631
|
const { reorderForCaching } = await import("./prompt-ordering.js");
|
|
1705
1632
|
finalPrompt = reorderForCaching(finalPrompt);
|
|
1706
1633
|
} catch (reorderErr) {
|
|
1707
|
-
const msg =
|
|
1634
|
+
const msg = getErrorMessage(reorderErr);
|
|
1708
1635
|
process.stderr.write(`[gsd] prompt reorder failed (non-fatal): ${msg}\n`);
|
|
1709
1636
|
}
|
|
1710
1637
|
|
|
@@ -1747,8 +1674,7 @@ async function dispatchNextUnit(
|
|
|
1747
1674
|
function ensurePreconditions(
|
|
1748
1675
|
unitType: string, unitId: string, base: string, state: GSDState,
|
|
1749
1676
|
): void {
|
|
1750
|
-
const
|
|
1751
|
-
const mid = parts[0]!;
|
|
1677
|
+
const { milestone: mid } = parseUnitId(unitId);
|
|
1752
1678
|
|
|
1753
1679
|
const mDir = resolveMilestonePath(base, mid);
|
|
1754
1680
|
if (!mDir) {
|
|
@@ -1756,8 +1682,8 @@ function ensurePreconditions(
|
|
|
1756
1682
|
mkdirSync(join(newDir, "slices"), { recursive: true });
|
|
1757
1683
|
}
|
|
1758
1684
|
|
|
1759
|
-
|
|
1760
|
-
|
|
1685
|
+
const sid = parseUnitId(unitId).slice;
|
|
1686
|
+
if (sid) {
|
|
1761
1687
|
|
|
1762
1688
|
const mDirResolved = resolveMilestonePath(base, mid);
|
|
1763
1689
|
if (mDirResolved) {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
8
|
+
import { getErrorMessage } from "./error-utils.js";
|
|
8
9
|
|
|
9
10
|
export interface InspectData {
|
|
10
11
|
schemaVersion: number | null;
|
|
@@ -84,7 +85,7 @@ export async function handleInspect(ctx: ExtensionCommandContext): Promise<void>
|
|
|
84
85
|
|
|
85
86
|
ctx.ui.notify(formatInspectOutput(data), "info");
|
|
86
87
|
} catch (err) {
|
|
87
|
-
process.stderr.write(`gsd-db: /gsd inspect failed: ${
|
|
88
|
+
process.stderr.write(`gsd-db: /gsd inspect failed: ${getErrorMessage(err)}\n`);
|
|
88
89
|
ctx.ui.notify("Failed to inspect GSD database. Check stderr for details.", "error");
|
|
89
90
|
}
|
|
90
91
|
}
|
|
@@ -21,6 +21,7 @@ import { loadPrompt } from "./prompt-loader.js";
|
|
|
21
21
|
import { gsdRoot } from "./paths.js";
|
|
22
22
|
import { createGitService, runGit } from "./git-service.js";
|
|
23
23
|
import { isAutoActive, isAutoPaused } from "./auto.js";
|
|
24
|
+
import { getErrorMessage } from "./error-utils.js";
|
|
24
25
|
|
|
25
26
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
26
27
|
|
|
@@ -439,7 +440,7 @@ export async function handleStart(
|
|
|
439
440
|
branchCreated = true;
|
|
440
441
|
}
|
|
441
442
|
} catch (err) {
|
|
442
|
-
const message =
|
|
443
|
+
const message = getErrorMessage(err);
|
|
443
444
|
ctx.ui.notify(
|
|
444
445
|
`Could not create branch ${branchName}: ${message}. Working on current branch.`,
|
|
445
446
|
"warning",
|
|
@@ -6,6 +6,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { gsdRoot } from "./paths.js";
|
|
8
8
|
import { getAdaptiveTierAdjustment } from "./routing-history.js";
|
|
9
|
+
import { parseUnitId } from "./unit-id.js";
|
|
9
10
|
|
|
10
11
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
11
12
|
|
|
@@ -180,15 +181,14 @@ function analyzePlanComplexity(
|
|
|
180
181
|
basePath: string,
|
|
181
182
|
): TaskAnalysis | null {
|
|
182
183
|
// Check if this is a milestone-level plan (more complex) vs single slice
|
|
183
|
-
const
|
|
184
|
-
if (
|
|
184
|
+
const { milestone: mid, slice: sid } = parseUnitId(unitId);
|
|
185
|
+
if (!sid) {
|
|
185
186
|
// Milestone-level planning is always at least standard
|
|
186
187
|
return { tier: "standard", reason: "milestone-level planning" };
|
|
187
188
|
}
|
|
188
189
|
|
|
189
190
|
// For slice planning, try to read the context/research to gauge complexity
|
|
190
191
|
// If research exists and is large, bump to heavy
|
|
191
|
-
const [mid, sid] = parts;
|
|
192
192
|
const researchPath = join(gsdRoot(basePath), mid, "slices", sid, "RESEARCH.md");
|
|
193
193
|
try {
|
|
194
194
|
if (existsSync(researchPath)) {
|
|
@@ -210,10 +210,8 @@ function analyzePlanComplexity(
|
|
|
210
210
|
*/
|
|
211
211
|
function extractTaskMetadata(unitId: string, basePath: string): TaskMetadata {
|
|
212
212
|
const meta: TaskMetadata = {};
|
|
213
|
-
const
|
|
214
|
-
if (
|
|
215
|
-
|
|
216
|
-
const [mid, sid, tid] = parts;
|
|
213
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
214
|
+
if (!mid || !sid || !tid) return meta;
|
|
217
215
|
const taskPlanPath = join(gsdRoot(basePath), mid, "slices", sid, "tasks", `${tid}-PLAN.md`);
|
|
218
216
|
|
|
219
217
|
try {
|