gsd-pi 2.78.1-dev.9d08d820b → 2.78.1-dev.a7b6e59b7

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 (101) hide show
  1. package/README.md +1 -0
  2. package/dist/cli-auto-routing.d.ts +1 -0
  3. package/dist/cli-auto-routing.js +5 -0
  4. package/dist/cli.js +5 -14
  5. package/dist/resources/.managed-resources-content-hash +1 -1
  6. package/dist/resources/extensions/google-search/index.js +2 -6
  7. package/dist/resources/extensions/gsd/auto/run-unit.js +23 -11
  8. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +55 -21
  9. package/dist/resources/extensions/gsd/auto-prompts.js +6 -0
  10. package/dist/resources/extensions/gsd/auto-worktree.js +15 -0
  11. package/dist/resources/extensions/gsd/auto.js +25 -9
  12. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +12 -0
  13. package/dist/resources/extensions/gsd/bootstrap/system-context.js +11 -0
  14. package/dist/resources/extensions/gsd/commands/catalog.js +8 -1
  15. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
  16. package/dist/resources/extensions/gsd/commands/handlers/ops.js +8 -0
  17. package/dist/resources/extensions/gsd/commands-worktree.js +309 -0
  18. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  19. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +2 -0
  20. package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +2 -0
  21. package/dist/resources/extensions/gsd/worktree-resolver.js +24 -0
  22. package/dist/resources/extensions/mcp-client/index.js +0 -6
  23. package/dist/resources/skills/lint/SKILL.md +4 -0
  24. package/dist/resources/skills/review/SKILL.md +4 -0
  25. package/dist/resources/skills/test/SKILL.md +3 -0
  26. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  27. package/dist/web/standalone/.next/BUILD_ID +1 -1
  28. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  29. package/dist/web/standalone/.next/build-manifest.json +2 -2
  30. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  31. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/index.html +1 -1
  48. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  55. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  56. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  57. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  58. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  59. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  60. package/dist/welcome-screen.js +27 -1
  61. package/package.json +1 -1
  62. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +278 -0
  63. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  64. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +7 -0
  65. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  66. package/packages/pi-coding-agent/dist/core/agent-session.js +125 -55
  67. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  68. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +319 -0
  69. package/packages/pi-coding-agent/src/core/agent-session.ts +128 -59
  70. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  71. package/src/resources/extensions/google-search/index.ts +2 -9
  72. package/src/resources/extensions/gsd/auto/run-unit.ts +23 -11
  73. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +60 -24
  74. package/src/resources/extensions/gsd/auto-prompts.ts +6 -0
  75. package/src/resources/extensions/gsd/auto-worktree.ts +15 -0
  76. package/src/resources/extensions/gsd/auto.ts +23 -6
  77. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +13 -0
  78. package/src/resources/extensions/gsd/bootstrap/system-context.ts +11 -0
  79. package/src/resources/extensions/gsd/commands/catalog.ts +8 -1
  80. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
  81. package/src/resources/extensions/gsd/commands/handlers/ops.ts +10 -0
  82. package/src/resources/extensions/gsd/commands-worktree.ts +383 -0
  83. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  84. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +2 -0
  85. package/src/resources/extensions/gsd/prompts/rewrite-docs.md +2 -0
  86. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -0
  87. package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +50 -27
  88. package/src/resources/extensions/gsd/tests/commands-worktree-clean.test.ts +48 -0
  89. package/src/resources/extensions/gsd/tests/google-search-stub.test.ts +25 -65
  90. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +34 -0
  91. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +8 -2
  92. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +12 -6
  93. package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +235 -0
  94. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +85 -0
  95. package/src/resources/extensions/gsd/worktree-resolver.ts +24 -0
  96. package/src/resources/extensions/mcp-client/index.ts +0 -7
  97. package/src/resources/skills/lint/SKILL.md +4 -0
  98. package/src/resources/skills/review/SKILL.md +4 -0
  99. package/src/resources/skills/test/SKILL.md +3 -0
  100. /package/dist/web/standalone/.next/static/{-Ukk6_YxRd4GY4iUOnRUE → GlYncvckBGG33CSoJaSnB}/_buildManifest.js +0 -0
  101. /package/dist/web/standalone/.next/static/{-Ukk6_YxRd4GY4iUOnRUE → GlYncvckBGG33CSoJaSnB}/_ssgManifest.js +0 -0
