gsd-pi 2.33.1-dev.ee47f1b → 2.34.0-dev.bbb5216

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 (135) hide show
  1. package/dist/bundled-resource-path.d.ts +8 -0
  2. package/dist/bundled-resource-path.js +14 -0
  3. package/dist/headless-query.js +6 -6
  4. package/dist/resources/extensions/gsd/auto/session.js +27 -32
  5. package/dist/resources/extensions/gsd/auto-dashboard.js +29 -109
  6. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +6 -1
  7. package/dist/resources/extensions/gsd/auto-dispatch.js +52 -81
  8. package/dist/resources/extensions/gsd/auto-loop.js +956 -0
  9. package/dist/resources/extensions/gsd/auto-observability.js +4 -2
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +75 -185
  11. package/dist/resources/extensions/gsd/auto-prompts.js +133 -101
  12. package/dist/resources/extensions/gsd/auto-recovery.js +59 -97
  13. package/dist/resources/extensions/gsd/auto-start.js +330 -309
  14. package/dist/resources/extensions/gsd/auto-supervisor.js +5 -11
  15. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +7 -7
  16. package/dist/resources/extensions/gsd/auto-timers.js +3 -4
  17. package/dist/resources/extensions/gsd/auto-verification.js +35 -73
  18. package/dist/resources/extensions/gsd/auto-worktree-sync.js +167 -0
  19. package/dist/resources/extensions/gsd/auto-worktree.js +291 -126
  20. package/dist/resources/extensions/gsd/auto.js +283 -1013
  21. package/dist/resources/extensions/gsd/captures.js +10 -4
  22. package/dist/resources/extensions/gsd/dispatch-guard.js +7 -8
  23. package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -18
  24. package/dist/resources/extensions/gsd/doctor-checks.js +3 -4
  25. package/dist/resources/extensions/gsd/git-service.js +1 -1
  26. package/dist/resources/extensions/gsd/gsd-db.js +296 -151
  27. package/dist/resources/extensions/gsd/index.js +92 -228
  28. package/dist/resources/extensions/gsd/post-unit-hooks.js +13 -13
  29. package/dist/resources/extensions/gsd/progress-score.js +61 -156
  30. package/dist/resources/extensions/gsd/quick.js +98 -122
  31. package/dist/resources/extensions/gsd/session-lock.js +13 -0
  32. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  33. package/dist/resources/extensions/gsd/undo.js +43 -48
  34. package/dist/resources/extensions/gsd/unit-runtime.js +16 -15
  35. package/dist/resources/extensions/gsd/verification-evidence.js +0 -1
  36. package/dist/resources/extensions/gsd/verification-gate.js +6 -35
  37. package/dist/resources/extensions/gsd/worktree-command.js +30 -24
  38. package/dist/resources/extensions/gsd/worktree-manager.js +2 -3
  39. package/dist/resources/extensions/gsd/worktree-resolver.js +344 -0
  40. package/dist/resources/extensions/gsd/worktree.js +7 -44
  41. package/dist/tool-bootstrap.js +59 -11
  42. package/dist/worktree-cli.js +7 -7
  43. package/package.json +1 -1
  44. package/packages/pi-ai/dist/models.generated.d.ts +3630 -5483
  45. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  46. package/packages/pi-ai/dist/models.generated.js +735 -2588
  47. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  48. package/packages/pi-ai/src/models.generated.ts +1039 -2892
  49. package/packages/pi-coding-agent/package.json +1 -1
  50. package/pkg/package.json +1 -1
  51. package/src/resources/extensions/gsd/auto/session.ts +47 -30
  52. package/src/resources/extensions/gsd/auto-dashboard.ts +28 -131
  53. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +6 -1
  54. package/src/resources/extensions/gsd/auto-dispatch.ts +135 -91
  55. package/src/resources/extensions/gsd/auto-loop.ts +1665 -0
  56. package/src/resources/extensions/gsd/auto-observability.ts +4 -2
  57. package/src/resources/extensions/gsd/auto-post-unit.ts +85 -228
  58. package/src/resources/extensions/gsd/auto-prompts.ts +138 -109
  59. package/src/resources/extensions/gsd/auto-recovery.ts +124 -118
  60. package/src/resources/extensions/gsd/auto-start.ts +440 -354
  61. package/src/resources/extensions/gsd/auto-supervisor.ts +5 -12
  62. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +8 -8
  63. package/src/resources/extensions/gsd/auto-timers.ts +3 -4
  64. package/src/resources/extensions/gsd/auto-verification.ts +76 -90
  65. package/src/resources/extensions/gsd/auto-worktree-sync.ts +204 -0
  66. package/src/resources/extensions/gsd/auto-worktree.ts +389 -141
  67. package/src/resources/extensions/gsd/auto.ts +515 -1199
  68. package/src/resources/extensions/gsd/captures.ts +10 -4
  69. package/src/resources/extensions/gsd/dispatch-guard.ts +13 -9
  70. package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -18
  71. package/src/resources/extensions/gsd/doctor-checks.ts +3 -4
  72. package/src/resources/extensions/gsd/git-service.ts +8 -1
  73. package/src/resources/extensions/gsd/gitignore.ts +4 -2
  74. package/src/resources/extensions/gsd/gsd-db.ts +375 -180
  75. package/src/resources/extensions/gsd/index.ts +104 -263
  76. package/src/resources/extensions/gsd/post-unit-hooks.ts +13 -13
  77. package/src/resources/extensions/gsd/progress-score.ts +65 -200
  78. package/src/resources/extensions/gsd/quick.ts +121 -125
  79. package/src/resources/extensions/gsd/session-lock.ts +11 -0
  80. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  81. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +32 -59
  82. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +75 -27
  83. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
  84. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +37 -0
  85. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1458 -0
  86. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +8 -162
  87. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -108
  88. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +1 -3
  89. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +0 -3
  90. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  91. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -55
  92. package/src/resources/extensions/gsd/tests/headless-query.test.ts +22 -0
  93. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +8 -11
  94. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +4 -6
  95. package/src/resources/extensions/gsd/tests/run-uat.test.ts +3 -3
  96. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +64 -0
  97. package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +181 -0
  98. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +0 -3
  99. package/src/resources/extensions/gsd/tests/token-profile.test.ts +6 -6
  100. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -6
  101. package/src/resources/extensions/gsd/tests/undo.test.ts +6 -0
  102. package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +24 -26
  103. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +7 -201
  104. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  105. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  106. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +0 -3
  107. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +705 -0
  108. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +57 -106
  109. package/src/resources/extensions/gsd/tests/worktree.test.ts +5 -1
  110. package/src/resources/extensions/gsd/tests/write-gate.test.ts +43 -132
  111. package/src/resources/extensions/gsd/types.ts +90 -81
  112. package/src/resources/extensions/gsd/undo.ts +42 -46
  113. package/src/resources/extensions/gsd/unit-runtime.ts +14 -18
  114. package/src/resources/extensions/gsd/verification-evidence.ts +1 -3
  115. package/src/resources/extensions/gsd/verification-gate.ts +6 -39
  116. package/src/resources/extensions/gsd/worktree-command.ts +36 -24
  117. package/src/resources/extensions/gsd/worktree-manager.ts +2 -3
  118. package/src/resources/extensions/gsd/worktree-resolver.ts +485 -0
  119. package/src/resources/extensions/gsd/worktree.ts +7 -44
  120. package/dist/resources/extensions/gsd/auto-constants.js +0 -5
  121. package/dist/resources/extensions/gsd/auto-idempotency.js +0 -106
  122. package/dist/resources/extensions/gsd/auto-stuck-detection.js +0 -165
  123. package/dist/resources/extensions/gsd/mechanical-completion.js +0 -351
  124. package/src/resources/extensions/gsd/auto-constants.ts +0 -6
  125. package/src/resources/extensions/gsd/auto-idempotency.ts +0 -151
  126. package/src/resources/extensions/gsd/auto-stuck-detection.ts +0 -221
  127. package/src/resources/extensions/gsd/mechanical-completion.ts +0 -430
  128. package/src/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +0 -691
  129. package/src/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +0 -127
  130. package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +0 -123
  131. package/src/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +0 -126
  132. package/src/resources/extensions/gsd/tests/loop-regression.test.ts +0 -874
  133. package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +0 -356
  134. package/src/resources/extensions/gsd/tests/progress-score.test.ts +0 -206
  135. package/src/resources/extensions/gsd/tests/session-lock.test.ts +0 -434
