gsd-pi 2.78.1-dev.b6a389b66 → 2.78.1-dev.d8826a445
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/phases.js +7 -2
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +3 -2
- package/dist/resources/extensions/gsd/auto-post-unit.js +7 -1
- package/dist/resources/extensions/gsd/auto-worktree.js +185 -40
- package/dist/resources/extensions/gsd/auto.js +62 -1
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -16
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
- package/dist/resources/extensions/gsd/db-writer.js +96 -16
- package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
- package/dist/resources/extensions/gsd/gsd-db.js +194 -0
- package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
- package/dist/resources/extensions/gsd/guided-flow.js +117 -25
- package/dist/resources/extensions/gsd/metrics.js +287 -1
- package/dist/resources/extensions/gsd/paths.js +79 -8
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/dist/resources/extensions/gsd/templates/project.md +10 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
- package/dist/resources/extensions/gsd/workspace.js +59 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +15 -2
- package/dist/resources/extensions/gsd/write-intercept.js +3 -3
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/README.md +2 -11
- package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
- package/packages/mcp-server/dist/remote-questions.js +28 -0
- package/packages/mcp-server/dist/remote-questions.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts +28 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +94 -4
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/mcp-server.test.ts +226 -0
- package/packages/mcp-server/src/remote-questions.test.ts +103 -0
- package/packages/mcp-server/src/remote-questions.ts +35 -0
- package/packages/mcp-server/src/server.ts +129 -6
- package/packages/mcp-server/src/workflow-tools.ts +1 -1
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/phases.ts +8 -2
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +10 -2
- package/src/resources/extensions/gsd/auto-post-unit.ts +8 -1
- package/src/resources/extensions/gsd/auto-worktree.ts +225 -47
- package/src/resources/extensions/gsd/auto.ts +79 -1
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +17 -17
- package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
- package/src/resources/extensions/gsd/db-writer.ts +113 -17
- package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
- package/src/resources/extensions/gsd/gsd-db.ts +184 -0
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
- package/src/resources/extensions/gsd/guided-flow.ts +154 -25
- package/src/resources/extensions/gsd/metrics.ts +321 -1
- package/src/resources/extensions/gsd/paths.ts +67 -8
- package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/src/resources/extensions/gsd/templates/project.md +10 -0
- package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
- package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
- package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
- package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +371 -0
- package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
- package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
- package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
- package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +74 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +28 -16
- package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +453 -0
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/workspace.test.ts +190 -0
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -52
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
- package/src/resources/extensions/gsd/workspace.ts +95 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +16 -2
- package/src/resources/extensions/gsd/write-intercept.ts +3 -3
- /package/dist/web/standalone/.next/static/{HahrZrc_Xn4wumj0O1Ydp → AT5qi39nKXkdmQIOIoh0f}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{HahrZrc_Xn4wumj0O1Ydp → AT5qi39nKXkdmQIOIoh0f}/_ssgManifest.js +0 -0
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
resetEmptyTurnCounter,
|
|
23
23
|
} from "../guided-flow.ts";
|
|
24
24
|
import { drainLogs } from "../workflow-logger.ts";
|
|
25
|
+
import { resolveMilestoneFile, clearPathCache } from "../paths.ts";
|
|
25
26
|
|
|
26
27
|
// ─── Test harness ──────────────────────────────────────────────────────────
|
|
27
28
|
|
|
@@ -178,6 +179,79 @@ describe("#4573 maybeHandleReadyPhraseWithoutFiles", () => {
|
|
|
178
179
|
}
|
|
179
180
|
});
|
|
180
181
|
|
|
182
|
+
test("stale path cache from a prior listing → fresh writes are detected (regression)", () => {
|
|
183
|
+
// Repro the live binary failure where:
|
|
184
|
+
// 1. paths.ts cached dir listings were populated when M001/ was empty
|
|
185
|
+
// (or the milestone dir didn't yet exist).
|
|
186
|
+
// 2. The LLM then wrote M001-CONTEXT.md and M001-ROADMAP.md via the
|
|
187
|
+
// standard Write tool — which has no awareness of paths.ts caches.
|
|
188
|
+
// 3. maybeHandleReadyPhraseWithoutFiles called resolveMilestoneFile,
|
|
189
|
+
// which read the stale cache and reported the artifacts missing,
|
|
190
|
+
// firing a false rejection nudge until MAX_READY_REJECTS aborted
|
|
191
|
+
// the auto-start with `LLM signaled "ready" 3 times without
|
|
192
|
+
// writing files`.
|
|
193
|
+
//
|
|
194
|
+
// The fix busts the path cache at the top of the validator before
|
|
195
|
+
// re-resolving. This test fails pre-fix (handled === true) because the
|
|
196
|
+
// cache returns the empty listing it captured in step (a).
|
|
197
|
+
const base = mkBase();
|
|
198
|
+
try {
|
|
199
|
+
const mDir = join(base, ".gsd", "milestones", "M001");
|
|
200
|
+
|
|
201
|
+
// (a) Prime the cache with a listing that DOES NOT include M001's
|
|
202
|
+
// CONTEXT/ROADMAP files. mkBase() has already created the M001
|
|
203
|
+
// directory but nothing inside it yet — so this readdir caches an
|
|
204
|
+
// empty entry list keyed by the M001 dir path.
|
|
205
|
+
clearPathCache();
|
|
206
|
+
assert.equal(
|
|
207
|
+
resolveMilestoneFile(base, "M001", "CONTEXT"),
|
|
208
|
+
null,
|
|
209
|
+
"precondition: resolver must report missing before files are written",
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// (b) Write the artifacts directly to disk (simulates the LLM Write
|
|
213
|
+
// tool — no clearPathCache() call between the write and the
|
|
214
|
+
// validator).
|
|
215
|
+
writeFileSync(join(mDir, "M001-CONTEXT.md"), "# ctx");
|
|
216
|
+
writeFileSync(join(mDir, "M001-ROADMAP.md"), "# roadmap");
|
|
217
|
+
|
|
218
|
+
// (c) Sanity: the cache is still stale. Without the fix, the
|
|
219
|
+
// validator would still see the empty cached listing.
|
|
220
|
+
assert.equal(
|
|
221
|
+
resolveMilestoneFile(base, "M001", "CONTEXT"),
|
|
222
|
+
null,
|
|
223
|
+
"stale cache still reports missing pre-clearPathCache",
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// (d) Run the validator. With the fix it busts the cache before
|
|
227
|
+
// resolving and returns false (no nudge). Without the fix it
|
|
228
|
+
// fires the nudge.
|
|
229
|
+
const cap = mkCapture();
|
|
230
|
+
setPendingAutoStart(base, {
|
|
231
|
+
basePath: base,
|
|
232
|
+
milestoneId: "M001",
|
|
233
|
+
ctx: mkCtx(cap),
|
|
234
|
+
pi: mkPi(cap),
|
|
235
|
+
});
|
|
236
|
+
const handled = maybeHandleReadyPhraseWithoutFiles({
|
|
237
|
+
messages: [assistantMsg("Milestone M001 ready.")],
|
|
238
|
+
});
|
|
239
|
+
assert.equal(
|
|
240
|
+
handled,
|
|
241
|
+
false,
|
|
242
|
+
"fresh writes must not trigger the rejection nudge — cache must be busted before resolution",
|
|
243
|
+
);
|
|
244
|
+
assert.equal(cap.messages.length, 0, "no nudge sent");
|
|
245
|
+
assert.equal(
|
|
246
|
+
cap.notifies.length,
|
|
247
|
+
0,
|
|
248
|
+
"no rejection notify when files exist on disk",
|
|
249
|
+
);
|
|
250
|
+
} finally {
|
|
251
|
+
clearPendingAutoStart();
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
181
255
|
test("legacy unprefixed files present → no nudge", () => {
|
|
182
256
|
const base = mkBase();
|
|
183
257
|
try {
|
|
@@ -25,12 +25,15 @@ test("register-hooks unlocks milestone depth verification from question id witho
|
|
|
25
25
|
const dir = makeTempDir("manual");
|
|
26
26
|
const originalCwd = process.cwd();
|
|
27
27
|
process.chdir(dir);
|
|
28
|
-
resetWriteGateState();
|
|
28
|
+
resetWriteGateState(dir);
|
|
29
29
|
|
|
30
30
|
t.after(() => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
try {
|
|
32
|
+
resetWriteGateState(dir);
|
|
33
|
+
} finally {
|
|
34
|
+
process.chdir(originalCwd);
|
|
35
|
+
rmSync(dir, { recursive: true, force: true });
|
|
36
|
+
}
|
|
34
37
|
});
|
|
35
38
|
|
|
36
39
|
const handlers = new Map<string, Array<(event: any, ctx?: any) => Promise<void> | void>>();
|
|
@@ -101,12 +104,15 @@ test("register-hooks clears depth gate when remote (Telegram/Slack/Discord) answ
|
|
|
101
104
|
const dir = makeTempDir("remote");
|
|
102
105
|
const originalCwd = process.cwd();
|
|
103
106
|
process.chdir(dir);
|
|
104
|
-
resetWriteGateState();
|
|
107
|
+
resetWriteGateState(dir);
|
|
105
108
|
|
|
106
109
|
t.after(() => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
try {
|
|
111
|
+
resetWriteGateState(dir);
|
|
112
|
+
} finally {
|
|
113
|
+
process.chdir(originalCwd);
|
|
114
|
+
rmSync(dir, { recursive: true, force: true });
|
|
115
|
+
}
|
|
110
116
|
});
|
|
111
117
|
|
|
112
118
|
const handlers = new Map<string, Array<(event: any, ctx?: any) => Promise<void> | void>>();
|
|
@@ -167,12 +173,15 @@ test("register-hooks returns hard blocker when depth question is cancelled", asy
|
|
|
167
173
|
const dir = makeTempDir("cancelled");
|
|
168
174
|
const originalCwd = process.cwd();
|
|
169
175
|
process.chdir(dir);
|
|
170
|
-
resetWriteGateState();
|
|
176
|
+
resetWriteGateState(dir);
|
|
171
177
|
|
|
172
178
|
t.after(() => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
179
|
+
try {
|
|
180
|
+
resetWriteGateState(dir);
|
|
181
|
+
} finally {
|
|
182
|
+
process.chdir(originalCwd);
|
|
183
|
+
rmSync(dir, { recursive: true, force: true });
|
|
184
|
+
}
|
|
176
185
|
});
|
|
177
186
|
|
|
178
187
|
const handlers = new Map<string, Array<(event: any, ctx?: any) => Promise<any> | any>>();
|
|
@@ -228,12 +237,15 @@ test("register-hooks gates MCP ask_user_questions cancellation before requiremen
|
|
|
228
237
|
const dir = makeTempDir("mcp-cancelled");
|
|
229
238
|
const originalCwd = process.cwd();
|
|
230
239
|
process.chdir(dir);
|
|
231
|
-
resetWriteGateState();
|
|
240
|
+
resetWriteGateState(dir);
|
|
232
241
|
|
|
233
242
|
t.after(() => {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
243
|
+
try {
|
|
244
|
+
resetWriteGateState(dir);
|
|
245
|
+
} finally {
|
|
246
|
+
process.chdir(originalCwd);
|
|
247
|
+
rmSync(dir, { recursive: true, force: true });
|
|
248
|
+
}
|
|
237
249
|
});
|
|
238
250
|
|
|
239
251
|
const handlers = new Map<string, Array<(event: any, ctx?: any) => Promise<any> | any>>();
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// GSD-2 + Regression tests for missing-worktree warning on resume (M4 fix)
|
|
2
|
+
//
|
|
3
|
+
// When paused-session.json records a worktreePath that no longer exists on disk,
|
|
4
|
+
// the resume path must emit a logWarning("session", ...) describing the situation
|
|
5
|
+
// rather than silently falling back to project-root mode.
|
|
6
|
+
//
|
|
7
|
+
// Strategy: drive the exported _warnIfWorktreeMissingForTest seam directly
|
|
8
|
+
// (mirrors the exact conditional used at the two resume sites in auto.ts),
|
|
9
|
+
// and independently verify the scope fallback via createWorkspace/scopeMilestone
|
|
10
|
+
// as in auto-session-scope.test.ts.
|
|
11
|
+
|
|
12
|
+
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
import { mkdtempSync, mkdirSync, rmSync, realpathSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
logWarning,
|
|
20
|
+
peekLogs,
|
|
21
|
+
_resetLogs,
|
|
22
|
+
setStderrLoggingEnabled,
|
|
23
|
+
} from "../workflow-logger.ts";
|
|
24
|
+
import { _warnIfWorktreeMissingForTest } from "../auto.ts";
|
|
25
|
+
import { AutoSession } from "../auto/session.ts";
|
|
26
|
+
import { createWorkspace, scopeMilestone } from "../workspace.ts";
|
|
27
|
+
|
|
28
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
function makeProjectDir(): string {
|
|
31
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-resume-warn-test-")));
|
|
32
|
+
mkdirSync(join(dir, ".gsd", "milestones"), { recursive: true });
|
|
33
|
+
return dir;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Mirror the rebuildScope() fallback from auto.ts when worktree is missing.
|
|
37
|
+
function applyProjectRootScope(
|
|
38
|
+
s: AutoSession,
|
|
39
|
+
projectDir: string,
|
|
40
|
+
milestoneId: string,
|
|
41
|
+
): void {
|
|
42
|
+
const workspace = createWorkspace(projectDir);
|
|
43
|
+
s.scope = scopeMilestone(workspace, milestoneId);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
describe("resume: missing worktree warning emission", () => {
|
|
49
|
+
let projectDir: string;
|
|
50
|
+
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
projectDir = makeProjectDir();
|
|
53
|
+
_resetLogs();
|
|
54
|
+
setStderrLoggingEnabled(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
setStderrLoggingEnabled(true);
|
|
59
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("logWarning is called when worktreePath is set but directory is missing", () => {
|
|
63
|
+
const missingPath = join(projectDir, ".gsd", "worktrees", "M001-nonexistent");
|
|
64
|
+
// missingPath was never created — existsSync returns false
|
|
65
|
+
|
|
66
|
+
const warned = _warnIfWorktreeMissingForTest(missingPath, "M001");
|
|
67
|
+
|
|
68
|
+
assert.equal(warned, true, "_warnIfWorktreeMissingForTest should return true when path is missing");
|
|
69
|
+
|
|
70
|
+
const logs = peekLogs();
|
|
71
|
+
assert.equal(logs.length, 1, "exactly one warning should be emitted");
|
|
72
|
+
assert.equal(logs[0].severity, "warn");
|
|
73
|
+
assert.equal(logs[0].component, "session");
|
|
74
|
+
assert.ok(
|
|
75
|
+
logs[0].message.includes(missingPath),
|
|
76
|
+
`warning message should include the missing path; got: ${logs[0].message}`,
|
|
77
|
+
);
|
|
78
|
+
assert.ok(
|
|
79
|
+
logs[0].message.includes("missing"),
|
|
80
|
+
"warning message should mention 'missing'",
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("logWarning message includes milestone ID", () => {
|
|
85
|
+
const missingPath = join(projectDir, ".gsd", "worktrees", "M042-gone");
|
|
86
|
+
_warnIfWorktreeMissingForTest(missingPath, "M042");
|
|
87
|
+
|
|
88
|
+
const logs = peekLogs();
|
|
89
|
+
assert.equal(logs.length, 1);
|
|
90
|
+
assert.equal(logs[0].context?.milestoneId, "M042");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("logWarning is NOT called when worktreePath is null", () => {
|
|
94
|
+
const warned = _warnIfWorktreeMissingForTest(null, "M001");
|
|
95
|
+
|
|
96
|
+
assert.equal(warned, false);
|
|
97
|
+
assert.equal(peekLogs().length, 0, "no warning when worktreePath is null");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("logWarning is NOT called when worktreePath is undefined", () => {
|
|
101
|
+
const warned = _warnIfWorktreeMissingForTest(undefined, "M001");
|
|
102
|
+
|
|
103
|
+
assert.equal(warned, false);
|
|
104
|
+
assert.equal(peekLogs().length, 0, "no warning when worktreePath is undefined");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("logWarning is NOT called when worktreePath exists on disk", () => {
|
|
108
|
+
const existingWorktree = join(projectDir, ".gsd", "worktrees", "M001");
|
|
109
|
+
mkdirSync(existingWorktree, { recursive: true });
|
|
110
|
+
|
|
111
|
+
const warned = _warnIfWorktreeMissingForTest(existingWorktree, "M001");
|
|
112
|
+
|
|
113
|
+
assert.equal(warned, false, "no warning when path exists");
|
|
114
|
+
assert.equal(peekLogs().length, 0);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("warning message mentions project-root fallback action", () => {
|
|
118
|
+
const missingPath = join(projectDir, ".gsd", "worktrees", "M099-deleted");
|
|
119
|
+
_warnIfWorktreeMissingForTest(missingPath, "M099");
|
|
120
|
+
|
|
121
|
+
const logs = peekLogs();
|
|
122
|
+
assert.equal(logs.length, 1);
|
|
123
|
+
assert.ok(
|
|
124
|
+
logs[0].message.includes("project-root mode"),
|
|
125
|
+
"warning should mention project-root mode fallback",
|
|
126
|
+
);
|
|
127
|
+
assert.ok(
|
|
128
|
+
logs[0].message.includes("gsd-debug"),
|
|
129
|
+
"warning should suggest /gsd-debug recovery action",
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("resume: scope fallback to project-root mode when worktree is missing", () => {
|
|
135
|
+
let s: AutoSession;
|
|
136
|
+
let projectDir: string;
|
|
137
|
+
|
|
138
|
+
beforeEach(() => {
|
|
139
|
+
projectDir = makeProjectDir();
|
|
140
|
+
s = new AutoSession();
|
|
141
|
+
_resetLogs();
|
|
142
|
+
setStderrLoggingEnabled(false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
afterEach(() => {
|
|
146
|
+
setStderrLoggingEnabled(true);
|
|
147
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("scope.workspace.mode is 'project' after fallback from missing worktree", () => {
|
|
151
|
+
const mid = "M001";
|
|
152
|
+
s.originalBasePath = projectDir;
|
|
153
|
+
s.currentMilestoneId = mid;
|
|
154
|
+
|
|
155
|
+
// Simulate auto.ts resume path: worktreePath is set but missing → use projectDir
|
|
156
|
+
const missingPath = join(projectDir, ".gsd", "worktrees", mid);
|
|
157
|
+
_warnIfWorktreeMissingForTest(missingPath, mid);
|
|
158
|
+
|
|
159
|
+
// Fallback: use originalBasePath (project root)
|
|
160
|
+
applyProjectRootScope(s, projectDir, mid);
|
|
161
|
+
|
|
162
|
+
assert.ok(s.scope, "scope should be set after fallback");
|
|
163
|
+
assert.equal(s.scope.workspace.mode, "project");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("scope.milestoneId is preserved after project-root fallback", () => {
|
|
167
|
+
const mid = "M002";
|
|
168
|
+
s.originalBasePath = projectDir;
|
|
169
|
+
s.currentMilestoneId = mid;
|
|
170
|
+
|
|
171
|
+
const missingPath = join(projectDir, ".gsd", "worktrees", mid);
|
|
172
|
+
_warnIfWorktreeMissingForTest(missingPath, mid);
|
|
173
|
+
|
|
174
|
+
applyProjectRootScope(s, projectDir, mid);
|
|
175
|
+
|
|
176
|
+
assert.ok(s.scope, "scope should be set");
|
|
177
|
+
assert.equal(s.scope.milestoneId, mid);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("does not throw when worktree path is missing and scope fallback is applied", () => {
|
|
181
|
+
const mid = "M003";
|
|
182
|
+
s.originalBasePath = projectDir;
|
|
183
|
+
s.currentMilestoneId = mid;
|
|
184
|
+
|
|
185
|
+
const missingPath = join(projectDir, ".gsd", "worktrees", mid);
|
|
186
|
+
|
|
187
|
+
assert.doesNotThrow(() => {
|
|
188
|
+
_warnIfWorktreeMissingForTest(missingPath, mid);
|
|
189
|
+
applyProjectRootScope(s, projectDir, mid);
|
|
190
|
+
}, "resume with missing worktree must not throw");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("warning is emitted once per missing worktree — no double-emission", () => {
|
|
194
|
+
const mid = "M004";
|
|
195
|
+
const missingPath = join(projectDir, ".gsd", "worktrees", mid);
|
|
196
|
+
|
|
197
|
+
_warnIfWorktreeMissingForTest(missingPath, mid);
|
|
198
|
+
|
|
199
|
+
// Simulating the second call as would happen if the resume-re-entry site
|
|
200
|
+
// also fires (e.g. pausedSession and freshStartAssessment both carry the path)
|
|
201
|
+
_warnIfWorktreeMissingForTest(missingPath, mid);
|
|
202
|
+
|
|
203
|
+
const logs = peekLogs();
|
|
204
|
+
// Two calls → two warnings (one per site — consistent with the two sites in auto.ts)
|
|
205
|
+
assert.equal(logs.length, 2, "each call to the seam emits one warning");
|
|
206
|
+
assert.equal(logs[0].component, "session");
|
|
207
|
+
assert.equal(logs[1].component, "session");
|
|
208
|
+
});
|
|
209
|
+
});
|