@@ -0,0 +1,309 @@
1
+ // GSD-2 — In-TUI handler for /gsd worktree commands (list, merge, clean, remove).
2
+ //
3
+ // Mirrors the CLI subcommands in src/worktree-cli.ts but emits results via
4
+ // ctx.ui.notify() instead of writing colored output to stderr. Reuses the
5
+ // same extension modules (worktree-manager, native-git-bridge, etc.) so the
6
+ // behavior is identical to the CLI surface.
7
+ import { existsSync } from "node:fs";
8
+ import { projectRoot } from "./commands/context.js";
9
+ import { listWorktrees, removeWorktree, mergeWorktreeToMain, diffWorktreeAll, diffWorktreeNumstat, worktreeBranchName, } from "./worktree-manager.js";
10
+ import { nativeHasChanges, nativeDetectMainBranch, nativeCommitCountBetween, } from "./native-git-bridge.js";
11
+ import { inferCommitType } from "./git-service.js";
12
+ import { autoCommitCurrentBranch } from "./worktree.js";
13
+ import { GSDError, GSD_GIT_ERROR } from "./errors.js";
14
+ // ─── Status helper ─────────────────────────────────────────────────────────
15
+ function getStatus(basePath, name, wtPath) {
16
+ const diff = diffWorktreeAll(basePath, name);
17
+ const numstat = diffWorktreeNumstat(basePath, name);
18
+ const filesChanged = diff.added.length + diff.modified.length + diff.removed.length;
19
+ let linesAdded = 0;
20
+ let linesRemoved = 0;
21
+ for (const s of numstat) {
22
+ linesAdded += s.added;
23
+ linesRemoved += s.removed;
24
+ }
25
+ let uncommitted = false;
26
+ try {
27
+ uncommitted = existsSync(wtPath) && nativeHasChanges(wtPath);
28
+ }
29
+ catch {
30
+ // native check failure → treat as clean for display purposes
31
+ }
32
+ let commits = 0;
33
+ try {
34
+ const main = nativeDetectMainBranch(basePath);
35
+ commits = nativeCommitCountBetween(basePath, main, worktreeBranchName(name));
36
+ }
37
+ catch {
38
+ // commit count unavailable → leave at 0
39
+ }
40
+ return {
41
+ name,
42
+ path: wtPath,
43
+ branch: worktreeBranchName(name),
44
+ exists: existsSync(wtPath),
45
+ filesChanged,
46
+ linesAdded,
47
+ linesRemoved,
48
+ uncommitted,
49
+ commits,
50
+ };
51
+ }
52
+ // ─── Formatters (exported for tests) ────────────────────────────────────────
53
+ export function formatWorktreeList(statuses) {
54
+ if (statuses.length === 0) {
55
+ return "No worktrees.\n\nCreate one from the CLI: gsd -w <name>";
56
+ }
57
+ const lines = [`Worktrees — ${statuses.length}`, ""];
58
+ for (const s of statuses) {
59
+ const badge = s.uncommitted
60
+ ? "(uncommitted)"
61
+ : s.filesChanged > 0
62
+ ? "(unmerged)"
63
+ : "(clean)";
64
+ lines.push(` ${s.name} ${badge}`);
65
+ lines.push(` branch ${s.branch}`);
66
+ lines.push(` path ${s.path}`);
67
+ if (s.filesChanged > 0) {
68
+ lines.push(` diff ${s.filesChanged} file${s.filesChanged === 1 ? "" : "s"}, +${s.linesAdded} -${s.linesRemoved}, ${s.commits} commit${s.commits === 1 ? "" : "s"}`);
69
+ }
70
+ lines.push("");
71
+ }
72
+ lines.push("Commands:");
73
+ lines.push(" /gsd worktree merge <name> Merge into main and clean up");
74
+ lines.push(" /gsd worktree remove <name> Remove a worktree (--force to skip safety checks)");
75
+ lines.push(" /gsd worktree clean Remove all merged/empty worktrees");
76
+ return lines.join("\n");
77
+ }
78
+ export function formatCleanKeepReason(status) {
79
+ if (!status.exists) {
80
+ return "directory missing — run 'git worktree prune' to unregister";
81
+ }
82
+ if (status.filesChanged > 0) {
83
+ return `${status.filesChanged} changed file${status.filesChanged === 1 ? "" : "s"}${status.uncommitted ? ", uncommitted" : ""}`;
84
+ }
85
+ return "uncommitted changes";
86
+ }
87
+ // ─── Subcommand: list ───────────────────────────────────────────────────────
88
+ async function handleList(ctx) {
89
+ const basePath = projectRoot();
90
+ const worktrees = listWorktrees(basePath);
91
+ const statuses = worktrees.map((wt) => getStatus(basePath, wt.name, wt.path));
92
+ ctx.ui.notify(formatWorktreeList(statuses), "info");
93
+ }
94
+ // ─── Subcommand: merge ──────────────────────────────────────────────────────
95
+ async function handleMerge(args, ctx) {
96
+ const basePath = projectRoot();
97
+ const worktrees = listWorktrees(basePath);
98
+ const trimmed = args.trim();
99
+ let target = trimmed;
100
+ if (!target) {
101
+ if (worktrees.length === 1) {
102
+ target = worktrees[0].name;
103
+ }
104
+ else if (worktrees.length === 0) {
105
+ ctx.ui.notify("No worktrees to merge.", "info");
106
+ return;
107
+ }
108
+ else {
109
+ const names = worktrees.map((w) => w.name).join(", ");
110
+ ctx.ui.notify(`Usage: /gsd worktree merge <name>\n\nWorktrees: ${names}`, "warning");
111
+ return;
112
+ }
113
+ }
114
+ const wt = worktrees.find((w) => w.name === target);
115
+ if (!wt) {
116
+ const available = worktrees.map((w) => w.name).join(", ") || "(none)";
117
+ ctx.ui.notify(`Worktree "${target}" not found.\n\nAvailable: ${available}`, "error");
118
+ return;
119
+ }
120
+ const status = getStatus(basePath, target, wt.path);
121
+ if (status.filesChanged === 0 && !status.uncommitted) {
122
+ try {
123
+ removeWorktree(basePath, target, { deleteBranch: true });
124
+ ctx.ui.notify(`Removed empty worktree ${target}.`, "info");
125
+ }
126
+ catch (err) {
127
+ const msg = err instanceof Error ? err.message : String(err);
128
+ ctx.ui.notify(`Worktree partially removed: ${msg}\n\nRun 'git worktree prune' to clean up any dangling registrations.`, "error");
129
+ }
130
+ return;
131
+ }
132
+ if (status.uncommitted) {
133
+ try {
134
+ autoCommitCurrentBranch(wt.path, "worktree-merge", target);
135
+ }
136
+ catch (err) {
137
+ const msg = err instanceof Error ? err.message : String(err);
138
+ ctx.ui.notify([
139
+ `Auto-commit before merge failed: ${msg}`,
140
+ "",
141
+ `Commit or stash changes in ${wt.path}, then re-run /gsd worktree merge ${target}.`,
142
+ ].join("\n"), "error");
143
+ return;
144
+ }
145
+ }
146
+ const commitType = inferCommitType(target);
147
+ const mainBranch = nativeDetectMainBranch(basePath);
148
+ const commitMessage = `${commitType}: merge worktree ${target}\n\nGSD-Worktree: ${target}`;
149
+ try {
150
+ mergeWorktreeToMain(basePath, target, commitMessage);
151
+ }
152
+ catch (err) {
153
+ const msg = err instanceof Error ? err.message : String(err);
154
+ if (err instanceof GSDError && err.code === GSD_GIT_ERROR) {
155
+ ctx.ui.notify(`Merge requires the main branch to be checked out: ${msg}\n\nSwitch to ${mainBranch} (e.g. 'git checkout ${mainBranch}'), then re-run /gsd worktree merge ${target}.`, "error");
156
+ }
157
+ else {
158
+ ctx.ui.notify(`Merge failed: ${msg}\n\nResolve conflicts manually, then run /gsd worktree merge ${target} again.`, "error");
159
+ }
160
+ return;
161
+ }
162
+ const successLines = [
163
+ `Merged ${target} → ${mainBranch}`,
164
+ ` ${status.filesChanged} file${status.filesChanged === 1 ? "" : "s"}, +${status.linesAdded} -${status.linesRemoved}`,
165
+ ` commit: ${commitMessage.split("\n")[0]}`,
166
+ ];
167
+ try {
168
+ removeWorktree(basePath, target, { deleteBranch: true });
169
+ ctx.ui.notify(successLines.join("\n"), "info");
170
+ }
171
+ catch (err) {
172
+ const msg = err instanceof Error ? err.message : String(err);
173
+ const cleanupLines = [
174
+ ...successLines,
175
+ "",
176
+ `Cleanup failed after the merge succeeded: ${msg}`,
177
+ err instanceof GSDError && err.code === GSD_GIT_ERROR
178
+ ? `Switch to ${mainBranch} (e.g. 'git checkout ${mainBranch}'), then remove the worktree manually with /gsd worktree remove ${target} --force.`
179
+ : `Remove the worktree manually with /gsd worktree remove ${target} --force, or run 'git worktree prune' to clean up dangling registrations.`,
180
+ ];
181
+ ctx.ui.notify(cleanupLines.join("\n"), "warning");
182
+ }
183
+ }
184
+ // ─── Subcommand: clean ──────────────────────────────────────────────────────
185
+ async function handleClean(ctx) {
186
+ const basePath = projectRoot();
187
+ const worktrees = listWorktrees(basePath);
188
+ if (worktrees.length === 0) {
189
+ ctx.ui.notify("No worktrees to clean.", "info");
190
+ return;
191
+ }
192
+ const removed = [];
193
+ const kept = [];
194
+ for (const wt of worktrees) {
195
+ const status = getStatus(basePath, wt.name, wt.path);
196
+ if (status.filesChanged === 0 && !status.uncommitted) {
197
+ try {
198
+ removeWorktree(basePath, wt.name, { deleteBranch: true });
199
+ removed.push(wt.name);
200
+ }
201
+ catch (err) {
202
+ const msg = err instanceof Error ? err.message : String(err);
203
+ kept.push(`${wt.name} (failed: ${msg})`);
204
+ }
205
+ }
206
+ else {
207
+ const reason = formatCleanKeepReason(status);
208
+ kept.push(`${wt.name} (${reason})`);
209
+ }
210
+ }
211
+ const lines = [`Cleaned ${removed.length} worktree${removed.length === 1 ? "" : "s"}.`];
212
+ if (removed.length > 0) {
213
+ lines.push("", "Removed:");
214
+ for (const n of removed)
215
+ lines.push(` ✓ ${n}`);
216
+ }
217
+ if (kept.length > 0) {
218
+ lines.push("", "Kept:");
219
+ for (const n of kept)
220
+ lines.push(` ─ ${n}`);
221
+ }
222
+ ctx.ui.notify(lines.join("\n"), "info");
223
+ }
224
+ // ─── Subcommand: remove ─────────────────────────────────────────────────────
225
+ async function handleRemove(args, ctx) {
226
+ const basePath = projectRoot();
227
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
228
+ const force = tokens.includes("--force");
229
+ const name = tokens.find((t) => t !== "--force");
230
+ if (!name) {
231
+ ctx.ui.notify("Usage: /gsd worktree remove <name> [--force]", "warning");
232
+ return;
233
+ }
234
+ const worktrees = listWorktrees(basePath);
235
+ const wt = worktrees.find((w) => w.name === name);
236
+ if (!wt) {
237
+ const available = worktrees.map((w) => w.name).join(", ") || "(none)";
238
+ ctx.ui.notify(`Worktree "${name}" not found.\n\nAvailable: ${available}`, "error");
239
+ return;
240
+ }
241
+ const status = getStatus(basePath, name, wt.path);
242
+ if ((status.filesChanged > 0 || status.uncommitted) && !force) {
243
+ ctx.ui.notify([
244
+ `Worktree "${name}" has pending changes (${formatCleanKeepReason(status)}).`,
245
+ "",
246
+ ` Merge first: /gsd worktree merge ${name}`,
247
+ ` Or force-remove: /gsd worktree remove ${name} --force`,
248
+ ].join("\n"), "warning");
249
+ return;
250
+ }
251
+ try {
252
+ removeWorktree(basePath, name, { deleteBranch: true });
253
+ ctx.ui.notify(`Removed worktree ${name}.`, "info");
254
+ }
255
+ catch (err) {
256
+ const msg = err instanceof Error ? err.message : String(err);
257
+ ctx.ui.notify(`Worktree partially removed: ${msg}\n\nRun 'git worktree prune' to clean up any dangling registrations.`, "error");
258
+ }
259
+ }
260
+ // ─── Help text ──────────────────────────────────────────────────────────────
261
+ const HELP_TEXT = [
262
+ "Usage: /gsd worktree <command> [args]",
263
+ "",
264
+ "Commands:",
265
+ " list Show all worktrees with status",
266
+ " merge [name] Merge a worktree into main, then remove it",
267
+ " remove <name> [--force] Remove a worktree (refuses unmerged changes without --force)",
268
+ " clean Remove all merged/empty worktrees",
269
+ "",
270
+ "The -w flag (CLI only) creates/resumes worktrees on session start:",
271
+ " gsd -w Auto-name a new worktree, or resume the only active one",
272
+ " gsd -w my-feature Create or resume a named worktree",
273
+ ].join("\n");
274
+ // ─── Dispatcher ─────────────────────────────────────────────────────────────
275
+ export async function handleWorktree(args, ctx) {
276
+ const trimmed = args.trim();
277
+ const lowered = trimmed.toLowerCase();
278
+ if (!lowered || lowered === "help" || lowered === "--help" || lowered === "-h") {
279
+ ctx.ui.notify(HELP_TEXT, "info");
280
+ return;
281
+ }
282
+ try {
283
+ if (lowered === "list" || lowered === "ls") {
284
+ await handleList(ctx);
285
+ return;
286
+ }
287
+ if (lowered === "merge" || lowered.startsWith("merge ")) {
288
+ await handleMerge(trimmed.replace(/^merge\s*/i, ""), ctx);
289
+ return;
290
+ }
291
+ if (lowered === "clean") {
292
+ await handleClean(ctx);
293
+ return;
294
+ }
295
+ if (lowered === "remove" ||
296
+ lowered.startsWith("remove ") ||
297
+ lowered === "rm" ||
298
+ lowered.startsWith("rm ")) {
299
+ const stripped = trimmed.replace(/^(remove|rm)\s*/i, "");
300
+ await handleRemove(stripped, ctx);
301
+ return;
302
+ }
303
+ ctx.ui.notify(`Unknown worktree command: ${trimmed}\n\n${HELP_TEXT}`, "warning");
304
+ }
305
+ catch (err) {
306
+ const msg = err instanceof Error ? err.message : String(err);
307
+ ctx.ui.notify(`Worktree command failed: ${msg}`, "error");
308
+ }
309
+ }
@@ -1,3 +1,5 @@
1
+ **Working directory:** `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory.
2
+
1
3
  Discuss milestone {{milestoneId}} ("{{milestoneTitle}}"). Identify gray areas, ask the user about them, and write `{{milestoneId}}-CONTEXT.md` in the milestone directory with the decisions. Use the **Context** output template below. If a `GSD Skill Preferences` block is present in system context, use it to decide which skills to load and follow; do not override required artifact rules.
2
4
 
3
5
  **Structured questions available: {{structuredQuestionsAvailable}}**
@@ -1,5 +1,7 @@
1
1
  # Parallel Slice Research
2
2
 
3
+ **Working directory:** `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory.
4
+
3
5
  You are dispatching parallel research agents for **{{sliceCount}} slices** in milestone **{{mid}} — {{midTitle}}**.
