gsd-pi 2.78.1-dev.8a893322c → 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 (79) hide show
  1. package/dist/cli-auto-routing.d.ts +1 -0
  2. package/dist/cli-auto-routing.js +5 -0
  3. package/dist/cli.js +5 -14
  4. package/dist/resources/.managed-resources-content-hash +1 -1
  5. package/dist/resources/extensions/gsd/auto/run-unit.js +23 -11
  6. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +55 -21
  7. package/dist/resources/extensions/gsd/auto-prompts.js +6 -0
  8. package/dist/resources/extensions/gsd/auto-worktree.js +15 -0
  9. package/dist/resources/extensions/gsd/auto.js +25 -9
  10. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  11. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +2 -0
  12. package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +2 -0
  13. package/dist/resources/extensions/gsd/worktree-resolver.js +24 -0
  14. package/dist/resources/skills/lint/SKILL.md +4 -0
  15. package/dist/resources/skills/review/SKILL.md +4 -0
  16. package/dist/resources/skills/test/SKILL.md +3 -0
  17. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  18. package/dist/web/standalone/.next/BUILD_ID +1 -1
  19. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  20. package/dist/web/standalone/.next/build-manifest.json +2 -2
  21. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  22. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/index.html +1 -1
  39. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  46. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  47. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  48. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  49. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  50. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  51. package/package.json +1 -1
  52. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +278 -0
  53. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  54. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +7 -0
  55. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  56. package/packages/pi-coding-agent/dist/core/agent-session.js +125 -55
  57. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  58. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +319 -0
  59. package/packages/pi-coding-agent/src/core/agent-session.ts +128 -59
  60. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  61. package/src/resources/extensions/gsd/auto/run-unit.ts +23 -11
  62. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +60 -24
  63. package/src/resources/extensions/gsd/auto-prompts.ts +6 -0
  64. package/src/resources/extensions/gsd/auto-worktree.ts +15 -0
  65. package/src/resources/extensions/gsd/auto.ts +23 -6
  66. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  67. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +2 -0
  68. package/src/resources/extensions/gsd/prompts/rewrite-docs.md +2 -0
  69. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -0
  70. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +8 -2
  71. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +12 -6
  72. package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +235 -0
  73. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +85 -0
  74. package/src/resources/extensions/gsd/worktree-resolver.ts +24 -0
  75. package/src/resources/skills/lint/SKILL.md +4 -0
  76. package/src/resources/skills/review/SKILL.md +4 -0
  77. package/src/resources/skills/test/SKILL.md +3 -0
  78. /package/dist/web/standalone/.next/static/{QK8fABiGPmonfTgboN0Y9 → GlYncvckBGG33CSoJaSnB}/_buildManifest.js +0 -0
  79. /package/dist/web/standalone/.next/static/{QK8fABiGPmonfTgboN0Y9 → GlYncvckBGG33CSoJaSnB}/_ssgManifest.js +0 -0