@@ -7,54 +7,52 @@ import { deriveState } from "./state.js";
7
7
  import { invalidateAllCaches } from "./cache.js";
8
8
  import { gsdRoot, resolveTasksDir, resolveSlicePath, buildTaskFileName } from "./paths.js";
9
9
  import { sendDesktopNotification } from "./notifications.js";
10
- import { parseUnitId } from "./unit-id.js";
11
10
  /**
12
- * Undo the last completed unit: revert git commits, remove from completed-units,
11
+ * Undo the last completed unit: revert git commits,
13
12
  * delete summary artifacts, and uncheck the task in PLAN.
13
+ * deriveState() handles re-derivation after revert.
14
14
  */
15
15
  export async function handleUndo(args, ctx, _pi, basePath) {
16
16
  const force = args.includes("--force");
17
- // 1. Load completed-units.json
18
- const completedKeysFile = join(gsdRoot(basePath), "completed-units.json");
19
- if (!existsSync(completedKeysFile)) {
20
- ctx.ui.notify("Nothing to undo — no completed units found.", "info");
17
+ // Find the last GSD-related commit from git activity logs
18
+ const activityDir = join(gsdRoot(basePath), "activity");
19
+ if (!existsSync(activityDir)) {
20
+ ctx.ui.notify("Nothing to undo — no activity logs found.", "info");
21
21
  return;
22
22
  }
23
- let keys;
24
- try {
25
- keys = JSON.parse(readFileSync(completedKeysFile, "utf-8"));
26
- }
27
- catch {
28
- ctx.ui.notify("Nothing to undo — completed-units.json is corrupt.", "warning");
23
+ // Parse activity logs to find the most recent unit
24
+ const files = readdirSync(activityDir)
25
+ .filter(f => f.endsWith(".jsonl"))
26
+ .sort()
27
+ .reverse();
28
+ if (files.length === 0) {
29
+ ctx.ui.notify("Nothing to undo — no activity logs found.", "info");
29
30
  return;
30
31
  }
31
- if (keys.length === 0) {
32
- ctx.ui.notify("Nothing to undo — no completed units.", "info");
32
+ // Extract unit type and ID from the most recent activity log filename
33
+ // Format: <seq>-<unitType>-<unitId>.jsonl
34
+ const match = files[0].match(/^\d+-(.+?)-(.+)\.jsonl$/);
35
+ if (!match) {
36
+ ctx.ui.notify("Nothing to undo — could not parse latest activity log.", "warning");
33
37
  return;
34
38
  }
35
- // Get the last completed unit
36
- const lastKey = keys[keys.length - 1];
37
- const sepIdx = lastKey.indexOf("/");
38
- const unitType = sepIdx >= 0 ? lastKey.slice(0, sepIdx) : lastKey;
39
- const unitId = sepIdx >= 0 ? lastKey.slice(sepIdx + 1) : lastKey;
39
+ const unitType = match[1];
40
+ const unitId = match[2].replace(/-/g, "/");
40
41
  if (!force) {
41
42
  ctx.ui.notify(`Will undo: ${unitType} (${unitId})\n` +
42
43
  `This will:\n` +
43
- ` - Remove from completed-units.json\n` +
44
44
  ` - Delete summary artifacts\n` +
45
45
  ` - Uncheck task in PLAN (if execute-task)\n` +
46
46
  ` - Attempt to revert associated git commits\n\n` +
47
47
  `Run /gsd undo --force to confirm.`, "warning");
48
48
  return;
49
49
  }
50
- // 2. Remove from completed-units.json
51
- keys = keys.filter(k => k !== lastKey);
52
- writeFileSync(completedKeysFile, JSON.stringify(keys), "utf-8");
53
- // 3. Delete summary artifact
54
- const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
50
+ // 1. Delete summary artifact
51
+ const parts = unitId.split("/");
55
52
  let summaryRemoved = false;
56
- if (mid && sid && tid) {
53
+ if (parts.length === 3) {
57
54
  // Task-level: M001/S01/T01
55
+ const [mid, sid, tid] = parts;
58
56
  const tasksDir = resolveTasksDir(basePath, mid, sid);
59
57
  if (tasksDir) {
60
58
  const summaryFile = join(tasksDir, buildTaskFileName(tid, "SUMMARY"));
@@ -64,11 +62,11 @@ export async function handleUndo(args, ctx, _pi, basePath) {
64
62
  }
65
63
  }
66
64
  }
67
- else if (mid && sid) {
65
+ else if (parts.length === 2) {
68
66
  // Slice-level: M001/S01
67
+ const [mid, sid] = parts;
69
68
  const slicePath = resolveSlicePath(basePath, mid, sid);
70
69
  if (slicePath) {
71
- // Try common summary filenames
72
70
  for (const suffix of ["SUMMARY", "COMPLETE"]) {
73
71
  const candidates = findFileWithPrefix(slicePath, sid, suffix);
74
72
  for (const f of candidates) {
@@ -78,43 +76,40 @@ export async function handleUndo(args, ctx, _pi, basePath) {
78
76
  }
79
77
  }
80
78
  }
81
- // 4. Uncheck task in PLAN if execute-task
79
+ // 2. Uncheck task in PLAN if execute-task
82
80
  let planUpdated = false;
83
- if (unitType === "execute-task" && mid && sid && tid) {
81
+ if (unitType === "execute-task" && parts.length === 3) {
82
+ const [mid, sid, tid] = parts;
84
83
  planUpdated = uncheckTaskInPlan(basePath, mid, sid, tid);
85
84
  }
86
- // 5. Try to revert git commits from activity log
85
+ // 3. Try to revert git commits from activity log
87
86
  let commitsReverted = 0;
88
- const activityDir = join(gsdRoot(basePath), "activity");
89
87
  try {
90
- if (existsSync(activityDir)) {
91
- const commits = findCommitsForUnit(activityDir, unitType, unitId);
92
- if (commits.length > 0) {
93
- for (const sha of commits.reverse()) {
88
+ const commits = findCommitsForUnit(activityDir, unitType, unitId);
89
+ if (commits.length > 0) {
90
+ for (const sha of commits.reverse()) {
91
+ try {
92
+ nativeRevertCommit(basePath, sha);
93
+ commitsReverted++;
94
+ }
95
+ catch {
96
+ // Revert conflict or already reverted — skip
94
97
  try {
95
- nativeRevertCommit(basePath, sha);
96
- commitsReverted++;
97
- }
98
- catch {
99
- // Revert conflict or already reverted — skip
100
- try {
101
- nativeRevertAbort(basePath);
102
- }
103
- catch { /* no-op */ }
104
- break;
98
+ nativeRevertAbort(basePath);
105
99
  }
100
+ catch { /* no-op */ }
101
+ break;
106
102
  }
107
103
  }
108
104
  }
109
105
  }
110
106
  finally {
111
- // 6. Re-derive state — always invalidate caches even if git operations fail
107
+ // 4. Re-derive state — always invalidate caches even if git operations fail
112
108
  invalidateAllCaches();
113
109
  await deriveState(basePath);
114
110
  }
115
111
  // Build result message
116
112
  const results = [`Undone: ${unitType} (${unitId})`];
117
- results.push(` - Removed from completed-units.json`);
118
113
  if (summaryRemoved)
119
114
  results.push(` - Deleted summary artifact`);
120
115
  if (planUpdated)
@@ -1,25 +1,18 @@
1
- import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { gsdRoot, relSliceFile, relTaskFile, resolveSliceFile, resolveTaskFile, } from "./paths.js";
4
4
  import { loadFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
5
- import { loadJsonFileOrNull, saveJsonFile } from "./json-persistence.js";
6
- import { parseUnitId } from "./unit-id.js";
7
- function isAutoUnitRuntimeRecord(data) {
8
- return (typeof data === "object" &&
9
- data !== null &&
10
- data.version === 1 &&
11
- typeof data.unitType === "string" &&
12
- typeof data.unitId === "string");
13
- }
14
5
  function runtimeDir(basePath) {
15
6
  return join(gsdRoot(basePath), "runtime", "units");
16
7
  }
17
8
  function runtimePath(basePath, unitType, unitId) {
18
- const sanitizedUnitType = unitType.replace(/[^a-zA-Z0-9._-]+/g, "-");
19
- const sanitizedUnitId = unitId.replace(/[^a-zA-Z0-9._-]+/g, "-");
9
+ const sanitizedUnitType = unitType.replace(/[\/]/g, "-");
10
+ const sanitizedUnitId = unitId.replace(/[\/]/g, "-");
20
11
  return join(runtimeDir(basePath), `${sanitizedUnitType}-${sanitizedUnitId}.json`);
21
12
  }
22
13
  export function writeUnitRuntimeRecord(basePath, unitType, unitId, startedAt, updates = {}) {
14
+ const dir = runtimeDir(basePath);
15
+ mkdirSync(dir, { recursive: true });
23
16
  const path = runtimePath(basePath, unitType, unitId);
24
17
  const prev = readUnitRuntimeRecord(basePath, unitType, unitId);
25
18
  const next = {
@@ -39,11 +32,19 @@ export function writeUnitRuntimeRecord(basePath, unitType, unitId, startedAt, up
39
32
  recoveryAttempts: updates.recoveryAttempts ?? prev?.recoveryAttempts ?? 0,
40
33
  lastRecoveryReason: updates.lastRecoveryReason ?? prev?.lastRecoveryReason,
41
34
  };
42
- saveJsonFile(path, next);
35
+ writeFileSync(path, JSON.stringify(next, null, 2) + "\n", "utf-8");
43
36
  return next;
44
37
  }
45
38
  export function readUnitRuntimeRecord(basePath, unitType, unitId) {
46
- return loadJsonFileOrNull(runtimePath(basePath, unitType, unitId), isAutoUnitRuntimeRecord);
39
+ const path = runtimePath(basePath, unitType, unitId);
40
+ if (!existsSync(path))
41
+ return null;
42
+ try {
43
+ return JSON.parse(readFileSync(path, "utf-8"));
44
+ }
45
+ catch {
46
+ return null;
47
+ }
47
48
  }
48
49
  export function clearUnitRuntimeRecord(basePath, unitType, unitId) {
49
50
  const path = runtimePath(basePath, unitType, unitId);
@@ -74,7 +75,7 @@ export function listUnitRuntimeRecords(basePath) {
74
75
  return results;
75
76
  }
76
77
  export async function inspectExecuteTaskDurability(basePath, unitId) {
77
- const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
78
+ const [mid, sid, tid] = unitId.split("/");
78
79
  if (!mid || !sid || !tid)
79
80
  return null;
80
81
  const planAbs = resolveSliceFile(basePath, mid, sid, "PLAN");
@@ -31,7 +31,6 @@ export function writeVerificationJSON(result, tasksDir, taskId, unitId, retryAtt
31
31
  exitCode: check.exitCode,
32
32
  durationMs: check.durationMs,
33
33
  verdict: check.exitCode === 0 ? "pass" : "fail",
34
- blocking: check.blocking,
35
34
  })),
36
35
  ...(retryAttempt !== undefined ? { retryAttempt } : {}),
37
36
  ...(maxRetries !== undefined ? { maxRetries } : {}),
@@ -28,12 +28,11 @@ const PACKAGE_SCRIPT_KEYS = ["typecheck", "lint", "test"];
28
28
  * 4. None found
29
29
  */
30
30
  export function discoverCommands(options) {
31
- // 1. Preference commands (still sanitize — may contain prose from misconfiguration)
31
+ // 1. Preference commands
32
32
  if (options.preferenceCommands && options.preferenceCommands.length > 0) {
33
33
  const filtered = options.preferenceCommands
34
34
  .map(c => c.trim())
35
- .filter(Boolean)
36
- .filter(c => isLikelyCommand(c));
35
+ .filter(Boolean);
37
36
  if (filtered.length > 0) {
38
37
  return { commands: filtered, source: "preference" };
39
38
  }
@@ -89,9 +88,7 @@ const MAX_FAILURE_CONTEXT_CHARS = 10_000;
89
88
  * Returns an empty string when all checks pass or the checks array is empty.
90
89
  */
91
90
  export function formatFailureContext(result) {
92
- // Only include blocking failures in retry context non-blocking (advisory) failures
93
- // should not be injected into retry prompts to avoid noise pollution.
94
- const failures = result.checks.filter((c) => c.exitCode !== 0 && c.blocking);
91
+ const failures = result.checks.filter((c) => c.exitCode !== 0);
95
92
  if (failures.length === 0)
96
93
  return "";
97
94
  const blocks = [];
@@ -187,19 +184,13 @@ function sanitizeCommand(cmd) {
187
184
  return null;
188
185
  return cmd;
189
186
  }
190
- /** Error codes from spawnSync that indicate infrastructure/OS-level failures
191
- * rather than the command itself failing. These are transient — the agent
192
- * cannot fix them, so they should not trigger auto-fix retries. */
193
- const INFRA_ERROR_CODES = new Set(["ETIMEDOUT", "ENOENT", "ENOMEM", "EMFILE", "ENFILE", "EAGAIN"]);
194
187
  /**
195
188
  * Run the verification gate: discover commands, execute each via spawnSync,
196
189
  * and return a structured result.
197
190
  *
198
191
  * - All commands run sequentially regardless of individual pass/fail.
199
- * - `passed` is true when every blocking command exits 0 (or no commands are discovered).
192
+ * - `passed` is true when every command exits 0 (or no commands are discovered).
200
193
  * - stdout/stderr per command are truncated to 10 KB.
201
- * - Spawn/infra errors (ETIMEDOUT, ENOENT, etc.) are tagged with `infraError: true`
202
- * so the retry logic can distinguish "the OS couldn't run this" from "the tests failed".
203
194
  */
204
195
  export function runVerificationGate(options) {
205
196
  const timestamp = Date.now();
@@ -216,9 +207,6 @@ export function runVerificationGate(options) {
216
207
  timestamp,
217
208
  };
218
209
  }
219
- // Commands from preference and task-plan sources are blocking;
220
- // package-json discovered commands are advisory (non-blocking).
221
- const blocking = source === "preference" || source === "task-plan";
222
210
  const checks = [];
223
211
  for (const command of commands) {
224
212
  const start = Date.now();
@@ -233,22 +221,9 @@ export function runVerificationGate(options) {
233
221
  let exitCode;
234
222
  let stderr;
235
223
  if (result.error) {
236
- // Spawn infrastructure failure OS-level, not a test failure.
237
- // Tag with infraError so the retry logic can skip auto-fix attempts.
238
- const errCode = result.error.code;
239
- const isInfra = !!errCode && INFRA_ERROR_CODES.has(errCode);
224
+ // Command not found or spawn failure
240
225
  exitCode = 127;
241
226
  stderr = truncate((result.stderr || "") + "\n" + result.error.message, MAX_OUTPUT_BYTES);
242
- checks.push({
243
- command,
244
- exitCode,
245
- stdout: truncate(result.stdout, MAX_OUTPUT_BYTES),
246
- stderr,
247
- durationMs,
248
- blocking,
249
- ...(isInfra ? { infraError: true } : {}),
250
- });
251
- continue;
252
227
  }
253
228
  else {
254
229
  // status is null when killed by signal — treat as failure
@@ -261,14 +236,10 @@ export function runVerificationGate(options) {
261
236
  stdout: truncate(result.stdout, MAX_OUTPUT_BYTES),
262
237
  stderr,
263
238
  durationMs,
264
- blocking,
265
239
  });
266
240
  }
267
- // Gate passes if all blocking checks pass (non-blocking failures are advisory)
268
- const blockingChecks = checks.filter(c => c.blocking);
269
- const passed = blockingChecks.length === 0 || blockingChecks.every(c => c.exitCode === 0);
270
241
  return {
271
- passed,
242
+ passed: checks.every(c => c.exitCode === 0),
272
243
  checks,
273
244
  discoverySource: source,
274
245
  timestamp,
@@ -19,34 +19,21 @@ import { inferCommitType } from "./git-service.js";
19
19
  import { existsSync, realpathSync, readdirSync, rmSync, unlinkSync } from "node:fs";
20
20
  import { nativeMergeAbort } from "./native-git-bridge.js";
21
21
  import { join, sep } from "node:path";
22
- import { getErrorMessage } from "./error-utils.js";
23
22
  /**
24
23
  * Tracks the original project root so we can switch back.
25
24
  * Set when we first chdir into a worktree, cleared on return.
26
25
  */
27
26
  let originalCwd = null;
28
- function ensureWorktreeStateInitialized() {
29
- if (originalCwd)
30
- return;
31
- const cwd = process.cwd();
32
- const marker = `${sep}.gsd${sep}worktrees${sep}`;
33
- const markerIdx = cwd.indexOf(marker);
34
- if (markerIdx !== -1) {
35
- originalCwd = cwd.slice(0, markerIdx);
36
- }
37
- }
38
27
  /** Get the original project root if currently in a worktree, or null. */
39
28
  export function getWorktreeOriginalCwd() {
40
- ensureWorktreeStateInitialized();
41
29
  return originalCwd;
42
30
  }
43
31
  /** Get the name of the active worktree, or null if not in one. */
44
32
  export function getActiveWorktreeName() {
45
- ensureWorktreeStateInitialized();
46
33
  if (!originalCwd)
47
34
  return null;
48
35
  const cwd = process.cwd();
49
- const wtDir = join(gsdRoot(originalCwd), "worktrees");
36
+ const wtDir = join(originalCwd, ".gsd", "worktrees");
50
37
  if (!cwd.startsWith(wtDir))
51
38
  return null;
52
39
  const rel = cwd.slice(wtDir.length + 1);
@@ -94,8 +81,7 @@ function worktreeCompletions(prefix) {
94
81
  }
95
82
  return [];
96
83
  }
97
- export async function handleWorktreeCommand(args, ctx, pi, alias) {
98
- ensureWorktreeStateInitialized();
84
+ async function worktreeHandler(args, ctx, pi, alias) {
99
85
  const trimmed = (typeof args === "string" ? args : "").trim();
100
86
  const basePath = process.cwd();
101
87
  if (trimmed === "") {
@@ -199,11 +185,21 @@ export async function handleWorktreeCommand(args, ctx, pi, alias) {
199
185
  await handleCreate(basePath, nameOnly, ctx);
200
186
  }
201
187
  }
188
+ export async function handleWorktreeCommand(args, ctx, pi, alias) {
189
+ await worktreeHandler(args, ctx, pi, alias);
190
+ }
202
191
  export function registerWorktreeCommand(pi) {
203
192
  // Restore worktree state after /reload.
204
193
  // The module-level originalCwd resets to null when extensions are re-loaded,
205
194
  // but process.cwd() is still inside the worktree. Detect this and recover.
206
- ensureWorktreeStateInitialized();
195
+ if (!originalCwd) {
196
+ const cwd = process.cwd();
197
+ const marker = `${sep}.gsd${sep}worktrees${sep}`;
198
+ const markerIdx = cwd.indexOf(marker);
199
+ if (markerIdx !== -1) {
200
+ originalCwd = cwd.slice(0, markerIdx);
201
+ }
202
+ }
207
203
  pi.registerCommand("worktree", {
208
204
  description: "Git worktrees (also /wt): /worktree <name> | list | merge | remove",
209
205
  getArgumentCompletions: worktreeCompletions,
@@ -317,7 +313,7 @@ async function handleCreate(basePath, name, ctx) {
317
313
  ].filter(Boolean).join("\n"), "info");
318
314
  }
319
315
  catch (error) {
320
- const msg = getErrorMessage(error);
316
+ const msg = error instanceof Error ? error.message : String(error);
321
317
  ctx.ui.notify(`Failed to create worktree: ${msg}`, "error");
322
318
  }
323
319
  }
@@ -351,7 +347,7 @@ async function handleSwitch(basePath, name, ctx) {
351
347
  ].filter(Boolean).join("\n"), "info");
352
348
  }
353
349
  catch (error) {
354
- const msg = getErrorMessage(error);
350
+ const msg = error instanceof Error ? error.message : String(error);
355
351
  ctx.ui.notify(`Failed to switch to worktree: ${msg}`, "error");
356
352
  }
357
353
  }
@@ -442,7 +438,7 @@ async function handleList(basePath, ctx) {
442
438
  ctx.ui.notify(lines.join("\n"), "info");
443
439
  }
444
440
  catch (error) {
445
- const msg = getErrorMessage(error);
441
+ const msg = error instanceof Error ? error.message : String(error);
446
442
  ctx.ui.notify(`Failed to list worktrees: ${msg}`, "error");
447
443
  }
448
444
  }
@@ -533,6 +529,16 @@ async function handleMerge(basePath, name, ctx, pi, targetBranch) {
533
529
  // Try a direct squash-merge first. Only fall back to LLM on conflict.
534
530
  const commitType = inferCommitType(name);
535
531
  const commitMessage = `${commitType}(${name}): merge worktree ${name}`;
532
+ // Reconcile worktree DB into main DB before squash merge
533
+ const wtDbPath = join(worktreePath(basePath, name), ".gsd", "gsd.db");
534
+ const mainDbPath = join(basePath, ".gsd", "gsd.db");
535
+ if (existsSync(wtDbPath) && existsSync(mainDbPath)) {
536
+ try {
537
+ const { reconcileWorktreeDb } = await import("./gsd-db.js");
538
+ reconcileWorktreeDb(mainDbPath, wtDbPath);
539
+ }
540
+ catch { /* non-fatal */ }
541
+ }
536
542
  try {
537
543
  mergeWorktreeToMain(basePath, name, commitMessage);
538
544
  ctx.ui.notify([
@@ -544,7 +550,7 @@ async function handleMerge(basePath, name, ctx, pi, targetBranch) {
544
550
  return;
545
551
  }
546
552
  catch (mergeErr) {
547
- const mergeMsg = getErrorMessage(mergeErr);
553
+ const mergeMsg = mergeErr instanceof Error ? mergeErr.message : String(mergeErr);
548
554
  const isConflict = /conflict/i.test(mergeMsg);
549
555
  if (isConflict) {
550
556
  // Abort the failed merge so the working tree is clean for LLM retry
@@ -588,7 +594,7 @@ async function handleMerge(basePath, name, ctx, pi, targetBranch) {
588
594
  ctx.ui.notify(`${CLR.ok("✓")} Merge helper started for ${CLR.name(name)} ${CLR.muted(`(${codeChanges} code + ${gsdChanges} GSD artifact change${totalChanges === 1 ? "" : "s"})`)}`, "info");
589
595
  }
590
596
  catch (error) {
591
- const msg = getErrorMessage(error);
597
+ const msg = error instanceof Error ? error.message : String(error);
592
598
  ctx.ui.notify(`Failed to start merge: ${msg}`, "error");
593
599
  }
594
600
  }
@@ -622,7 +628,7 @@ async function handleRemove(basePath, name, ctx) {
622
628
  ctx.ui.notify(`${CLR.ok("✓")} Worktree ${CLR.name(name)} removed ${CLR.muted("(branch deleted)")}.`, "info");
623
629
  }
624
630
  catch (error) {
625
- const msg = getErrorMessage(error);
631
+ const msg = error instanceof Error ? error.message : String(error);
626
632
  ctx.ui.notify(`Failed to remove worktree: ${msg}`, "error");
627
633
  }
628
634
  }
@@ -670,7 +676,7 @@ async function handleRemoveAll(basePath, ctx) {
670
676
  ctx.ui.notify(lines.join("\n"), failed.length > 0 ? "warning" : "info");
671
677
  }
672
678
  catch (error) {
673
- const msg = getErrorMessage(error);
679
+ const msg = error instanceof Error ? error.message : String(error);
674
680
  ctx.ui.notify(`Failed to remove worktrees: ${msg}`, "error");
675
681
  }
676
682
  }
@@ -16,7 +16,6 @@
16
16
  */
17
17
  import { existsSync, mkdirSync, readFileSync, realpathSync } from "node:fs";
18
18
  import { join, resolve, sep } from "node:path";
19
- import { gsdRoot } from "./paths.js";
20
19
  import { GSDError, GSD_PARSE_ERROR, GSD_STALE_STATE, GSD_LOCK_HELD, GSD_GIT_ERROR, GSD_MERGE_CONFLICT } from "./errors.js";
21
20
  import { nativeBranchDelete, nativeBranchExists, nativeBranchForceReset, nativeCommit, nativeDetectMainBranch, nativeDiffContent, nativeDiffNameStatus, nativeDiffNumstat, nativeGetCurrentBranch, nativeLogOneline, nativeMergeSquash, nativeWorktreeAdd, nativeWorktreeList, nativeWorktreePrune, nativeWorktreeRemove, } from "./native-git-bridge.js";
22
21
  // ─── Path Helpers ──────────────────────────────────────────────────────────
@@ -56,7 +55,7 @@ export function resolveGitDir(basePath) {
56
55
  return join(basePath, ".git");
57
56
  }
58
57
  export function worktreesDir(basePath) {
59
- return join(gsdRoot(basePath), "worktrees");
58
+ return join(basePath, ".gsd", "worktrees");
60
59
  }
61
60
  export function worktreePath(basePath, name) {
62
61
  return join(worktreesDir(basePath), name);
@@ -133,7 +132,7 @@ export function listWorktrees(basePath) {
133
132
  const seenRoots = new Set();
134
133
  const worktreeRoots = baseVariants
135
134
  .map(baseVariant => {
136
- const path = join(gsdRoot(baseVariant), "worktrees");
135
+ const path = join(baseVariant, ".gsd", "worktrees");
137
136
  return {
138
137
  normalized: normalizePathForComparison(path),
139
138
  };