4
6
 
5
7
  ## Slices to Research
@@ -1,5 +1,7 @@
1
1
  You are executing GSD auto-mode.
2
2
 
3
+ **Working directory:** `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory.
4
+
3
5
  ## UNIT: Rewrite Documents — Apply Override(s) for Milestone {{milestoneId}} ("{{milestoneTitle}}")
4
6
 
5
7
  An override was issued by the user that changes a fundamental decision or approach. Your job is to propagate this change across all active planning documents so they are internally consistent and future tasks execute correctly.
@@ -297,6 +297,30 @@ export class WorktreeResolver {
297
297
  */
298
298
  mergeAndExit(milestoneId, ctx) {
299
299
  this.validateMilestoneId(milestoneId);
300
+ // Anchor cwd at the project root before any merge work. Some merge code
301
+ // paths (mergeMilestoneToMain, slice-cadence) chdir explicitly; others
302
+ // (branch-mode, isolation-degraded skip, missing-original-base skip)
303
+ // do not. If the worktree dir is later torn down while cwd still points
304
+ // into it, every subsequent process.cwd() throws ENOENT — and after
305
+ // de73fb43d that surfaces as a session-failed cancel and (in headless
306
+ // mode) terminates the whole gsd process. Best-effort: silent on
307
+ // failure so existing test fixtures that use synthetic paths still pass.
308
+ if (this.s.originalBasePath) {
309
+ try {
310
+ // process.cwd() can throw ENOENT when cwd was removed, so attempt
311
+ // recovery directly.
312
+ process.chdir(this.s.originalBasePath);
313
+ }
314
+ catch (err) {
315
+ debugLog("WorktreeResolver", {
316
+ action: "mergeAndExit",
317
+ phase: "pre-merge-chdir-failed",
318
+ milestoneId,
319
+ originalBasePath: this.s.originalBasePath,
320
+ error: err instanceof Error ? err.message : String(err),
321
+ });
322
+ }
323
+ }
300
324
  // #4764 — telemetry: record start timestamp so we can emit merge duration.
301
325
  const mergeStartedAt = new Date().toISOString();
302
326
  const mergeStartMs = Date.now();
@@ -503,12 +503,6 @@ export default function (pi) {
503
503
  },
504
504
  });
505
505
  // ── Lifecycle ─────────────────────────────────────────────────────────────
506
- pi.on("session_start", async (_event, ctx) => {
507
- const servers = readConfigs();
508
- if (servers.length > 0) {
509
- ctx.ui.notify(`MCP client ready — ${servers.length} server(s) configured`, "info");
510
- }
511
- });
512
506
  pi.on("session_shutdown", async () => {
513
507
  await closeAll();
514
508
  });
@@ -7,6 +7,10 @@ description: Lint and format code. Auto-detects ESLint, Biome, Prettier, or lang
7
7
  Lint and format code in the current project. Auto-detect the project's linter and formatter toolchain, run them against the target files, and report results grouped by severity with actionable fix suggestions.
8
8
  </objective>
9
9
 
10
+ <working_directory_awareness>
11
+ **Before running any `git` or build command:** check whether your dispatch context specifies a working directory (look for "Working directory:" in your initial prompt). If it does and `pwd` does not match it, prefix every git invocation with `-C <that path>` (e.g. `git -C /path/to/worktree diff --name-only`) and run linters/formatters with the explicit path argument. Linting the wrong directory is a silent failure mode.
12
+ </working_directory_awareness>
13
+
10
14
  <arguments>
11
15
  This skill accepts optional arguments after `/lint`:
12
16
 
@@ -23,6 +23,10 @@ The reviewer reads both the diff and the surrounding source files to understand
23
23
  The purpose is to review and report findings. Making changes during review conflates the reviewer and author roles. Present findings and let the user decide what to act on.
24
24
  </analysis_only_rule>
25
25
 
26
+ <working_directory_awareness>
27
+ **Before running any `git` command:** check whether your dispatch context specifies a working directory (look for "Working directory:" in your initial prompt). If it does and `pwd` does not match it, prefix every git invocation with `-C <that path>` (e.g. `git -C /path/to/worktree diff --cached`). Reviewing the wrong directory's diff is a silent failure mode — the review will look correct but cover the wrong code.
28
+ </working_directory_awareness>
29
+
26
30
  <quick_start>
27
31
 
28
32
  <determine_review_scope>
@@ -151,6 +151,9 @@ Failures:
151
151
  **Suggest what to test when no arguments are given.**
152
152
 
153
153
  **A. Check recent changes:**
154
+
155
+ > **Working directory check:** if your dispatch context specifies a working directory and `pwd` does not match it, prefix the git commands below with `-C <that path>` (e.g. `git -C /path/to/worktree diff --name-only HEAD~5`).
156
+
154
157
  - Run `git diff --name-only HEAD~5` to find recently changed files
155
158
  - Run `git diff --name-only --cached` for staged files
156
159
  - Filter to source files (exclude configs, docs, lockfiles)