@@ -0,0 +1,235 @@
1
+ import test, { after } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+
7
+ const ownsGsdHome = process.env.GSD_HOME_TEST_OVERRIDE === undefined;
8
+ const previousGsdHome = process.env.GSD_HOME;
9
+ const synthesizedGsdHome = join(tmpdir(), `gsd-test-home-${process.pid}-${Date.now()}`);
10
+ process.env.GSD_HOME = process.env.GSD_HOME_TEST_OVERRIDE
11
+ ?? synthesizedGsdHome;
12
+
13
+ after(() => {
14
+ if (ownsGsdHome) {
15
+ rmSync(synthesizedGsdHome, { recursive: true, force: true });
16
+ }
17
+ if (previousGsdHome === undefined) {
18
+ delete process.env.GSD_HOME;
19
+ } else {
20
+ process.env.GSD_HOME = previousGsdHome;
21
+ }
22
+ });
23
+
24
+ const { dispatchDirectPhase } = await import("../auto-direct-dispatch.ts");
25
+ const {
26
+ buildDiscussMilestonePrompt,
27
+ buildParallelResearchSlicesPrompt,
28
+ buildRewriteDocsPrompt,
29
+ } = await import("../auto-prompts.ts");
30
+ const { invalidateStateCache } = await import("../state.ts");
31
+ const { resolveAgentEnd, runUnit, _resetPendingResolve } = await import("../auto-loop.ts");
32
+
33
+ function writeMilestone(base: string, mid = "M001", title = "Worktree Path Injection"): void {
34
+ const milestoneDir = join(base, ".gsd", "milestones", mid);
35
+ mkdirSync(milestoneDir, { recursive: true });
36
+ writeFileSync(
37
+ join(milestoneDir, `${mid}-CONTEXT.md`),
38
+ `# ${mid}: ${title}\n\nContext.\n`,
39
+ "utf-8",
40
+ );
41
+ writeFileSync(
42
+ join(milestoneDir, `${mid}-ROADMAP.md`),
43
+ [
44
+ `# ${mid}: ${title}`,
45
+ "",
46
+ "## Slices",
47
+ "",
48
+ "- [ ] **S01: First slice** `risk:low` `depends:[]`",
49
+ "",
50
+ ].join("\n"),
51
+ "utf-8",
52
+ );
53
+ }
54
+
55
+ function makeLiveMilestoneWorktree(base: string, mid = "M001"): string {
56
+ const worktreeRoot = join(base, ".gsd", "worktrees", mid);
57
+ mkdirSync(worktreeRoot, { recursive: true });
58
+ writeFileSync(
59
+ join(worktreeRoot, ".git"),
60
+ `gitdir: ${join(base, ".git", "worktrees", mid)}\n`,
61
+ "utf-8",
62
+ );
63
+ writeMilestone(worktreeRoot, mid);
64
+ return worktreeRoot;
65
+ }
66
+
67
+ async function waitFor(condition: () => boolean, label: string): Promise<void> {
68
+ const rawTimeout = process.env.READABLE_WAIT_TIMEOUT_MS;
69
+ const parsedTimeout = rawTimeout === undefined ? NaN : Number.parseInt(rawTimeout, 10);
70
+ const timeoutMs = Number.isFinite(parsedTimeout) && parsedTimeout > 0 ? parsedTimeout : 1000;
71
+ const deadline = Date.now() + timeoutMs;
72
+
73
+ while (Date.now() < deadline) {
74
+ if (condition()) return;
75
+ await new Promise((resolve) => setTimeout(resolve, 5));
76
+ }
77
+ if (condition()) return;
78
+ assert.fail(`Timed out waiting for ${label} after ${timeoutMs}ms`);
79
+ }
80
+
81
+ test("runUnit changes cwd to basePath before creating a new session", async (t) => {
82
+ _resetPendingResolve();
83
+
84
+ const originalCwd = process.cwd();
85
+ const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rununit-base-")));
86
+ const drifted = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rununit-drift-")));
87
+ t.after(() => {
88
+ process.chdir(originalCwd);
89
+ rmSync(base, { recursive: true, force: true });
90
+ rmSync(drifted, { recursive: true, force: true });
91
+ });
92
+
93
+ process.chdir(drifted);
94
+
95
+ let cwdAtNewSession: string | undefined;
96
+ const session = {
97
+ active: true,
98
+ basePath: base,
99
+ verbose: false,
100
+ cmdCtx: {
101
+ newSession: () => {
102
+ cwdAtNewSession = process.cwd();
103
+ return Promise.resolve({ cancelled: false });
104
+ },
105
+ },
106
+ } as any;
107
+ const pi = {
108
+ calls: [] as unknown[],
109
+ sendMessage(...args: unknown[]) {
110
+ this.calls.push(args);
111
+ },
112
+ } as any;
113
+ const ctx = { ui: { notify: () => {} }, model: { id: "test-model" } } as any;
114
+
115
+ const resultPromise = runUnit(ctx, pi, session, "task", "T01", "prompt");
116
+ await waitFor(() => pi.calls.length === 1, "runUnit dispatch");
117
+ resolveAgentEnd({ messages: [{ role: "assistant" }] });
118
+
119
+ const result = await resultPromise;
120
+ assert.equal(result.status, "completed");
121
+ assert.equal(cwdAtNewSession, base);
122
+ });
123
+
124
+ test("runUnit cancels before creating a session when basePath chdir fails", async (t) => {
125
+ _resetPendingResolve();
126
+
127
+ const originalCwd = process.cwd();
128
+ const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rununit-missing-base-")));
129
+ const drifted = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rununit-missing-drift-")));
130
+ rmSync(base, { recursive: true, force: true });
131
+ t.after(() => {
132
+ process.chdir(originalCwd);
133
+ rmSync(drifted, { recursive: true, force: true });
134
+ });
135
+
136
+ process.chdir(drifted);
137
+
138
+ let newSessionCalled = false;
139
+ const session = {
140
+ active: true,
141
+ basePath: base,
142
+ verbose: false,
143
+ cmdCtx: {
144
+ newSession: () => {
145
+ newSessionCalled = true;
146
+ return Promise.resolve({ cancelled: false });
147
+ },
148
+ },
149
+ } as any;
150
+ const pi = {
151
+ calls: [] as unknown[],
152
+ sendMessage(...args: unknown[]) {
153
+ this.calls.push(args);
154
+ },
155
+ } as any;
156
+ const ctx = { ui: { notify: () => {} }, model: { id: "test-model" } } as any;
157
+
158
+ const result = await runUnit(ctx, pi, session, "task", "T01", "prompt");
159
+
160
+ assert.equal(result.status, "cancelled");
161
+ assert.equal(result.errorContext?.category, "session-failed");
162
+ assert.equal(result.errorContext?.isTransient, true);
163
+ assert.match(result.errorContext?.message ?? "", /Failed to chdir to basePath before newSession/);
164
+ assert.ok(result.errorContext?.message.includes(base), "error should include the failed basePath");
165
+ assert.equal(newSessionCalled, false, "newSession must not run after chdir failure");
166
+ assert.equal(pi.calls.length, 0, "unit must not dispatch after chdir failure");
167
+ });
168
+
169
+ test("direct dispatch redirects to the canonical milestone worktree before newSession", async (t) => {
170
+ invalidateStateCache();
171
+
172
+ const originalCwd = process.cwd();
173
+ const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-direct-base-")));
174
+ const drifted = realpathSync(mkdtempSync(join(tmpdir(), "gsd-direct-drift-")));
175
+ writeMilestone(base);
176
+ const worktreeRoot = makeLiveMilestoneWorktree(base);
177
+
178
+ t.after(() => {
179
+ process.chdir(originalCwd);
180
+ rmSync(base, { recursive: true, force: true });
181
+ rmSync(drifted, { recursive: true, force: true });
182
+ invalidateStateCache();
183
+ });
184
+
185
+ process.chdir(drifted);
186
+
187
+ let cwdAtNewSession: string | undefined;
188
+ let sentPrompt: string | undefined;
189
+ const ctx = {
190
+ ui: { notify: () => {} },
191
+ newSession: async () => {
192
+ cwdAtNewSession = process.cwd();
193
+ return { cancelled: false };
194
+ },
195
+ } as any;
196
+ const pi = {
197
+ sendMessage(message: { content: string }) {
198
+ sentPrompt = message.content;
199
+ },
200
+ } as any;
201
+
202
+ await dispatchDirectPhase(ctx, pi, "research-milestone", base);
203
+
204
+ assert.equal(cwdAtNewSession, worktreeRoot);
205
+ assert.equal(process.cwd(), drifted);
206
+ assert.ok(sentPrompt?.includes(worktreeRoot), "prompt should name the canonical worktree root");
207
+ });
208
+
209
+ test("worktree-aware prompt builders include the explicit working directory", async (t) => {
210
+ const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-prompt-base-")));
211
+ writeMilestone(base);
212
+ t.after(() => rmSync(base, { recursive: true, force: true }));
213
+
214
+ const prompts = await Promise.all([
215
+ buildDiscussMilestonePrompt("M001", "Worktree Path Injection", base),
216
+ buildParallelResearchSlicesPrompt(
217
+ "M001",
218
+ "Worktree Path Injection",
219
+ [{ id: "S01", title: "First slice" }],
220
+ base,
221
+ ),
222
+ buildRewriteDocsPrompt(
223
+ "M001",
224
+ "Worktree Path Injection",
225
+ null,
226
+ base,
227
+ [{ change: "Refresh docs", timestamp: "2026-04-27T00:00:00.000Z", appliedAt: "test" }] as any,
228
+ ),
229
+ ]);
230
+
231
+ for (const prompt of prompts) {
232
+ assert.match(prompt, /working directory/i);
233
+ assert.ok(prompt.includes(base), "prompt should include the provided working directory");
234
+ }
235
+ });
@@ -1,5 +1,8 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
+ import { mkdtempSync, rmSync, mkdirSync, realpathSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
3
6
  import {
4
7
  WorktreeResolver,
5
8
  type WorktreeResolverDeps,
@@ -1142,3 +1145,85 @@ test("mergeAndExit propagates non-MergeConflictError to caller (#4380)", () => {
1142
1145
  "non-MergeConflictError must propagate to the caller, not be swallowed",
1143
1146
  );
1144
1147
  });
1148
+
1149
+ // ─── Regression: mergeAndExit anchors cwd at project root before merge work ─
1150
+ // (de73fb43d headless `gsd auto` exits-on-task regression)
1151
+ //
1152
+ // Background: the auto loop runs tasks inside the milestone worktree
1153
+ // (process.cwd() === worktreePath). When the milestone completes, the
1154
+ // worktree dir is torn down. If cwd was still inside it at that moment,
1155
+ // every subsequent process.cwd() throws ENOENT — and after de73fb43d
1156
+ // auto/run-unit.ts:50 turns that ENOENT into a session-failed cancel,
1157
+ // which in headless mode bubbles up to a "Auto-mode stopped" notify
1158
+ // and process.exit(0). mergeAndExit must therefore guarantee cwd is
1159
+ // anchored at the project root regardless of which merge path runs.
1160
+
1161
+ test("mergeAndExit chdirs to project root before merge work (regression: headless gsd auto exit)", () => {
1162
+ // Set up real dirs so process.chdir actually succeeds. realpathSync
1163
+ // canonicalizes the macOS /var → /private/var symlink so equality holds.
1164
+ const projectRoot = realpathSync(mkdtempSync(join(tmpdir(), "gsd-resolver-cwd-")));
1165
+ const worktreePath = join(projectRoot, ".gsd/worktrees/M001");
1166
+ mkdirSync(worktreePath, { recursive: true });
1167
+ const previousCwd = process.cwd();
1168
+
1169
+ try {
1170
+ process.chdir(worktreePath);
1171
+ assert.equal(process.cwd(), worktreePath, "precondition: cwd is in worktree");
1172
+
1173
+ const s = makeSession({
1174
+ basePath: worktreePath,
1175
+ originalBasePath: projectRoot,
1176
+ });
1177
+ const deps = makeDeps({
1178
+ isInAutoWorktree: () => true,
1179
+ getIsolationMode: () => "worktree",
1180
+ });
1181
+ const ctx = makeNotifyCtx();
1182
+ const resolver = new WorktreeResolver(s, deps);
1183
+
1184
+ resolver.mergeAndExit("M001", ctx);
1185
+
1186
+ assert.equal(
1187
+ process.cwd(),
1188
+ projectRoot,
1189
+ "mergeAndExit must leave cwd at the project root, not the (about-to-be-removed) worktree",
1190
+ );
1191
+ } finally {
1192
+ try { process.chdir(previousCwd); } catch { /* best-effort */ }
1193
+ rmSync(projectRoot, { recursive: true, force: true });
1194
+ }
1195
+ });
1196
+
1197
+ test("mergeAndExit anchors cwd even on isolation-degraded skip path", () => {
1198
+ // The skip paths (isolation-degraded, mode-none, missing-original-base)
1199
+ // bypass the per-mode merge helpers entirely. They must still leave cwd
1200
+ // at the project root so a subsequent worktree teardown elsewhere does
1201
+ // not strand cwd in a deleted dir.
1202
+ const projectRoot = realpathSync(mkdtempSync(join(tmpdir(), "gsd-resolver-cwd-degraded-")));
1203
+ const worktreePath = join(projectRoot, ".gsd/worktrees/M001");
1204
+ mkdirSync(worktreePath, { recursive: true });
1205
+ const previousCwd = process.cwd();
1206
+
1207
+ try {
1208
+ process.chdir(worktreePath);
1209
+ const s = makeSession({
1210
+ basePath: worktreePath,
1211
+ originalBasePath: projectRoot,
1212
+ });
1213
+ s.isolationDegraded = true;
1214
+ const deps = makeDeps({ getIsolationMode: () => "worktree" });
1215
+ const ctx = makeNotifyCtx();
1216
+ const resolver = new WorktreeResolver(s, deps);
1217
+
1218
+ resolver.mergeAndExit("M001", ctx);
1219
+
1220
+ assert.equal(
1221
+ process.cwd(),
1222
+ projectRoot,
1223
+ "isolation-degraded skip must still anchor cwd at project root",
1224
+ );
1225
+ } finally {
1226
+ try { process.chdir(previousCwd); } catch { /* best-effort */ }
1227
+ rmSync(projectRoot, { recursive: true, force: true });
1228
+ }
1229
+ });
@@ -404,6 +404,30 @@ export class WorktreeResolver {
404
404
  mergeAndExit(milestoneId: string, ctx: NotifyCtx): void {
405
405
  this.validateMilestoneId(milestoneId);
406
406
 
407
+ // Anchor cwd at the project root before any merge work. Some merge code
408
+ // paths (mergeMilestoneToMain, slice-cadence) chdir explicitly; others
409
+ // (branch-mode, isolation-degraded skip, missing-original-base skip)
410
+ // do not. If the worktree dir is later torn down while cwd still points
411
+ // into it, every subsequent process.cwd() throws ENOENT — and after
412
+ // de73fb43d that surfaces as a session-failed cancel and (in headless
413
+ // mode) terminates the whole gsd process. Best-effort: silent on
414
+ // failure so existing test fixtures that use synthetic paths still pass.
415
+ if (this.s.originalBasePath) {
416
+ try {
417
+ // process.cwd() can throw ENOENT when cwd was removed, so attempt
418
+ // recovery directly.
419
+ process.chdir(this.s.originalBasePath);
420
+ } catch (err) {
421
+ debugLog("WorktreeResolver", {
422
+ action: "mergeAndExit",
423
+ phase: "pre-merge-chdir-failed",
424
+ milestoneId,
425
+ originalBasePath: this.s.originalBasePath,
426
+ error: err instanceof Error ? err.message : String(err),
427
+ });
428
+ }
429
+ }
430
+
407
431
  // #4764 — telemetry: record start timestamp so we can emit merge duration.
408
432
  const mergeStartedAt = new Date().toISOString();
409
433
  const mergeStartMs = Date.now();
@@ -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)