gsd-pi 2.45.0-dev.6b9da3e → 2.45.0-dev.fdcf73c

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.
Files changed (109) hide show
  1. package/dist/help-text.js +1 -1
  2. package/dist/loader.js +34 -0
  3. package/dist/resources/extensions/gsd/auto/phases.js +16 -10
  4. package/dist/resources/extensions/gsd/auto/run-unit.js +6 -3
  5. package/dist/resources/extensions/gsd/auto-worktree.js +5 -4
  6. package/dist/resources/extensions/gsd/auto.js +4 -5
  7. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +15 -12
  8. package/dist/resources/extensions/gsd/db-writer.js +9 -9
  9. package/dist/resources/extensions/gsd/doctor-checks.js +1 -1
  10. package/dist/resources/extensions/gsd/doctor.js +2 -2
  11. package/dist/resources/extensions/gsd/gsd-db.js +5 -1
  12. package/dist/resources/extensions/gsd/preferences-types.js +2 -2
  13. package/dist/resources/extensions/gsd/preferences.js +8 -4
  14. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +20 -7
  15. package/dist/resources/extensions/gsd/tools/complete-milestone.js +4 -0
  16. package/dist/resources/extensions/gsd/workflow-logger.js +138 -0
  17. package/dist/resources/extensions/gsd/worktree-manager.js +4 -3
  18. package/dist/resources/extensions/gsd/worktree-resolver.js +37 -0
  19. package/dist/resources/extensions/voice/index.js +11 -16
  20. package/dist/resources/extensions/voice/linux-ready.js +67 -0
  21. package/dist/web/standalone/.next/BUILD_ID +1 -1
  22. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  23. package/dist/web/standalone/.next/build-manifest.json +2 -2
  24. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  25. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  26. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/index.html +1 -1
  42. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  49. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  50. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  51. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  52. package/package.json +2 -1
  53. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
  54. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +2 -0
  55. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
  56. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
  57. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  58. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  59. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts +4 -0
  60. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts.map +1 -1
  61. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js +10 -5
  62. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js.map +1 -1
  63. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts +2 -0
  64. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts.map +1 -0
  65. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js +185 -0
  66. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js.map +1 -0
  67. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +239 -10
  68. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  69. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +2 -1
  70. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  71. package/packages/pi-coding-agent/dist/core/model-registry.js +20 -2
  72. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  73. package/packages/pi-coding-agent/dist/core/package-commands.test.js +206 -195
  74. package/packages/pi-coding-agent/dist/core/package-commands.test.js.map +1 -1
  75. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +2 -0
  76. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -1
  77. package/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts +227 -0
  78. package/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +11 -5
  79. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +297 -11
  80. package/packages/pi-coding-agent/src/core/model-registry.ts +30 -3
  81. package/packages/pi-coding-agent/src/core/package-commands.test.ts +227 -205
  82. package/src/resources/extensions/gsd/auto/phases.ts +16 -12
  83. package/src/resources/extensions/gsd/auto/run-unit.ts +6 -3
  84. package/src/resources/extensions/gsd/auto-worktree.ts +8 -5
  85. package/src/resources/extensions/gsd/auto.ts +3 -3
  86. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +15 -12
  87. package/src/resources/extensions/gsd/db-writer.ts +9 -17
  88. package/src/resources/extensions/gsd/doctor-checks.ts +1 -1
  89. package/src/resources/extensions/gsd/doctor.ts +2 -2
  90. package/src/resources/extensions/gsd/gsd-db.ts +5 -1
  91. package/src/resources/extensions/gsd/journal.ts +6 -1
  92. package/src/resources/extensions/gsd/preferences-types.ts +2 -2
  93. package/src/resources/extensions/gsd/preferences.ts +7 -3
  94. package/src/resources/extensions/gsd/prompts/complete-milestone.md +20 -7
  95. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +96 -0
  96. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -1
  97. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +42 -3
  98. package/src/resources/extensions/gsd/tests/preferences.test.ts +7 -9
  99. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +275 -0
  100. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +220 -0
  101. package/src/resources/extensions/gsd/tools/complete-milestone.ts +6 -0
  102. package/src/resources/extensions/gsd/workflow-logger.ts +193 -0
  103. package/src/resources/extensions/gsd/worktree-manager.ts +4 -9
  104. package/src/resources/extensions/gsd/worktree-resolver.ts +37 -0
  105. package/src/resources/extensions/voice/index.ts +11 -21
  106. package/src/resources/extensions/voice/linux-ready.ts +87 -0
  107. package/src/resources/extensions/voice/tests/linux-ready.test.ts +124 -0
  108. /package/dist/web/standalone/.next/static/{rzO54ZboyINyEt7cVM_uS → zWYDSwB-terOjfhmWzqk1}/_buildManifest.js +0 -0
  109. /package/dist/web/standalone/.next/static/{rzO54ZboyINyEt7cVM_uS → zWYDSwB-terOjfhmWzqk1}/_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 declared lifecycle hooks.',
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
- // Non-fatal
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 { /* non-fatal */ }
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
- // Non-fatal — STATE.md will be rebuilt on the next regular cycle
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
- // Non-fatal
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
- process.stderr.write(`[gsd] prompt reorder failed (non-fatal): ${msg}\n`);
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 { /* non-fatal: disk flush failure */ }
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 { /* non-fatal — chdir may fail if dir was removed */ }
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
- // Non-fatal clearQueue may not be available in all contexts
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
- console.error(`[GSD] ${hookError}`);
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
- console.error(`[GSD] WARNING: Worktree directory still exists after teardown: ${wtDir}\n` +
694
- ` This is likely an orphaned directory consuming disk space.\n` +
695
- ` Remove it manually with: rm -rf "${wtDir.replaceAll("\\", "/")}"`);
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 === "none")
86
- return false;
87
- if (prefs?.isolation === "branch")
88
- return false;
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
- process.stderr.write(`gsd-db: gsd_decision_save tool failed: ${msg}\n`);
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
- process.stderr.write(`gsd-db: gsd_requirement_update tool failed: ${msg}\n`);
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
- process.stderr.write(`gsd-db: gsd_summary_save tool failed: ${msg}\n`);
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
- process.stderr.write(`gsd-db: plan_milestone tool failed: ${msg}\n`);
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
- process.stderr.write(`gsd-db: plan_slice tool failed: ${msg}\n`);
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
- process.stderr.write(`gsd-db: plan_task tool failed: ${msg}\n`);
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
- process.stderr.write(`gsd-db: complete_task tool failed: ${msg}\n`);
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
- process.stderr.write(`gsd-db: complete_slice tool failed: ${msg}\n`);
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
- process.stderr.write(`gsd-db: complete_milestone tool failed: ${msg}\n`);
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 },
@@ -804,6 +805,7 @@ export function registerDbTools(pi) {
804
805
  promptGuidelines: [
805
806
  "Use gsd_complete_milestone when all slices in a milestone are finished and the milestone needs to be recorded.",
806
807
  "All slices in the milestone must have status 'complete' — the handler validates this before proceeding.",
808
+ "verificationPassed must be explicitly set to true — the handler rejects completion if verification did not pass.",
807
809
  "On success, returns summaryPath where the MILESTONE-SUMMARY.md was written.",
808
810
  ],
809
811
  parameters: Type.Object({
@@ -819,6 +821,7 @@ export function registerDbTools(pi) {
819
821
  lessonsLearned: Type.Array(Type.String(), { description: "Lessons learned during the milestone" }),
820
822
  followUps: Type.Optional(Type.String({ description: "Follow-up items for future milestones" })),
821
823
  deviations: Type.Optional(Type.String({ description: "Deviations from the original plan" })),
824
+ verificationPassed: Type.Boolean({ description: "Must be true — confirms that code change verification, success criteria, and definition of done checks all passed before completion" }),
822
825
  }),
823
826
  execute: milestoneCompleteExecute,
824
827
  };
@@ -854,7 +857,7 @@ export function registerDbTools(pi) {
854
857
  }
855
858
  catch (err) {
856
859
  const msg = err instanceof Error ? err.message : String(err);
857
- process.stderr.write(`gsd-db: validate_milestone tool failed: ${msg}\n`);
860
+ logError("tool", `validate_milestone tool failed: ${msg}`, { tool: "gsd_validate_milestone", error: String(err) });
858
861
  return {
859
862
  content: [{ type: "text", text: `Error validating milestone: ${msg}` }],
860
863
  details: { operation: "validate_milestone", error: msg },
@@ -919,7 +922,7 @@ export function registerDbTools(pi) {
919
922
  }
920
923
  catch (err) {
921
924
  const msg = err instanceof Error ? err.message : String(err);
922
- process.stderr.write(`gsd-db: replan_slice tool failed: ${msg}\n`);
925
+ logError("tool", `replan_slice tool failed: ${msg}`, { tool: "gsd_replan_slice", error: String(err) });
923
926
  return {
924
927
  content: [{ type: "text", text: `Error replanning slice: ${msg}` }],
925
928
  details: { operation: "replan_slice", error: msg },
@@ -992,7 +995,7 @@ export function registerDbTools(pi) {
992
995
  }
993
996
  catch (err) {
994
997
  const msg = err instanceof Error ? err.message : String(err);
995
- process.stderr.write(`gsd-db: reassess_roadmap tool failed: ${msg}\n`);
998
+ logError("tool", `reassess_roadmap tool failed: ${msg}`, { tool: "gsd_reassess_roadmap", error: String(err) });
996
999
  return {
997
1000
  content: [{ type: "text", text: `Error reassessing roadmap: ${msg}` }],
998
1001
  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
- process.stderr.write(`gsd-db: nextDecisionId failed: ${err.message}\n`);
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
- process.stderr.write(`gsd-db: saveDecisionToDb — disk write failed, rolling back DB row: ${diskErr.message}\n`);
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
- process.stderr.write(`gsd-db: saveDecisionToDb failed: ${err.message}\n`);
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
- process.stderr.write(`gsd-db: updateRequirementInDb — disk write failed, reverting DB row: ${diskErr.message}\n`);
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
- process.stderr.write(`gsd-db: updateRequirementInDb failed: ${err.message}\n`);
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
- process.stderr.write(`gsd-db: saveArtifactToDb — new content (${newSize}B) is <50% of existing file ` +
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
- process.stderr.write(`gsd-db: saveArtifactToDb — disk write failed, rolling back DB row: ${diskErr.message}\n`);
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
- process.stderr.write(`gsd-db: saveArtifactToDb failed: ${err.message}\n`);
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 = "worktree") {
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 === "none" ? "none" :
336
- prefs?.preferences?.git?.isolation === "branch" ? "branch" : "worktree");
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.stderr.write("gsd-db: No SQLite provider available (tried node:sqlite, better-sqlite3)\n");
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: "worktree",
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: "worktree",
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 "worktree" (default), "branch", or "none".
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 === "none")
401
- return "none";
404
+ if (prefs?.isolation === "worktree")
405
+ return "worktree";
402
406
  if (prefs?.isolation === "branch")
403
407
  return "branch";
404
- return "worktree"; // default
408
+ return "none"; // default — no isolation, work on current branch
405
409
  }
406
410
  export function resolveParallelConfig(prefs) {
407
411
  return {
@@ -17,18 +17,31 @@ All relevant context has been preloaded below — the roadmap, all slice summari
17
17
  Then:
18
18
  1. Use the **Milestone Summary** output template from the inlined context above
19
19
  2. {{skillActivation}}
20
- 3. **Verify code changes exist.** Run `git diff --stat HEAD $(git merge-base HEAD main) -- ':!.gsd/'` (or the equivalent for the integration branch). If no non-`.gsd/` files appear in the diff, the milestone produced only planning artifacts and no actual code. In that case, do NOT mark the milestone as passing verification — document the gap clearly in the summary and state that implementation is missing.
21
- 4. Verify each **success criterion** from the milestone definition in `{{roadmapPath}}`. For each criterion, confirm it was met with specific evidence from slice summaries, test results, or observable behavior. List any criterion that was NOT met.
22
- 5. Verify the milestone's **definition of done** — all slices are `[x]`, all slice summaries exist, and any cross-slice integration points work correctly.
20
+ 3. **Verify code changes exist.** Run `git diff --stat HEAD $(git merge-base HEAD main) -- ':!.gsd/'` (or the equivalent for the integration branch). If no non-`.gsd/` files appear in the diff, the milestone produced only planning artifacts and no actual code. Record this as a **verification failure**.
21
+ 4. Verify each **success criterion** from the milestone definition in `{{roadmapPath}}`. For each criterion, confirm it was met with specific evidence from slice summaries, test results, or observable behavior. Record any criterion that was NOT met as a **verification failure**.
22
+ 5. Verify the milestone's **definition of done** — all slices are `[x]`, all slice summaries exist, and any cross-slice integration points work correctly. Record any unmet items as a **verification failure**.
23
23
  6. Validate **requirement status transitions**. For each requirement that changed status during this milestone, confirm the transition is supported by evidence. Requirements can move between Active, Validated, Deferred, Blocked, or Out of Scope — but only with proof.
24
- 7. **Persist completion through `gsd_complete_milestone`.** Call it with: `milestoneId`, `title`, `oneLiner`, `narrative`, `successCriteriaResults`, `definitionOfDoneResults`, `requirementOutcomes`, `keyDecisions`, `keyFiles`, `lessonsLearned`, `followUps`, `deviations`. The tool updates the milestone status in the DB, renders `{{milestoneSummaryPath}}`, and validates all slices are complete before proceeding.
24
+
25
+ ### Verification Gate — STOP if verification failed
26
+
27
+ **If ANY verification failure was recorded in steps 3, 4, or 5, you MUST follow the failure path below. Do NOT proceed to step 7.**
28
+
29
+ **Failure path** (verification failed):
30
+ - Do NOT call `gsd_complete_milestone` — the milestone must not be marked as complete.
31
+ - Do NOT update `.gsd/PROJECT.md` to reflect completion.
32
+ - Do NOT update `.gsd/REQUIREMENTS.md` to mark requirements as validated.
33
+ - Write a clear summary of what failed and why to help the next attempt.
34
+ - Say: "Milestone {{milestoneId}} verification FAILED — not complete." and stop.
35
+
36
+ **Success path** (all verifications passed — continue with steps 7–11):
37
+
38
+ 7. **Persist completion through `gsd_complete_milestone`.** Call it with: `milestoneId`, `title`, `oneLiner`, `narrative`, `successCriteriaResults`, `definitionOfDoneResults`, `requirementOutcomes`, `keyDecisions`, `keyFiles`, `lessonsLearned`, `followUps`, `deviations`, `verificationPassed: true`. The tool updates the milestone status in the DB, renders `{{milestoneSummaryPath}}`, and validates all slices are complete before proceeding.
25
39
  8. Update `.gsd/REQUIREMENTS.md` if any requirement status transitions were validated in step 6.
26
40
  9. Update `.gsd/PROJECT.md` to reflect milestone completion and current project state.
27
41
  10. Review all slice summaries for cross-cutting lessons, patterns, or gotchas that emerged during this milestone. Append any non-obvious, reusable insights to `.gsd/KNOWLEDGE.md`.
28
42
  11. Do not commit manually — the system auto-commits your changes after this unit completes.
43
+ - Say: "Milestone {{milestoneId}} complete."
29
44
 
30
- **Important:** Do NOT skip the code change verification, success criteria, or definition of done verification (steps 3-5). The milestone summary must reflect actual verified outcomes, not assumed success. If any criterion was not met or no code changes exist, document it clearly in the summary and do not mark the milestone as passing verification.
45
+ **Important:** Do NOT skip the code change verification, success criteria, or definition of done verification (steps 3-5). The milestone summary must reflect actual verified outcomes, not assumed success. Verification failures BLOCK completion there is no override. The milestone stays in its current state until issues are resolved and verification is re-run.
31
46
 
32
47
  **File system safety:** When scanning milestone directories for evidence, use `ls` or `find` to list directory contents first — never pass a directory path (e.g. `tasks/`, `slices/`) directly to the `read` tool. The `read` tool only accepts file paths, not directories.
33
-
34
- When done, say: "Milestone {{milestoneId}} complete."
@@ -72,6 +72,10 @@ export async function handleCompleteMilestone(params, basePath) {
72
72
  if (!params.title || typeof params.title !== "string" || params.title.trim() === "") {
73
73
  return { error: "title is required and must be a non-empty string" };
74
74
  }
75
+ // ── Verify that verification passed ─────────────────────────────────────
76
+ if (params.verificationPassed !== true) {
77
+ return { error: "verification did not pass — milestone completion blocked. verificationPassed must be explicitly set to true after all verification steps succeed" };
78
+ }
75
79
  // ── Verify all slices are complete ───────────────────────────────────────
76
80
  const slices = getMilestoneSlices(params.milestoneId);
77
81
  if (slices.length === 0) {