gsd-pi 2.32.0 → 2.33.0-dev.bafba33
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-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/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/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 +3 -2
- package/packages/pi-coding-agent/package.json +1 -1
- package/pkg/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/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/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
|
@@ -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 {
|
|
@@ -98,11 +98,24 @@ export function isLockProcessAlive(lock: LockData): boolean {
|
|
|
98
98
|
|
|
99
99
|
/** Format crash info for display or injection into a prompt. */
|
|
100
100
|
export function formatCrashInfo(lock: LockData): string {
|
|
101
|
-
|
|
101
|
+
const lines = [
|
|
102
102
|
`Previous auto-mode session was interrupted.`,
|
|
103
103
|
` Was executing: ${lock.unitType} (${lock.unitId})`,
|
|
104
104
|
` Started at: ${lock.unitStartedAt}`,
|
|
105
105
|
` Units completed before crash: ${lock.completedUnits}`,
|
|
106
106
|
` PID: ${lock.pid}`,
|
|
107
|
-
]
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
// Add recovery guidance based on what was happening when it crashed
|
|
110
|
+
if (lock.unitType === "starting" && lock.unitId === "bootstrap" && lock.completedUnits === 0) {
|
|
111
|
+
lines.push(`No work was lost. Run /gsd auto to restart.`);
|
|
112
|
+
} else if (lock.unitType.includes("research") || lock.unitType.includes("plan")) {
|
|
113
|
+
lines.push(`The ${lock.unitType} unit may be incomplete. Run /gsd auto to re-run it.`);
|
|
114
|
+
} else if (lock.unitType.includes("execute")) {
|
|
115
|
+
lines.push(`Task execution was interrupted. Run /gsd auto to resume — completed work is preserved.`);
|
|
116
|
+
} else if (lock.unitType.includes("complete")) {
|
|
117
|
+
lines.push(`Slice/milestone completion was interrupted. Run /gsd auto to finish.`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return lines.join("\n");
|
|
108
121
|
}
|
|
@@ -5,6 +5,7 @@ import { readdirSync } from "node:fs";
|
|
|
5
5
|
import { resolveMilestoneFile, milestonesDir } from "./paths.js";
|
|
6
6
|
import { parseRoadmapSlices } from "./roadmap-slices.js";
|
|
7
7
|
import { findMilestoneIds } from "./guided-flow.js";
|
|
8
|
+
import { parseUnitId } from "./unit-id.js";
|
|
8
9
|
|
|
9
10
|
const SLICE_DISPATCH_TYPES = new Set([
|
|
10
11
|
"research-slice",
|
|
@@ -39,7 +40,7 @@ function readRoadmapFromDisk(base: string, milestoneId: string): string | null {
|
|
|
39
40
|
export function getPriorSliceCompletionBlocker(base: string, _mainBranch: string, unitType: string, unitId: string): string | null {
|
|
40
41
|
if (!SLICE_DISPATCH_TYPES.has(unitType)) return null;
|
|
41
42
|
|
|
42
|
-
const
|
|
43
|
+
const { milestone: targetMid, slice: targetSid } = parseUnitId(unitId);
|
|
43
44
|
if (!targetMid || !targetSid) return null;
|
|
44
45
|
|
|
45
46
|
// Use findMilestoneIds to respect custom queue order.
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import type { UnitMetrics } from "./metrics.js";
|
|
13
13
|
import { gsdRoot } from "./paths.js";
|
|
14
14
|
import { formatDuration, fileLink } from "../shared/mod.js";
|
|
15
|
+
import { getErrorMessage } from "./error-utils.js";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Open a file in the user's default browser.
|
|
@@ -226,7 +227,7 @@ export async function handleExport(args: string, ctx: ExtensionCommandContext, b
|
|
|
226
227
|
}
|
|
227
228
|
} catch (err) {
|
|
228
229
|
ctx.ui.notify(
|
|
229
|
-
`HTML export failed: ${
|
|
230
|
+
`HTML export failed: ${getErrorMessage(err)}`,
|
|
230
231
|
"error",
|
|
231
232
|
);
|
|
232
233
|
}
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
nativeAddPaths,
|
|
34
34
|
} from "./native-git-bridge.js";
|
|
35
35
|
import { GSDError, GSD_MERGE_CONFLICT, GSD_GIT_ERROR } from "./errors.js";
|
|
36
|
+
import { getErrorMessage } from "./error-utils.js";
|
|
36
37
|
|
|
37
38
|
// ─── Types ─────────────────────────────────────────────────────────────────
|
|
38
39
|
|
|
@@ -281,7 +282,7 @@ export function runGit(basePath: string, args: string[], options: { allowFailure
|
|
|
281
282
|
}).trim();
|
|
282
283
|
} catch (error) {
|
|
283
284
|
if (options.allowFailure) return "";
|
|
284
|
-
const message =
|
|
285
|
+
const message = getErrorMessage(error);
|
|
285
286
|
throw new GSDError(GSD_GIT_ERROR, `git ${args.join(" ")} failed in ${basePath}: ${filterGitSvnNoise(message)}`);
|
|
286
287
|
}
|
|
287
288
|
}
|
|
@@ -533,7 +534,7 @@ export class GitServiceImpl {
|
|
|
533
534
|
execSync(command, { cwd: this.basePath, stdio: "pipe", encoding: "utf-8" });
|
|
534
535
|
return { passed: true, skipped: false, command };
|
|
535
536
|
} catch (err) {
|
|
536
|
-
const msg =
|
|
537
|
+
const msg = getErrorMessage(err);
|
|
537
538
|
return { passed: false, skipped: false, command, error: msg };
|
|
538
539
|
}
|
|
539
540
|
}
|
|
@@ -44,6 +44,7 @@ export {
|
|
|
44
44
|
showQueue, handleQueueReorder, showQueueAdd,
|
|
45
45
|
buildExistingMilestonesContext,
|
|
46
46
|
} from "./guided-flow-queue.js";
|
|
47
|
+
import { getErrorMessage } from "./error-utils.js";
|
|
47
48
|
|
|
48
49
|
// ─── Commit Instruction Helpers ──────────────────────────────────────────────
|
|
49
50
|
|
|
@@ -158,9 +159,9 @@ export function checkAutoStartAfterDiscuss(): boolean {
|
|
|
158
159
|
|
|
159
160
|
pendingAutoStart = null;
|
|
160
161
|
startAuto(ctx, pi, basePath, false, { step }).catch((err) => {
|
|
161
|
-
ctx.ui.notify(`Auto-start failed: ${
|
|
162
|
+
ctx.ui.notify(`Auto-start failed: ${getErrorMessage(err)}`, "error");
|
|
162
163
|
if (process.env.GSD_DEBUG) console.error('[gsd] auto start error:', err);
|
|
163
|
-
debugLog("auto-start-failed", { error:
|
|
164
|
+
debugLog("auto-start-failed", { error: getErrorMessage(err) });
|
|
164
165
|
});
|
|
165
166
|
return true;
|
|
166
167
|
}
|