gsd-pi 2.45.0-dev.6b9da3e → 2.45.0-dev.e0ee972
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/help-text.js +1 -1
- package/dist/loader.js +34 -0
- package/dist/resources/extensions/gsd/auto/phases.js +16 -10
- package/dist/resources/extensions/gsd/auto/run-unit.js +6 -3
- package/dist/resources/extensions/gsd/auto-worktree.js +5 -4
- package/dist/resources/extensions/gsd/auto.js +4 -5
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +13 -12
- package/dist/resources/extensions/gsd/db-writer.js +9 -9
- package/dist/resources/extensions/gsd/doctor-checks.js +1 -1
- package/dist/resources/extensions/gsd/doctor.js +2 -2
- package/dist/resources/extensions/gsd/gsd-db.js +5 -1
- package/dist/resources/extensions/gsd/preferences-types.js +2 -2
- package/dist/resources/extensions/gsd/preferences.js +8 -4
- package/dist/resources/extensions/gsd/workflow-logger.js +138 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +4 -3
- package/dist/resources/extensions/gsd/worktree-resolver.js +37 -0
- package/dist/resources/extensions/voice/index.js +11 -16
- package/dist/resources/extensions/voice/linux-ready.js +67 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts +4 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js +10 -5
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js +185 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +239 -10
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +2 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +20 -2
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-commands.test.js +206 -195
- package/packages/pi-coding-agent/dist/core/package-commands.test.js.map +1 -1
- package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +2 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -1
- package/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts +227 -0
- package/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +11 -5
- package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +297 -11
- package/packages/pi-coding-agent/src/core/model-registry.ts +30 -3
- package/packages/pi-coding-agent/src/core/package-commands.test.ts +227 -205
- package/src/resources/extensions/gsd/auto/phases.ts +16 -12
- package/src/resources/extensions/gsd/auto/run-unit.ts +6 -3
- package/src/resources/extensions/gsd/auto-worktree.ts +8 -5
- package/src/resources/extensions/gsd/auto.ts +3 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +13 -12
- package/src/resources/extensions/gsd/db-writer.ts +9 -17
- package/src/resources/extensions/gsd/doctor-checks.ts +1 -1
- package/src/resources/extensions/gsd/doctor.ts +2 -2
- package/src/resources/extensions/gsd/gsd-db.ts +5 -1
- package/src/resources/extensions/gsd/journal.ts +6 -1
- package/src/resources/extensions/gsd/preferences-types.ts +2 -2
- package/src/resources/extensions/gsd/preferences.ts +7 -3
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +42 -3
- package/src/resources/extensions/gsd/tests/preferences.test.ts +7 -9
- package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +275 -0
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +220 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +193 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +4 -9
- package/src/resources/extensions/gsd/worktree-resolver.ts +37 -0
- package/src/resources/extensions/voice/index.ts +11 -21
- package/src/resources/extensions/voice/linux-ready.ts +87 -0
- package/src/resources/extensions/voice/tests/linux-ready.test.ts +124 -0
- /package/dist/web/standalone/.next/static/{rzO54ZboyINyEt7cVM_uS → dFMji9G1LZ-Tv36el9pRT}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{rzO54ZboyINyEt7cVM_uS → dFMji9G1LZ-Tv36el9pRT}/_ssgManifest.js +0 -0
package/dist/help-text.js
CHANGED
|
@@ -32,7 +32,7 @@ const SUBCOMMAND_HELP = {
|
|
|
32
32
|
install: [
|
|
33
33
|
'Usage: gsd install <source> [-l, --local]',
|
|
34
34
|
'',
|
|
35
|
-
'Install a package/extension source and run
|
|
35
|
+
'Install a package/extension source and run post-install validation (dependency checks, setup).',
|
|
36
36
|
'',
|
|
37
37
|
'Examples:',
|
|
38
38
|
' gsd install npm:@foo/bar',
|
package/dist/loader.js
CHANGED
|
@@ -26,6 +26,40 @@ if (firstArg === '--help' || firstArg === '-h') {
|
|
|
26
26
|
printHelp(gsdVersion);
|
|
27
27
|
process.exit(0);
|
|
28
28
|
}
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Runtime dependency checks — fail fast with clear diagnostics before any
|
|
31
|
+
// heavy imports. Reads minimum Node version from the engines field in
|
|
32
|
+
// package.json (already parsed above) and verifies git is available.
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
{
|
|
35
|
+
const MIN_NODE_MAJOR = 22;
|
|
36
|
+
const red = '\x1b[31m';
|
|
37
|
+
const bold = '\x1b[1m';
|
|
38
|
+
const dim = '\x1b[2m';
|
|
39
|
+
const reset = '\x1b[0m';
|
|
40
|
+
// -- Node version --
|
|
41
|
+
const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
|
|
42
|
+
if (nodeMajor < MIN_NODE_MAJOR) {
|
|
43
|
+
process.stderr.write(`\n${red}${bold}Error:${reset} GSD requires Node.js >= ${MIN_NODE_MAJOR}.0.0\n` +
|
|
44
|
+
` You are running Node.js ${process.versions.node}\n\n` +
|
|
45
|
+
`${dim}Install a supported version:${reset}\n` +
|
|
46
|
+
` nvm install ${MIN_NODE_MAJOR} ${dim}# if using nvm${reset}\n` +
|
|
47
|
+
` fnm install ${MIN_NODE_MAJOR} ${dim}# if using fnm${reset}\n` +
|
|
48
|
+
` brew install node@${MIN_NODE_MAJOR} ${dim}# macOS Homebrew${reset}\n\n`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
// -- git --
|
|
52
|
+
try {
|
|
53
|
+
const { execFileSync } = await import('child_process');
|
|
54
|
+
execFileSync('git', ['--version'], { stdio: 'ignore' });
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
process.stderr.write(`\n${red}${bold}Error:${reset} GSD requires git but it was not found on PATH.\n\n` +
|
|
58
|
+
`${dim}Install git:${reset}\n` +
|
|
59
|
+
` https://git-scm.com/downloads\n\n`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
29
63
|
import { agentDir, appRoot } from './app-paths.js';
|
|
30
64
|
import { serializeBundledExtensionPaths } from './bundled-extension-paths.js';
|
|
31
65
|
import { discoverExtensionEntryPaths } from './extension-discovery.js';
|
|
@@ -17,6 +17,7 @@ import { PROJECT_FILES } from "../detection.js";
|
|
|
17
17
|
import { MergeConflictError } from "../git-service.js";
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { existsSync, cpSync } from "node:fs";
|
|
20
|
+
import { logWarning } from "../workflow-logger.js";
|
|
20
21
|
// ─── generateMilestoneReport ──────────────────────────────────────────────────
|
|
21
22
|
/**
|
|
22
23
|
* Generate and write an HTML milestone report snapshot.
|
|
@@ -102,8 +103,8 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
102
103
|
return { action: "break", reason: "health-gate-failed" };
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
|
-
catch {
|
|
106
|
-
|
|
106
|
+
catch (e) {
|
|
107
|
+
logWarning("engine", "Pre-dispatch health gate threw unexpectedly", { error: String(e) });
|
|
107
108
|
}
|
|
108
109
|
// Sync project root artifacts into worktree
|
|
109
110
|
if (s.originalBasePath &&
|
|
@@ -157,7 +158,8 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
157
158
|
await deps.stopAuto(ctx, pi, `Merge conflict on milestone ${s.currentMilestoneId}`);
|
|
158
159
|
return { action: "break", reason: "merge-conflict" };
|
|
159
160
|
}
|
|
160
|
-
// Non-conflict errors — log and continue
|
|
161
|
+
// Non-conflict merge errors — log and continue
|
|
162
|
+
logWarning("engine", "Milestone merge failed with non-conflict error", { milestone: s.currentMilestoneId, error: String(mergeErr) });
|
|
161
163
|
}
|
|
162
164
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
163
165
|
deps.invalidateAllCaches();
|
|
@@ -190,7 +192,9 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
190
192
|
}
|
|
191
193
|
atomicWriteSync(completedKeysPath, JSON.stringify([], null, 2));
|
|
192
194
|
}
|
|
193
|
-
catch {
|
|
195
|
+
catch (e) {
|
|
196
|
+
logWarning("engine", "Failed to archive completed-units on milestone transition", { error: String(e) });
|
|
197
|
+
}
|
|
194
198
|
// Rebuild STATE.md immediately so it reflects the new active milestone.
|
|
195
199
|
// This bypasses the 30-second throttle in the normal rebuild path —
|
|
196
200
|
// milestone transitions are rare and important enough to warrant an
|
|
@@ -198,8 +202,8 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
198
202
|
try {
|
|
199
203
|
await deps.rebuildState(s.basePath);
|
|
200
204
|
}
|
|
201
|
-
catch {
|
|
202
|
-
|
|
205
|
+
catch (e) {
|
|
206
|
+
logWarning("engine", "STATE.md rebuild failed after milestone transition", { error: String(e) });
|
|
203
207
|
}
|
|
204
208
|
}
|
|
205
209
|
if (mid) {
|
|
@@ -644,8 +648,8 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
644
648
|
(requirementsContent?.length ?? 0) +
|
|
645
649
|
(projectContent?.length ?? 0);
|
|
646
650
|
}
|
|
647
|
-
catch {
|
|
648
|
-
|
|
651
|
+
catch (e) {
|
|
652
|
+
logWarning("engine", "Baseline char count measurement failed", { error: String(e) });
|
|
649
653
|
}
|
|
650
654
|
}
|
|
651
655
|
// Cache-optimize prompt section ordering
|
|
@@ -654,7 +658,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
654
658
|
}
|
|
655
659
|
catch (reorderErr) {
|
|
656
660
|
const msg = reorderErr instanceof Error ? reorderErr.message : String(reorderErr);
|
|
657
|
-
|
|
661
|
+
logWarning("engine", "Prompt reorder failed", { error: msg });
|
|
658
662
|
}
|
|
659
663
|
// Select and apply model (with tier escalation on retry — normal units only)
|
|
660
664
|
const modelResult = await deps.selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel, sidecarItem ? undefined : { isRetry, previousTier });
|
|
@@ -789,7 +793,9 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
789
793
|
const keys = s.completedUnits.map((u) => `${u.type}/${u.id}`);
|
|
790
794
|
atomicWriteSync(completedKeysPath, JSON.stringify(keys, null, 2));
|
|
791
795
|
}
|
|
792
|
-
catch {
|
|
796
|
+
catch (e) {
|
|
797
|
+
logWarning("engine", "Failed to flush completed-units to disk", { error: String(e) });
|
|
798
|
+
}
|
|
793
799
|
deps.clearUnitRuntimeRecord(s.basePath, unitType, unitId);
|
|
794
800
|
s.unitDispatchCount.delete(`${unitType}/${unitId}`);
|
|
795
801
|
s.unitRecoveryCount.delete(`${unitType}/${unitId}`);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { NEW_SESSION_TIMEOUT_MS } from "./session.js";
|
|
7
7
|
import { _setCurrentResolve, _setSessionSwitchInFlight } from "./resolve.js";
|
|
8
8
|
import { debugLog } from "../debug-logger.js";
|
|
9
|
+
import { logWarning } from "../workflow-logger.js";
|
|
9
10
|
/**
|
|
10
11
|
* Execute a single unit: create a new session, send the prompt, and await
|
|
11
12
|
* the agent_end promise. Returns a UnitResult describing what happened.
|
|
@@ -66,7 +67,9 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
66
67
|
process.chdir(s.basePath);
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
|
-
catch {
|
|
70
|
+
catch (e) {
|
|
71
|
+
logWarning("engine", "Failed to chdir to basePath before dispatch", { basePath: s.basePath, error: String(e) });
|
|
72
|
+
}
|
|
70
73
|
// ── Send the prompt ──
|
|
71
74
|
debugLog("runUnit", { phase: "send-message", unitType, unitId });
|
|
72
75
|
pi.sendMessage({ customType: "gsd-auto", content: prompt, display: s.verbose }, { triggerTurn: true });
|
|
@@ -90,8 +93,8 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
90
93
|
cmdCtxAny.clearQueue();
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
|
-
catch {
|
|
94
|
-
|
|
96
|
+
catch (e) {
|
|
97
|
+
logWarning("engine", "clearQueue failed after unit completion", { error: String(e) });
|
|
95
98
|
}
|
|
96
99
|
return result;
|
|
97
100
|
}
|
|
@@ -17,6 +17,7 @@ import { createWorktree, removeWorktree, resolveGitDir, worktreePath, } from "./
|
|
|
17
17
|
import { detectWorktreeName, nudgeGitBranchCache, } from "./worktree.js";
|
|
18
18
|
import { MergeConflictError, readIntegrationBranch, RUNTIME_EXCLUSION_PATHS } from "./git-service.js";
|
|
19
19
|
import { debugLog } from "./debug-logger.js";
|
|
20
|
+
import { logWarning } from "./workflow-logger.js";
|
|
20
21
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
21
22
|
import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeWorkingTreeStatus, nativeAddAllWithExclusions, nativeCommit, nativeCheckoutBranch, nativeMergeSquash, nativeConflictFiles, nativeCheckoutTheirs, nativeAddPaths, nativeRmForce, nativeBranchDelete, nativeBranchExists, nativeDiffNumstat, nativeUpdateRef, nativeIsAncestor, } from "./native-git-bridge.js";
|
|
22
23
|
// ─── Module State ──────────────────────────────────────────────────────────
|
|
@@ -615,7 +616,7 @@ export function createAutoWorktree(basePath, milestoneId) {
|
|
|
615
616
|
const hookError = runWorktreePostCreateHook(basePath, info.path);
|
|
616
617
|
if (hookError) {
|
|
617
618
|
// Non-fatal — log but don't prevent worktree usage
|
|
618
|
-
|
|
619
|
+
logWarning("reconcile", hookError, { worktree: info.name });
|
|
619
620
|
}
|
|
620
621
|
const previousCwd = process.cwd();
|
|
621
622
|
try {
|
|
@@ -690,9 +691,9 @@ export function teardownAutoWorktree(originalBasePath, milestoneId, opts = {}) {
|
|
|
690
691
|
// backslashes (#1436), leaving ~1 GB+ orphaned directories.
|
|
691
692
|
const wtDir = worktreePath(originalBasePath, milestoneId);
|
|
692
693
|
if (existsSync(wtDir)) {
|
|
693
|
-
|
|
694
|
-
`
|
|
695
|
-
`
|
|
694
|
+
logWarning("reconcile", `Worktree directory still exists after teardown: ${wtDir}. ` +
|
|
695
|
+
`This is likely an orphaned directory consuming disk space. ` +
|
|
696
|
+
`Remove it manually with: rm -rf "${wtDir.replaceAll("\\", "/")}"`, { worktree: milestoneId });
|
|
696
697
|
// Attempt a direct filesystem removal as a fallback
|
|
697
698
|
try {
|
|
698
699
|
rmSync(wtDir, { recursive: true, force: true });
|
|
@@ -82,11 +82,10 @@ const s = new AutoSession();
|
|
|
82
82
|
const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
|
|
83
83
|
export function shouldUseWorktreeIsolation() {
|
|
84
84
|
const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
85
|
-
if (prefs?.isolation === "
|
|
86
|
-
return
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return true; // default: worktree
|
|
85
|
+
if (prefs?.isolation === "worktree")
|
|
86
|
+
return true;
|
|
87
|
+
// Default is false — worktree isolation requires explicit opt-in
|
|
88
|
+
return false;
|
|
90
89
|
}
|
|
91
90
|
/** Crash recovery prompt — set by startAuto, consumed by the main loop */
|
|
92
91
|
/** Pending verification retry — set when gate fails with retries remaining, consumed by autoLoop */
|
|
@@ -4,6 +4,7 @@ import { findMilestoneIds, nextMilestoneId, claimReservedId, getReservedMileston
|
|
|
4
4
|
import { loadEffectiveGSDPreferences } from "../preferences.js";
|
|
5
5
|
import { ensureDbOpen } from "./dynamic-tools.js";
|
|
6
6
|
import { StringEnum } from "@gsd/pi-ai";
|
|
7
|
+
import { logError } from "../workflow-logger.js";
|
|
7
8
|
/**
|
|
8
9
|
* Register an alias tool that shares the same execute function as its canonical counterpart.
|
|
9
10
|
* The alias description and promptGuidelines direct the LLM to prefer the canonical name.
|
|
@@ -45,7 +46,7 @@ export function registerDbTools(pi) {
|
|
|
45
46
|
}
|
|
46
47
|
catch (err) {
|
|
47
48
|
const msg = err instanceof Error ? err.message : String(err);
|
|
48
|
-
|
|
49
|
+
logError("tool", `gsd_decision_save tool failed: ${msg}`, { tool: "gsd_decision_save", error: String(err) });
|
|
49
50
|
return {
|
|
50
51
|
content: [{ type: "text", text: `Error saving decision: ${msg}` }],
|
|
51
52
|
details: { operation: "save_decision", error: msg },
|
|
@@ -142,7 +143,7 @@ export function registerDbTools(pi) {
|
|
|
142
143
|
}
|
|
143
144
|
catch (err) {
|
|
144
145
|
const msg = err instanceof Error ? err.message : String(err);
|
|
145
|
-
|
|
146
|
+
logError("tool", `gsd_requirement_update tool failed: ${msg}`, { tool: "gsd_requirement_update", error: String(err) });
|
|
146
147
|
return {
|
|
147
148
|
content: [{ type: "text", text: `Error updating requirement: ${msg}` }],
|
|
148
149
|
details: { operation: "update_requirement", id: params.id, error: msg },
|
|
@@ -235,7 +236,7 @@ export function registerDbTools(pi) {
|
|
|
235
236
|
}
|
|
236
237
|
catch (err) {
|
|
237
238
|
const msg = err instanceof Error ? err.message : String(err);
|
|
238
|
-
|
|
239
|
+
logError("tool", `gsd_summary_save tool failed: ${msg}`, { tool: "gsd_summary_save", error: String(err) });
|
|
239
240
|
return {
|
|
240
241
|
content: [{ type: "text", text: `Error saving artifact: ${msg}` }],
|
|
241
242
|
details: { operation: "save_summary", error: msg },
|
|
@@ -394,7 +395,7 @@ export function registerDbTools(pi) {
|
|
|
394
395
|
}
|
|
395
396
|
catch (err) {
|
|
396
397
|
const msg = err instanceof Error ? err.message : String(err);
|
|
397
|
-
|
|
398
|
+
logError("tool", `plan_milestone tool failed: ${msg}`, { tool: "gsd_plan_milestone", error: String(err) });
|
|
398
399
|
return {
|
|
399
400
|
content: [{ type: "text", text: `Error planning milestone: ${msg}` }],
|
|
400
401
|
details: { operation: "plan_milestone", error: msg },
|
|
@@ -483,7 +484,7 @@ export function registerDbTools(pi) {
|
|
|
483
484
|
}
|
|
484
485
|
catch (err) {
|
|
485
486
|
const msg = err instanceof Error ? err.message : String(err);
|
|
486
|
-
|
|
487
|
+
logError("tool", `plan_slice tool failed: ${msg}`, { tool: "gsd_plan_slice", error: String(err) });
|
|
487
488
|
return {
|
|
488
489
|
content: [{ type: "text", text: `Error planning slice: ${msg}` }],
|
|
489
490
|
details: { operation: "plan_slice", error: msg },
|
|
@@ -556,7 +557,7 @@ export function registerDbTools(pi) {
|
|
|
556
557
|
}
|
|
557
558
|
catch (err) {
|
|
558
559
|
const msg = err instanceof Error ? err.message : String(err);
|
|
559
|
-
|
|
560
|
+
logError("tool", `plan_task tool failed: ${msg}`, { tool: "gsd_plan_task", error: String(err) });
|
|
560
561
|
return {
|
|
561
562
|
content: [{ type: "text", text: `Error planning task: ${msg}` }],
|
|
562
563
|
details: { operation: "plan_task", error: msg },
|
|
@@ -622,7 +623,7 @@ export function registerDbTools(pi) {
|
|
|
622
623
|
}
|
|
623
624
|
catch (err) {
|
|
624
625
|
const msg = err instanceof Error ? err.message : String(err);
|
|
625
|
-
|
|
626
|
+
logError("tool", `complete_task tool failed: ${msg}`, { tool: "gsd_task_complete", error: String(err) });
|
|
626
627
|
return {
|
|
627
628
|
content: [{ type: "text", text: `Error completing task: ${msg}` }],
|
|
628
629
|
details: { operation: "complete_task", error: msg },
|
|
@@ -696,7 +697,7 @@ export function registerDbTools(pi) {
|
|
|
696
697
|
}
|
|
697
698
|
catch (err) {
|
|
698
699
|
const msg = err instanceof Error ? err.message : String(err);
|
|
699
|
-
|
|
700
|
+
logError("tool", `complete_slice tool failed: ${msg}`, { tool: "gsd_slice_complete", error: String(err) });
|
|
700
701
|
return {
|
|
701
702
|
content: [{ type: "text", text: `Error completing slice: ${msg}` }],
|
|
702
703
|
details: { operation: "complete_slice", error: msg },
|
|
@@ -788,7 +789,7 @@ export function registerDbTools(pi) {
|
|
|
788
789
|
}
|
|
789
790
|
catch (err) {
|
|
790
791
|
const msg = err instanceof Error ? err.message : String(err);
|
|
791
|
-
|
|
792
|
+
logError("tool", `complete_milestone tool failed: ${msg}`, { tool: "gsd_complete_milestone", error: String(err) });
|
|
792
793
|
return {
|
|
793
794
|
content: [{ type: "text", text: `Error completing milestone: ${msg}` }],
|
|
794
795
|
details: { operation: "complete_milestone", error: msg },
|
|
@@ -854,7 +855,7 @@ export function registerDbTools(pi) {
|
|
|
854
855
|
}
|
|
855
856
|
catch (err) {
|
|
856
857
|
const msg = err instanceof Error ? err.message : String(err);
|
|
857
|
-
|
|
858
|
+
logError("tool", `validate_milestone tool failed: ${msg}`, { tool: "gsd_validate_milestone", error: String(err) });
|
|
858
859
|
return {
|
|
859
860
|
content: [{ type: "text", text: `Error validating milestone: ${msg}` }],
|
|
860
861
|
details: { operation: "validate_milestone", error: msg },
|
|
@@ -919,7 +920,7 @@ export function registerDbTools(pi) {
|
|
|
919
920
|
}
|
|
920
921
|
catch (err) {
|
|
921
922
|
const msg = err instanceof Error ? err.message : String(err);
|
|
922
|
-
|
|
923
|
+
logError("tool", `replan_slice tool failed: ${msg}`, { tool: "gsd_replan_slice", error: String(err) });
|
|
923
924
|
return {
|
|
924
925
|
content: [{ type: "text", text: `Error replanning slice: ${msg}` }],
|
|
925
926
|
details: { operation: "replan_slice", error: msg },
|
|
@@ -992,7 +993,7 @@ export function registerDbTools(pi) {
|
|
|
992
993
|
}
|
|
993
994
|
catch (err) {
|
|
994
995
|
const msg = err instanceof Error ? err.message : String(err);
|
|
995
|
-
|
|
996
|
+
logError("tool", `reassess_roadmap tool failed: ${msg}`, { tool: "gsd_reassess_roadmap", error: String(err) });
|
|
996
997
|
return {
|
|
997
998
|
content: [{ type: "text", text: `Error reassessing roadmap: ${msg}` }],
|
|
998
999
|
details: { operation: "reassess_roadmap", error: msg },
|
|
@@ -12,6 +12,7 @@ import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
|
12
12
|
import { resolveGsdRootFile } from './paths.js';
|
|
13
13
|
import { saveFile } from './files.js';
|
|
14
14
|
import { GSDError, GSD_STALE_STATE, GSD_IO_ERROR } from './errors.js';
|
|
15
|
+
import { logWarning, logError } from './workflow-logger.js';
|
|
15
16
|
import { invalidateStateCache } from './state.js';
|
|
16
17
|
import { clearPathCache } from './paths.js';
|
|
17
18
|
import { clearParseCache } from './files.js';
|
|
@@ -200,7 +201,7 @@ export async function nextDecisionId() {
|
|
|
200
201
|
return `D${String(next).padStart(3, '0')}`;
|
|
201
202
|
}
|
|
202
203
|
catch (err) {
|
|
203
|
-
|
|
204
|
+
logError('manifest', 'nextDecisionId failed', { fn: 'nextDecisionId', error: String(err.message) });
|
|
204
205
|
return 'D001';
|
|
205
206
|
}
|
|
206
207
|
}
|
|
@@ -269,7 +270,7 @@ export async function saveDecisionToDb(fields, basePath) {
|
|
|
269
270
|
await saveFile(filePath, md);
|
|
270
271
|
}
|
|
271
272
|
catch (diskErr) {
|
|
272
|
-
|
|
273
|
+
logError('manifest', 'disk write failed, rolling back DB row', { fn: 'saveDecisionToDb', error: String(diskErr.message) });
|
|
273
274
|
adapter?.prepare('DELETE FROM decisions WHERE id = :id').run({ ':id': id });
|
|
274
275
|
throw diskErr;
|
|
275
276
|
}
|
|
@@ -281,7 +282,7 @@ export async function saveDecisionToDb(fields, basePath) {
|
|
|
281
282
|
return { id };
|
|
282
283
|
}
|
|
283
284
|
catch (err) {
|
|
284
|
-
|
|
285
|
+
logError('manifest', 'saveDecisionToDb failed', { fn: 'saveDecisionToDb', error: String(err.message) });
|
|
285
286
|
throw err;
|
|
286
287
|
}
|
|
287
288
|
}
|
|
@@ -333,7 +334,7 @@ export async function updateRequirementInDb(id, updates, basePath) {
|
|
|
333
334
|
await saveFile(filePath, md);
|
|
334
335
|
}
|
|
335
336
|
catch (diskErr) {
|
|
336
|
-
|
|
337
|
+
logError('manifest', 'disk write failed, reverting DB row', { fn: 'updateRequirementInDb', error: String(diskErr.message) });
|
|
337
338
|
db.upsertRequirement(existing);
|
|
338
339
|
throw diskErr;
|
|
339
340
|
}
|
|
@@ -344,7 +345,7 @@ export async function updateRequirementInDb(id, updates, basePath) {
|
|
|
344
345
|
clearParseCache();
|
|
345
346
|
}
|
|
346
347
|
catch (err) {
|
|
347
|
-
|
|
348
|
+
logError('manifest', 'updateRequirementInDb failed', { fn: 'updateRequirementInDb', error: String(err.message) });
|
|
348
349
|
throw err;
|
|
349
350
|
}
|
|
350
351
|
}
|
|
@@ -371,8 +372,7 @@ export async function saveArtifactToDb(opts, basePath) {
|
|
|
371
372
|
const existingSize = statSync(fullPath).size;
|
|
372
373
|
const newSize = Buffer.byteLength(opts.content, 'utf-8');
|
|
373
374
|
if (existingSize > 0 && newSize < existingSize * 0.5) {
|
|
374
|
-
|
|
375
|
-
`(${existingSize}B) at ${opts.path}. Preserving disk file to prevent data loss.\n`);
|
|
375
|
+
logWarning('manifest', `new content (${newSize}B) is <50% of existing file (${existingSize}B), preserving disk file`, { fn: 'saveArtifactToDb', path: opts.path });
|
|
376
376
|
dbContent = readFileSync(fullPath, 'utf-8');
|
|
377
377
|
skipDiskWrite = true;
|
|
378
378
|
}
|
|
@@ -391,7 +391,7 @@ export async function saveArtifactToDb(opts, basePath) {
|
|
|
391
391
|
await saveFile(fullPath, opts.content);
|
|
392
392
|
}
|
|
393
393
|
catch (diskErr) {
|
|
394
|
-
|
|
394
|
+
logError('manifest', 'disk write failed, rolling back DB row', { fn: 'saveArtifactToDb', error: String(diskErr.message) });
|
|
395
395
|
const rollbackAdapter = db._getAdapter();
|
|
396
396
|
rollbackAdapter?.prepare('DELETE FROM artifacts WHERE path = :path').run({ ':path': opts.path });
|
|
397
397
|
throw diskErr;
|
|
@@ -404,7 +404,7 @@ export async function saveArtifactToDb(opts, basePath) {
|
|
|
404
404
|
clearParseCache();
|
|
405
405
|
}
|
|
406
406
|
catch (err) {
|
|
407
|
-
|
|
407
|
+
logError('manifest', 'saveArtifactToDb failed', { fn: 'saveArtifactToDb', error: String(err.message) });
|
|
408
408
|
throw err;
|
|
409
409
|
}
|
|
410
410
|
}
|
|
@@ -17,7 +17,7 @@ import { getAllWorktreeHealth } from "./worktree-health.js";
|
|
|
17
17
|
import { readAllSessionStatuses, isSessionStale, removeSessionStatus } from "./session-status-io.js";
|
|
18
18
|
import { recoverFailedMigration } from "./migrate-external.js";
|
|
19
19
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
20
|
-
export async function checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode = "
|
|
20
|
+
export async function checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode = "none") {
|
|
21
21
|
// Degrade gracefully if not a git repo
|
|
22
22
|
if (!nativeIsRepo(basePath)) {
|
|
23
23
|
return; // Not a git repo — skip all git health checks
|
|
@@ -332,8 +332,8 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
332
332
|
// Git health checks — timed
|
|
333
333
|
const t0git = Date.now();
|
|
334
334
|
const isolationMode = options?.isolationMode ??
|
|
335
|
-
(prefs?.preferences?.git?.isolation === "
|
|
336
|
-
prefs?.preferences?.git?.isolation === "branch" ? "branch" : "
|
|
335
|
+
(prefs?.preferences?.git?.isolation === "worktree" ? "worktree" :
|
|
336
|
+
prefs?.preferences?.git?.isolation === "branch" ? "branch" : "none");
|
|
337
337
|
await checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode);
|
|
338
338
|
const gitMs = Date.now() - t0git;
|
|
339
339
|
// Runtime health checks — timed
|
|
@@ -56,7 +56,11 @@ function loadProvider() {
|
|
|
56
56
|
catch {
|
|
57
57
|
// unavailable
|
|
58
58
|
}
|
|
59
|
-
process.
|
|
59
|
+
const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
|
|
60
|
+
const versionHint = nodeMajor < 22
|
|
61
|
+
? ` GSD requires Node >= 22.0.0 (current: v${process.versions.node}). Upgrade Node to fix this.`
|
|
62
|
+
: "";
|
|
63
|
+
process.stderr.write(`gsd-db: No SQLite provider available (tried node:sqlite, better-sqlite3).${versionHint}\n`);
|
|
60
64
|
}
|
|
61
65
|
function normalizeRow(row) {
|
|
62
66
|
if (row == null)
|
|
@@ -13,7 +13,7 @@ export const MODE_DEFAULTS = {
|
|
|
13
13
|
push_branches: false,
|
|
14
14
|
pre_merge_check: false,
|
|
15
15
|
merge_strategy: "squash",
|
|
16
|
-
isolation: "
|
|
16
|
+
isolation: "none",
|
|
17
17
|
},
|
|
18
18
|
unique_milestone_ids: false,
|
|
19
19
|
},
|
|
@@ -23,7 +23,7 @@ export const MODE_DEFAULTS = {
|
|
|
23
23
|
push_branches: true,
|
|
24
24
|
pre_merge_check: true,
|
|
25
25
|
merge_strategy: "squash",
|
|
26
|
-
isolation: "
|
|
26
|
+
isolation: "none",
|
|
27
27
|
},
|
|
28
28
|
unique_milestone_ids: true,
|
|
29
29
|
},
|
|
@@ -393,15 +393,19 @@ export function resolvePreDispatchHooks() {
|
|
|
393
393
|
// ─── Isolation & Parallel ─────────────────────────────────────────────────────
|
|
394
394
|
/**
|
|
395
395
|
* Resolve the effective git isolation mode from preferences.
|
|
396
|
-
* Returns "
|
|
396
|
+
* Returns "none" (default), "worktree", or "branch".
|
|
397
|
+
*
|
|
398
|
+
* Default is "none" so GSD works out of the box without preferences.md.
|
|
399
|
+
* Worktree isolation requires explicit opt-in because it depends on git
|
|
400
|
+
* branch infrastructure that must be set up before use.
|
|
397
401
|
*/
|
|
398
402
|
export function getIsolationMode() {
|
|
399
403
|
const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
400
|
-
if (prefs?.isolation === "
|
|
401
|
-
return "
|
|
404
|
+
if (prefs?.isolation === "worktree")
|
|
405
|
+
return "worktree";
|
|
402
406
|
if (prefs?.isolation === "branch")
|
|
403
407
|
return "branch";
|
|
404
|
-
return "
|
|
408
|
+
return "none"; // default — no isolation, work on current branch
|
|
405
409
|
}
|
|
406
410
|
export function resolveParallelConfig(prefs) {
|
|
407
411
|
return {
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// GSD Extension — Workflow Logger
|
|
2
|
+
// Centralized warning/error accumulator for the workflow engine pipeline.
|
|
3
|
+
// Captures structured entries that the auto-loop can drain after each unit
|
|
4
|
+
// to surface root causes for stuck loops, silent degradation, and blocked writes.
|
|
5
|
+
//
|
|
6
|
+
// Stderr policy: every logWarning/logError call writes immediately to stderr
|
|
7
|
+
// for terminal visibility. This is intentional — unlike debug-logger (which is
|
|
8
|
+
// opt-in and zero-overhead when disabled), workflow-logger covers operational
|
|
9
|
+
// warnings/errors that should always be visible. There is no disable flag.
|
|
10
|
+
//
|
|
11
|
+
// Singleton safety: _buffer is module-level and shared across all calls within
|
|
12
|
+
// a process. The auto-loop must call _resetLogs() (or drainAndSummarize()) at
|
|
13
|
+
// the start of each unit to prevent log bleed between units running in the same
|
|
14
|
+
// Node process.
|
|
15
|
+
// ─── Buffer ─────────────────────────────────────────────────────────────
|
|
16
|
+
const MAX_BUFFER = 100;
|
|
17
|
+
let _buffer = [];
|
|
18
|
+
// ─── Public API ─────────────────────────────────────────────────────────
|
|
19
|
+
/**
|
|
20
|
+
* Record a warning. Also writes to stderr for terminal visibility.
|
|
21
|
+
*/
|
|
22
|
+
export function logWarning(component, message, context) {
|
|
23
|
+
_push("warn", component, message, context);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Record an error. Also writes to stderr for terminal visibility.
|
|
27
|
+
*/
|
|
28
|
+
export function logError(component, message, context) {
|
|
29
|
+
_push("error", component, message, context);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Drain all accumulated entries and clear the buffer.
|
|
33
|
+
* Returns entries oldest-first.
|
|
34
|
+
*
|
|
35
|
+
* WARNING: Call summarizeLogs() or drainAndSummarize() BEFORE calling this
|
|
36
|
+
* if you need a summary — drainLogs() clears the buffer immediately.
|
|
37
|
+
*/
|
|
38
|
+
export function drainLogs() {
|
|
39
|
+
const entries = _buffer;
|
|
40
|
+
_buffer = [];
|
|
41
|
+
return entries;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Atomically summarize then drain — the safe way to consume logs.
|
|
45
|
+
* Use this in the auto-loop instead of calling summarizeLogs() + drainLogs()
|
|
46
|
+
* separately to avoid the ordering footgun.
|
|
47
|
+
*/
|
|
48
|
+
export function drainAndSummarize() {
|
|
49
|
+
const summary = summarizeLogs();
|
|
50
|
+
const logs = drainLogs();
|
|
51
|
+
return { logs, summary };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Peek at current entries without clearing.
|
|
55
|
+
*/
|
|
56
|
+
export function peekLogs() {
|
|
57
|
+
return _buffer;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Returns true if the buffer contains any error-severity entries.
|
|
61
|
+
*/
|
|
62
|
+
export function hasErrors() {
|
|
63
|
+
return _buffer.some((e) => e.severity === "error");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Returns true if the buffer contains any warn-severity entries.
|
|
67
|
+
* Use hasAnyIssues() if you want to check for either severity.
|
|
68
|
+
*/
|
|
69
|
+
export function hasWarnings() {
|
|
70
|
+
return _buffer.some((e) => e.severity === "warn");
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Returns true if the buffer contains any entries (warn or error).
|
|
74
|
+
*/
|
|
75
|
+
export function hasAnyIssues() {
|
|
76
|
+
return _buffer.length > 0;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get a one-line summary of accumulated issues for stuck detection messages.
|
|
80
|
+
* Returns null if no entries.
|
|
81
|
+
*
|
|
82
|
+
* Must be called BEFORE drainLogs() — use drainAndSummarize() for safe ordering.
|
|
83
|
+
*/
|
|
84
|
+
export function summarizeLogs() {
|
|
85
|
+
if (_buffer.length === 0)
|
|
86
|
+
return null;
|
|
87
|
+
const errors = _buffer.filter((e) => e.severity === "error");
|
|
88
|
+
const warns = _buffer.filter((e) => e.severity === "warn");
|
|
89
|
+
const parts = [];
|
|
90
|
+
if (errors.length > 0) {
|
|
91
|
+
parts.push(`${errors.length} error(s): ${errors.map((e) => e.message).join("; ")}`);
|
|
92
|
+
}
|
|
93
|
+
if (warns.length > 0) {
|
|
94
|
+
parts.push(`${warns.length} warning(s): ${warns.map((e) => e.message).join("; ")}`);
|
|
95
|
+
}
|
|
96
|
+
return parts.join(" | ");
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Format entries for display (used by auto-loop post-unit notification).
|
|
100
|
+
* Note: context fields are not included in the formatted output.
|
|
101
|
+
*/
|
|
102
|
+
export function formatForNotification(entries) {
|
|
103
|
+
if (entries.length === 0)
|
|
104
|
+
return "";
|
|
105
|
+
if (entries.length === 1) {
|
|
106
|
+
const e = entries[0];
|
|
107
|
+
return `[${e.component}] ${e.message}`;
|
|
108
|
+
}
|
|
109
|
+
return entries
|
|
110
|
+
.map((e) => `[${e.component}] ${e.message}`)
|
|
111
|
+
.join("\n");
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Reset buffer. Call at the start of each auto-loop unit to prevent log bleed
|
|
115
|
+
* between units running in the same process. Also used in tests via _resetLogs().
|
|
116
|
+
*/
|
|
117
|
+
export function _resetLogs() {
|
|
118
|
+
_buffer = [];
|
|
119
|
+
}
|
|
120
|
+
// ─── Internal ───────────────────────────────────────────────────────────
|
|
121
|
+
function _push(severity, component, message, context) {
|
|
122
|
+
const entry = {
|
|
123
|
+
ts: new Date().toISOString(),
|
|
124
|
+
severity,
|
|
125
|
+
component,
|
|
126
|
+
message,
|
|
127
|
+
...(context ? { context } : {}),
|
|
128
|
+
};
|
|
129
|
+
// Always forward to stderr so terminal watchers see it (see module header for policy)
|
|
130
|
+
const prefix = severity === "error" ? "ERROR" : "WARN";
|
|
131
|
+
const ctxStr = context ? ` ${JSON.stringify(context)}` : "";
|
|
132
|
+
process.stderr.write(`[gsd:${component}] ${prefix}: ${message}${ctxStr}\n`);
|
|
133
|
+
// Buffer for auto-loop to drain
|
|
134
|
+
_buffer.push(entry);
|
|
135
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
136
|
+
_buffer.shift();
|
|
137
|
+
}
|
|
138
|
+
}
|