gsd-pi 2.67.0-dev.1cd1e0f → 2.67.0-dev.2367d7e
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/README.md +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +155 -70
- package/dist/resources/extensions/gsd/auto/phases.js +17 -0
- package/dist/resources/extensions/gsd/auto/session.js +10 -0
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +12 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto-start.js +16 -30
- package/dist/resources/extensions/gsd/auto-worktree.js +62 -15
- package/dist/resources/extensions/gsd/auto.js +121 -59
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +11 -435
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -4
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +7 -64
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +7 -2
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +88 -8
- package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +39 -25
- package/dist/resources/extensions/gsd/commands/index.js +8 -1
- package/dist/resources/extensions/gsd/commands-mcp-status.js +43 -7
- package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -4
- package/dist/resources/extensions/gsd/doctor-proactive.js +3 -3
- package/dist/resources/extensions/gsd/doctor.js +8 -4
- package/dist/resources/extensions/gsd/gsd-db.js +11 -0
- package/dist/resources/extensions/gsd/guided-flow.js +56 -31
- package/dist/resources/extensions/gsd/init-wizard.js +37 -0
- package/dist/resources/extensions/gsd/interrupted-session.js +146 -0
- package/dist/resources/extensions/gsd/mcp-project-config.js +83 -0
- package/dist/resources/extensions/gsd/state.js +7 -2
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +508 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +18 -3
- package/dist/resources/extensions/gsd/workflow-mcp.js +261 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +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/page_client-reference-manifest.js +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 +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
- 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/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-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/.next/static/chunks/2826.821e01b07d92e948.js +9 -0
- package/dist/web/standalone/.next/static/chunks/app/{page-0c485498795110d6.js → page-f1e30ab6bb269149.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{webpack-b49b09f97429b5d0.js → webpack-6e4d7e9a4f57bed4.js} +1 -1
- package/package.json +4 -2
- package/packages/mcp-server/README.md +38 -0
- package/packages/mcp-server/dist/cli.d.ts +9 -0
- package/packages/mcp-server/dist/cli.d.ts.map +1 -0
- package/packages/mcp-server/dist/cli.js +58 -0
- package/packages/mcp-server/dist/cli.js.map +1 -0
- package/packages/mcp-server/dist/index.d.ts +20 -0
- package/packages/mcp-server/dist/index.d.ts.map +1 -0
- package/packages/mcp-server/dist/index.js +14 -0
- package/packages/mcp-server/dist/index.js.map +1 -0
- package/packages/mcp-server/dist/readers/captures.d.ts +25 -0
- package/packages/mcp-server/dist/readers/captures.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/captures.js +67 -0
- package/packages/mcp-server/dist/readers/captures.js.map +1 -0
- package/packages/mcp-server/dist/readers/doctor-lite.d.ts +20 -0
- package/packages/mcp-server/dist/readers/doctor-lite.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/doctor-lite.js +173 -0
- package/packages/mcp-server/dist/readers/doctor-lite.js.map +1 -0
- package/packages/mcp-server/dist/readers/index.d.ts +14 -0
- package/packages/mcp-server/dist/readers/index.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/index.js +10 -0
- package/packages/mcp-server/dist/readers/index.js.map +1 -0
- package/packages/mcp-server/dist/readers/knowledge.d.ts +18 -0
- package/packages/mcp-server/dist/readers/knowledge.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/knowledge.js +82 -0
- package/packages/mcp-server/dist/readers/knowledge.js.map +1 -0
- package/packages/mcp-server/dist/readers/metrics.d.ts +32 -0
- package/packages/mcp-server/dist/readers/metrics.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/metrics.js +74 -0
- package/packages/mcp-server/dist/readers/metrics.js.map +1 -0
- package/packages/mcp-server/dist/readers/paths.d.ts +42 -0
- package/packages/mcp-server/dist/readers/paths.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/paths.js +199 -0
- package/packages/mcp-server/dist/readers/paths.js.map +1 -0
- package/packages/mcp-server/dist/readers/roadmap.d.ts +26 -0
- package/packages/mcp-server/dist/readers/roadmap.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/roadmap.js +194 -0
- package/packages/mcp-server/dist/readers/roadmap.js.map +1 -0
- package/packages/mcp-server/dist/readers/state.d.ts +43 -0
- package/packages/mcp-server/dist/readers/state.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/state.js +184 -0
- package/packages/mcp-server/dist/readers/state.js.map +1 -0
- package/packages/mcp-server/dist/server.d.ts +28 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -0
- package/packages/mcp-server/dist/server.js +319 -0
- package/packages/mcp-server/dist/server.js.map +1 -0
- package/packages/mcp-server/dist/session-manager.d.ts +54 -0
- package/packages/mcp-server/dist/session-manager.d.ts.map +1 -0
- package/packages/mcp-server/dist/session-manager.js +284 -0
- package/packages/mcp-server/dist/session-manager.js.map +1 -0
- package/packages/mcp-server/dist/types.d.ts +61 -0
- package/packages/mcp-server/dist/types.d.ts.map +1 -0
- package/packages/mcp-server/dist/types.js +11 -0
- package/packages/mcp-server/dist/types.js.map +1 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts +9 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -0
- package/packages/mcp-server/dist/workflow-tools.js +532 -0
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -0
- package/packages/mcp-server/src/server.ts +6 -2
- package/packages/mcp-server/src/workflow-tools.test.ts +976 -0
- package/packages/mcp-server/src/workflow-tools.ts +997 -0
- package/packages/mcp-server/tsconfig.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +14 -6
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +53 -0
- package/packages/pi-agent-core/src/agent-loop.ts +20 -6
- package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts +43 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.js +208 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.js +227 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/index.js +1 -0
- package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +28 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +19 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +14 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -12
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/contextual-tips.test.ts +259 -0
- package/packages/pi-coding-agent/src/core/contextual-tips.ts +232 -0
- package/packages/pi-coding-agent/src/core/index.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +54 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -12
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +21 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +19 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +19 -15
- package/packages/rpc-client/dist/index.d.ts +10 -0
- package/packages/rpc-client/dist/index.d.ts.map +1 -0
- package/packages/rpc-client/dist/index.js +9 -0
- package/packages/rpc-client/dist/index.js.map +1 -0
- package/packages/rpc-client/dist/jsonl.d.ts +17 -0
- package/packages/rpc-client/dist/jsonl.d.ts.map +1 -0
- package/packages/rpc-client/dist/jsonl.js +54 -0
- package/packages/rpc-client/dist/jsonl.js.map +1 -0
- package/packages/rpc-client/dist/rpc-client.d.ts +259 -0
- package/packages/rpc-client/dist/rpc-client.d.ts.map +1 -0
- package/packages/rpc-client/dist/rpc-client.js +541 -0
- package/packages/rpc-client/dist/rpc-client.js.map +1 -0
- package/packages/rpc-client/dist/rpc-client.test.d.ts +2 -0
- package/packages/rpc-client/dist/rpc-client.test.d.ts.map +1 -0
- package/packages/rpc-client/dist/rpc-client.test.js +477 -0
- package/packages/rpc-client/dist/rpc-client.test.js.map +1 -0
- package/packages/rpc-client/dist/rpc-types.d.ts +566 -0
- package/packages/rpc-client/dist/rpc-types.d.ts.map +1 -0
- package/packages/rpc-client/dist/rpc-types.js +12 -0
- package/packages/rpc-client/dist/rpc-types.js.map +1 -0
- package/scripts/ensure-workspace-builds.cjs +2 -0
- package/scripts/link-workspace-packages.cjs +21 -14
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +193 -93
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +173 -79
- package/src/resources/extensions/gsd/auto/phases.ts +25 -0
- package/src/resources/extensions/gsd/auto/session.ts +10 -0
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +20 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto-start.ts +23 -55
- package/src/resources/extensions/gsd/auto-worktree.ts +59 -15
- package/src/resources/extensions/gsd/auto.ts +133 -64
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +22 -435
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -5
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +7 -72
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -2
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +122 -6
- package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +53 -26
- package/src/resources/extensions/gsd/commands/index.ts +7 -1
- package/src/resources/extensions/gsd/commands-mcp-status.ts +53 -7
- package/src/resources/extensions/gsd/doctor-git-checks.ts +4 -4
- package/src/resources/extensions/gsd/doctor-proactive.ts +3 -3
- package/src/resources/extensions/gsd/doctor.ts +9 -5
- package/src/resources/extensions/gsd/gsd-db.ts +12 -0
- package/src/resources/extensions/gsd/guided-flow.ts +66 -36
- package/src/resources/extensions/gsd/init-wizard.ts +40 -0
- package/src/resources/extensions/gsd/interrupted-session.ts +224 -0
- package/src/resources/extensions/gsd/mcp-project-config.ts +128 -0
- package/src/resources/extensions/gsd/state.ts +7 -1
- package/src/resources/extensions/gsd/tests/auto-project-root-env.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +668 -2
- package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +14 -4
- package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/core-overlay-fallback.test.ts +101 -0
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +380 -2
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/integration/doctor-fixlevel.test.ts +52 -1
- package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +2 -9
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +0 -33
- package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +169 -0
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +146 -0
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +136 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +500 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +625 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +629 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +19 -3
- package/src/resources/extensions/gsd/workflow-mcp.ts +320 -0
- package/dist/web/standalone/.next/static/chunks/6502.b804e48b7919f55e.js +0 -9
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts +0 -13
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts.map +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js +0 -27
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js.map +0 -1
- package/packages/pi-coding-agent/src/modes/interactive/provider-auth-setup.ts +0 -40
- /package/dist/web/standalone/.next/static/{PHqEommYRR8CRn3i84CGM → WMDT_0C0XDkBKtsAI_AX4}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{PHqEommYRR8CRn3i84CGM → WMDT_0C0XDkBKtsAI_AX4}/_ssgManifest.js +0 -0
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
openDatabase,
|
|
8
8
|
closeDatabase,
|
|
9
9
|
isDbAvailable,
|
|
10
|
+
wasDbOpenAttempted,
|
|
10
11
|
getDbProvider,
|
|
11
12
|
insertDecision,
|
|
12
13
|
getDecisionById,
|
|
@@ -346,6 +347,17 @@ describe('gsd-db', () => {
|
|
|
346
347
|
assert.deepStrictEqual(ar, [], 'getActiveRequirements returns [] when DB closed');
|
|
347
348
|
});
|
|
348
349
|
|
|
350
|
+
test('gsd-db: wasDbOpenAttempted tracks openDatabase calls', () => {
|
|
351
|
+
// wasDbOpenAttempted should return true once openDatabase has been called
|
|
352
|
+
// (previous tests in this suite already called openDatabase, so the flag is set)
|
|
353
|
+
assert.ok(wasDbOpenAttempted(), 'wasDbOpenAttempted should be true after openDatabase was called');
|
|
354
|
+
|
|
355
|
+
// Verify the flag persists even after closeDatabase
|
|
356
|
+
closeDatabase();
|
|
357
|
+
assert.ok(!isDbAvailable(), 'DB should not be available after close');
|
|
358
|
+
assert.ok(wasDbOpenAttempted(), 'wasDbOpenAttempted should remain true after closeDatabase');
|
|
359
|
+
});
|
|
360
|
+
|
|
349
361
|
// ─── Final Report ──────────────────────────────────────────────────────────
|
|
350
362
|
|
|
351
363
|
});
|
|
@@ -100,7 +100,7 @@ describe("#2985 Bug 4 — getDiscussionMilestoneId must be keyed by basePath", (
|
|
|
100
100
|
});
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
-
test("checkAutoStartAfterDiscuss
|
|
103
|
+
test("checkAutoStartAfterDiscuss ignores missing manifest for single-milestone discuss on established project", () => {
|
|
104
104
|
const base = mkdtempSync(join(tmpdir(), "gsd-auto-start-manifest-"));
|
|
105
105
|
try {
|
|
106
106
|
const gsdDir = join(base, ".gsd");
|
|
@@ -123,7 +123,7 @@ test("checkAutoStartAfterDiscuss fails closed when a multi-milestone manifest is
|
|
|
123
123
|
});
|
|
124
124
|
|
|
125
125
|
const started = checkAutoStartAfterDiscuss();
|
|
126
|
-
assert.equal(started,
|
|
126
|
+
assert.equal(started, true, "project history alone should not require a manifest");
|
|
127
127
|
} finally {
|
|
128
128
|
clearPendingAutoStart();
|
|
129
129
|
rmSync(base, { recursive: true, force: true });
|
|
@@ -15,7 +15,7 @@ import { tmpdir } from "node:os";
|
|
|
15
15
|
import test from "node:test";
|
|
16
16
|
import assert from "node:assert/strict";
|
|
17
17
|
import { runGSDDoctor } from "../../doctor.ts";
|
|
18
|
-
import { closeDatabase } from "../../gsd-db.ts";
|
|
18
|
+
import { closeDatabase, insertMilestone, insertSlice, openDatabase } from "../../gsd-db.ts";
|
|
19
19
|
|
|
20
20
|
function makeTmp(name: string): string {
|
|
21
21
|
const dir = join(tmpdir(), `doctor-fixlevel-${name}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
@@ -177,6 +177,57 @@ test("legacy roadmap fallback: future slices are treated as pending, active slic
|
|
|
177
177
|
);
|
|
178
178
|
});
|
|
179
179
|
|
|
180
|
+
test("db skipped slices do not report missing directories", async (t) => {
|
|
181
|
+
const tmp = makeTmp("skipped-slice-dir");
|
|
182
|
+
t.after(() => {
|
|
183
|
+
try { closeDatabase(); } catch { /* noop */ }
|
|
184
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const gsd = join(tmp, ".gsd");
|
|
188
|
+
const m = join(gsd, "milestones", "M001");
|
|
189
|
+
mkdirSync(m, { recursive: true });
|
|
190
|
+
|
|
191
|
+
writeFileSync(join(m, "M001-ROADMAP.md"), `# M001: Test
|
|
192
|
+
|
|
193
|
+
## Slices
|
|
194
|
+
|
|
195
|
+
- [ ] **S05: Skipped Slice** \`risk:low\` \`depends:[]\`
|
|
196
|
+
> Intentionally skipped
|
|
197
|
+
`);
|
|
198
|
+
|
|
199
|
+
openDatabase(join(gsd, "gsd.db"));
|
|
200
|
+
insertMilestone({ id: "M001", title: "Test", status: "active" });
|
|
201
|
+
insertSlice({ id: "S05", milestoneId: "M001", title: "Skipped Slice", status: "skipped", sequence: 5 });
|
|
202
|
+
|
|
203
|
+
const report = await runGSDDoctor(tmp, { scope: "M001" });
|
|
204
|
+
const missingDirIssues = report.issues.filter(
|
|
205
|
+
i =>
|
|
206
|
+
(i.code === "missing_slice_dir" || i.code === "missing_tasks_dir") &&
|
|
207
|
+
i.unitId === "M001/S05",
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
assert.deepStrictEqual(
|
|
211
|
+
missingDirIssues,
|
|
212
|
+
[],
|
|
213
|
+
"skipped slices should not require slice or tasks directories",
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("doctor source treats skipped DB slices as closed and directory-optional", () => {
|
|
218
|
+
const doctorSource = readFileSync(join(process.cwd(), "src/resources/extensions/gsd/doctor.ts"), "utf8");
|
|
219
|
+
assert.match(
|
|
220
|
+
doctorSource,
|
|
221
|
+
/done:\s*isClosedStatus\(s\.status\)/,
|
|
222
|
+
"doctor should normalize skipped DB slices through isClosedStatus()",
|
|
223
|
+
);
|
|
224
|
+
assert.match(
|
|
225
|
+
doctorSource,
|
|
226
|
+
/if \(slice\.pending \|\| slice\.skipped\) continue;/,
|
|
227
|
+
"doctor should skip missing-directory checks for skipped slices",
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
|
|
180
231
|
test("fixLevel:all — delimiter_in_title still fixable", async (t) => {
|
|
181
232
|
const tmp = makeTmp("delimiter-fix");
|
|
182
233
|
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
@@ -661,10 +661,9 @@ describe('doctor-git', async () => {
|
|
|
661
661
|
env: { ...process.env, GIT_COMMITTER_DATE: pastDate },
|
|
662
662
|
});
|
|
663
663
|
|
|
664
|
-
// Modify
|
|
665
|
-
//
|
|
664
|
+
// Modify an already-tracked file (nativeAddTracked uses git add -u,
|
|
665
|
+
// which only stages tracked files — new untracked files are not staged)
|
|
666
666
|
writeFileSync(join(dir, "README.md"), "# test\nmodified content\n");
|
|
667
|
-
writeFileSync(join(dir, "new-untracked.ts"), "export const preserved = true;\n");
|
|
668
667
|
|
|
669
668
|
const detect = await runGSDDoctor(dir);
|
|
670
669
|
const staleIssues = detect.issues.filter(i => i.code === "stale_uncommitted_changes");
|
|
@@ -682,12 +681,6 @@ describe('doctor-git', async () => {
|
|
|
682
681
|
// Verify the snapshot commit was created with the gsd snapshot tag
|
|
683
682
|
const log = run("git log -1 --oneline", dir);
|
|
684
683
|
assert.ok(log.includes("gsd snapshot"), "commit is tagged with gsd snapshot");
|
|
685
|
-
|
|
686
|
-
const files = run("git show --name-only --format= HEAD", dir);
|
|
687
|
-
assert.ok(files.includes("README.md"), "snapshot keeps tracked modifications");
|
|
688
|
-
assert.ok(files.includes("new-untracked.ts"), "snapshot also includes new untracked files");
|
|
689
|
-
const status = run("git status --short", dir);
|
|
690
|
-
assert.ok(!status.includes("new-untracked.ts"), "snapshot does not leave the new source file untracked");
|
|
691
684
|
});
|
|
692
685
|
|
|
693
686
|
// ─── Test: stale_uncommitted_changes NOT flagged when recent commit ──
|
|
@@ -219,39 +219,6 @@ describe('doctor-proactive', async () => {
|
|
|
219
219
|
assert.ok(result.fixesApplied.some((f: string) => f.includes("STATE.md")), "reports STATE.md status as info");
|
|
220
220
|
});
|
|
221
221
|
|
|
222
|
-
test('health gate: pre-dispatch snapshot includes new untracked files', async () => {
|
|
223
|
-
const dir = createRepoWithActiveMilestone();
|
|
224
|
-
cleanups.push(dir);
|
|
225
|
-
|
|
226
|
-
const pastDate = new Date(Date.now() - 45 * 60 * 1000).toISOString();
|
|
227
|
-
run(`git commit --amend --no-edit --date="${pastDate}"`, dir);
|
|
228
|
-
execSync(`git commit --amend --no-edit`, {
|
|
229
|
-
cwd: dir,
|
|
230
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
231
|
-
encoding: "utf-8",
|
|
232
|
-
env: { ...process.env, GIT_COMMITTER_DATE: pastDate },
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
writeFileSync(join(dir, "README.md"), "# test\nmodified content\n");
|
|
236
|
-
writeFileSync(join(dir, "new-untracked.ts"), "export const preserved = true;\n");
|
|
237
|
-
|
|
238
|
-
const result = await preDispatchHealthGate(dir);
|
|
239
|
-
assert.ok(result.proceed, "dispatch still proceeds after snapshotting");
|
|
240
|
-
assert.ok(
|
|
241
|
-
result.fixesApplied.some((f: string) => f.includes("gsd snapshot")),
|
|
242
|
-
"pre-dispatch gate creates a snapshot commit",
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
const log = run("git log -1 --oneline", dir);
|
|
246
|
-
assert.ok(log.includes("gsd snapshot"), "snapshot commit is created");
|
|
247
|
-
|
|
248
|
-
const files = run("git show --name-only --format= HEAD", dir);
|
|
249
|
-
assert.ok(files.includes("README.md"), "snapshot keeps tracked modifications");
|
|
250
|
-
assert.ok(files.includes("new-untracked.ts"), "snapshot also includes new untracked files");
|
|
251
|
-
const status = run("git status --short", dir);
|
|
252
|
-
assert.ok(!status.includes("new-untracked.ts"), "snapshot does not leave the new source file untracked");
|
|
253
|
-
});
|
|
254
|
-
|
|
255
222
|
test('health gate: stale crash lock auto-cleared', async () => {
|
|
256
223
|
const dir = realpathSync(mkdtempSync(join(tmpdir(), "doc-proactive-")));
|
|
257
224
|
cleanups.push(dir);
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD-2 — Regression tests for merge cwd restore (#2929)
|
|
3
|
+
* merge-cwd-restore.test.ts — Regression tests for #2929.
|
|
4
|
+
*
|
|
5
|
+
* Verifies:
|
|
6
|
+
* 1. MergeConflictError restores process.cwd() to the pre-merge directory.
|
|
7
|
+
* 2. autoCommitDirtyState does not run on the integration branch when cwd
|
|
8
|
+
* leaked there from a prior failed merge (parallel mode).
|
|
9
|
+
*
|
|
10
|
+
* Bug: PR #2298 added a stash lifecycle around mergeMilestoneToMain but the
|
|
11
|
+
* MergeConflictError throw path omitted the process.chdir(previousCwd) that
|
|
12
|
+
* the dirty-working-tree and divergence handlers both include. In parallel
|
|
13
|
+
* merge sequences, this left cwd on the integration branch, causing the next
|
|
14
|
+
* merge's autoCommitDirtyState to commit dirty files from OTHER milestones
|
|
15
|
+
* onto main.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
19
|
+
import assert from "node:assert/strict";
|
|
20
|
+
import {
|
|
21
|
+
mkdtempSync,
|
|
22
|
+
mkdirSync,
|
|
23
|
+
writeFileSync,
|
|
24
|
+
rmSync,
|
|
25
|
+
realpathSync,
|
|
26
|
+
} from "node:fs";
|
|
27
|
+
import { join } from "node:path";
|
|
28
|
+
import { tmpdir } from "node:os";
|
|
29
|
+
import { execSync } from "node:child_process";
|
|
30
|
+
|
|
31
|
+
import { mergeMilestoneToMain } from "../../auto-worktree.ts";
|
|
32
|
+
import { MergeConflictError } from "../../git-service.ts";
|
|
33
|
+
|
|
34
|
+
function run(cmd: string, cwd: string): string {
|
|
35
|
+
return execSync(cmd, {
|
|
36
|
+
cwd,
|
|
37
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
38
|
+
encoding: "utf-8",
|
|
39
|
+
}).trim();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createTempRepo(): string {
|
|
43
|
+
const dir = realpathSync(
|
|
44
|
+
mkdtempSync(join(tmpdir(), "merge-cwd-restore-test-")),
|
|
45
|
+
);
|
|
46
|
+
run("git init -b main", dir);
|
|
47
|
+
run("git config user.email test@test.com", dir);
|
|
48
|
+
run("git config user.name Test", dir);
|
|
49
|
+
writeFileSync(join(dir, "README.md"), "# test\n");
|
|
50
|
+
writeFileSync(join(dir, ".gitignore"), ".gsd/worktrees/\n");
|
|
51
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
52
|
+
writeFileSync(join(dir, ".gsd", "STATE.md"), "# State\n");
|
|
53
|
+
run("git add .", dir);
|
|
54
|
+
run("git commit -m init", dir);
|
|
55
|
+
return dir;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function makeRoadmap(mid: string, title: string): string {
|
|
59
|
+
return [
|
|
60
|
+
`# ${mid}: Test milestone`,
|
|
61
|
+
"",
|
|
62
|
+
"## Slices",
|
|
63
|
+
"- [x] **S01: Test slice**",
|
|
64
|
+
].join("\n");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
describe("merge cwd restore (#2929)", () => {
|
|
68
|
+
let repo: string;
|
|
69
|
+
let savedCwd: string;
|
|
70
|
+
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
savedCwd = process.cwd();
|
|
73
|
+
repo = createTempRepo();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
afterEach(() => {
|
|
77
|
+
process.chdir(savedCwd);
|
|
78
|
+
try { run("git reset --hard HEAD", repo); } catch { /* */ }
|
|
79
|
+
rmSync(repo, { recursive: true, force: true });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
83
|
+
// Test 1: MergeConflictError restores cwd (#2929 bug 2)
|
|
84
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
test("MergeConflictError restores cwd to pre-merge directory", () => {
|
|
87
|
+
// Create milestone branch that modifies README.md
|
|
88
|
+
run("git checkout -b milestone/M010", repo);
|
|
89
|
+
writeFileSync(join(repo, "README.md"), "# M010 version\n");
|
|
90
|
+
run("git add .", repo);
|
|
91
|
+
run('git commit -m "M010 changes README"', repo);
|
|
92
|
+
run("git checkout main", repo);
|
|
93
|
+
|
|
94
|
+
// Modify README.md on main to create a conflict
|
|
95
|
+
writeFileSync(join(repo, "README.md"), "# main version (diverged)\n");
|
|
96
|
+
run("git add .", repo);
|
|
97
|
+
run('git commit -m "main diverges README"', repo);
|
|
98
|
+
|
|
99
|
+
// cwd must be repo root (simulates parallel-merge calling from project root)
|
|
100
|
+
process.chdir(repo);
|
|
101
|
+
const cwdBefore = process.cwd();
|
|
102
|
+
|
|
103
|
+
let caught: unknown = null;
|
|
104
|
+
try {
|
|
105
|
+
mergeMilestoneToMain(repo, "M010", makeRoadmap("M010", "Conflict test"));
|
|
106
|
+
} catch (err) {
|
|
107
|
+
caught = err;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Should have thrown a MergeConflictError
|
|
111
|
+
assert.ok(caught instanceof MergeConflictError, "expected MergeConflictError");
|
|
112
|
+
|
|
113
|
+
// Critical: cwd must be restored to where it was before the merge
|
|
114
|
+
const cwdAfter = process.cwd();
|
|
115
|
+
assert.equal(
|
|
116
|
+
cwdAfter,
|
|
117
|
+
cwdBefore,
|
|
118
|
+
"cwd should be restored after MergeConflictError — was left on integration branch before fix",
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
123
|
+
// Test 2: autoCommitDirtyState skipped when on integration branch (#2929 bug 1)
|
|
124
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
test("autoCommitDirtyState does not commit on integration branch in worktree mode", () => {
|
|
127
|
+
// Create milestone branch with real work
|
|
128
|
+
run("git checkout -b milestone/M010", repo);
|
|
129
|
+
writeFileSync(join(repo, "m010.ts"), "export const m010 = true;\n");
|
|
130
|
+
run("git add .", repo);
|
|
131
|
+
run('git commit -m "M010 work"', repo);
|
|
132
|
+
run("git checkout main", repo);
|
|
133
|
+
|
|
134
|
+
// Simulate the parallel-mode state: cwd is on main with dirty files
|
|
135
|
+
// from another milestone (as if a prior merge's MergeConflictError
|
|
136
|
+
// left cwd on main and syncStateToProjectRoot wrote these files).
|
|
137
|
+
writeFileSync(join(repo, "dirty-from-m020.txt"), "should not be committed\n");
|
|
138
|
+
|
|
139
|
+
// Set up roadmap so mergeMilestoneToMain can find milestone metadata
|
|
140
|
+
mkdirSync(join(repo, ".gsd", "milestones", "M010"), { recursive: true });
|
|
141
|
+
writeFileSync(
|
|
142
|
+
join(repo, ".gsd", "milestones", "M010", "M010-ROADMAP.md"),
|
|
143
|
+
makeRoadmap("M010", "First milestone"),
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
process.chdir(repo);
|
|
147
|
+
|
|
148
|
+
const result = mergeMilestoneToMain(
|
|
149
|
+
repo,
|
|
150
|
+
"M010",
|
|
151
|
+
makeRoadmap("M010", "First milestone"),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
assert.ok(result.commitMessage.includes("M010"), "commit should be for M010");
|
|
155
|
+
|
|
156
|
+
// Verify the squash merge brought M010's work file
|
|
157
|
+
const mergeLog = run("git log --oneline --diff-filter=A -- m010.ts", repo);
|
|
158
|
+
assert.ok(mergeLog.length > 0, "m010.ts should be in a commit on main");
|
|
159
|
+
|
|
160
|
+
// The dirty file should NOT appear in the squash merge commit.
|
|
161
|
+
const squashCommit = run("git log --format=%H --grep='GSD-Milestone: M010' -1", repo);
|
|
162
|
+
assert.ok(squashCommit.length > 0, "should find the squash merge commit");
|
|
163
|
+
const filesInSquash = run(`git diff-tree --no-commit-id --name-only -r ${squashCommit}`, repo);
|
|
164
|
+
assert.ok(
|
|
165
|
+
!filesInSquash.includes("dirty-from-m020.txt"),
|
|
166
|
+
"dirty-from-m020.txt should NOT be in the squash merge commit",
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
|
|
8
|
+
import { assessInterruptedSession } from "../interrupted-session.ts";
|
|
9
|
+
|
|
10
|
+
function makeTmpBase(): string {
|
|
11
|
+
const base = join(tmpdir(), `gsd-auto-interrupted-${randomUUID()}`);
|
|
12
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
13
|
+
return base;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function cleanup(base: string): void {
|
|
17
|
+
try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function writeRoadmap(base: string, checked = false): void {
|
|
21
|
+
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
22
|
+
mkdirSync(join(milestoneDir, "slices", "S01", "tasks"), { recursive: true });
|
|
23
|
+
writeFileSync(
|
|
24
|
+
join(milestoneDir, "M001-ROADMAP.md"),
|
|
25
|
+
[
|
|
26
|
+
"# M001: Test Milestone",
|
|
27
|
+
"",
|
|
28
|
+
"## Vision",
|
|
29
|
+
"",
|
|
30
|
+
"Test milestone.",
|
|
31
|
+
"",
|
|
32
|
+
"## Success Criteria",
|
|
33
|
+
"",
|
|
34
|
+
"- It works.",
|
|
35
|
+
"",
|
|
36
|
+
"## Slices",
|
|
37
|
+
"",
|
|
38
|
+
`- [${checked ? "x" : " "}] **S01: Test slice** \`risk:low\``,
|
|
39
|
+
" After this: Demo",
|
|
40
|
+
"",
|
|
41
|
+
"## Boundary Map",
|
|
42
|
+
"",
|
|
43
|
+
"- S01 → terminal",
|
|
44
|
+
" - Produces: done",
|
|
45
|
+
" - Consumes: nothing",
|
|
46
|
+
].join("\n"),
|
|
47
|
+
"utf-8",
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function writeCompleteArtifacts(base: string): void {
|
|
52
|
+
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
53
|
+
const sliceDir = join(milestoneDir, "slices", "S01");
|
|
54
|
+
mkdirSync(sliceDir, { recursive: true });
|
|
55
|
+
writeFileSync(join(sliceDir, "S01-SUMMARY.md"), "# Summary\nDone.\n", "utf-8");
|
|
56
|
+
writeFileSync(join(sliceDir, "S01-UAT.md"), "# UAT\nPassed.\n", "utf-8");
|
|
57
|
+
writeFileSync(join(milestoneDir, "M001-SUMMARY.md"), "# Milestone Summary\nDone.\n", "utf-8");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function writeLock(base: string, unitType: string, unitId: string): void {
|
|
61
|
+
writeFileSync(
|
|
62
|
+
join(base, ".gsd", "auto.lock"),
|
|
63
|
+
JSON.stringify({
|
|
64
|
+
pid: 999999999,
|
|
65
|
+
startedAt: new Date().toISOString(),
|
|
66
|
+
unitType,
|
|
67
|
+
unitId,
|
|
68
|
+
unitStartedAt: new Date().toISOString(),
|
|
69
|
+
}, null, 2),
|
|
70
|
+
"utf-8",
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function writePausedSession(base: string, milestoneId = "M001", stepMode = false): void {
|
|
75
|
+
const runtimeDir = join(base, ".gsd", "runtime");
|
|
76
|
+
mkdirSync(runtimeDir, { recursive: true });
|
|
77
|
+
writeFileSync(
|
|
78
|
+
join(runtimeDir, "paused-session.json"),
|
|
79
|
+
JSON.stringify({ milestoneId, originalBasePath: base, stepMode }, null, 2),
|
|
80
|
+
"utf-8",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
test("direct /gsd auto stale complete repo yields stale classification with no recovery payload", async () => {
|
|
85
|
+
const base = makeTmpBase();
|
|
86
|
+
try {
|
|
87
|
+
writeRoadmap(base, true);
|
|
88
|
+
writeCompleteArtifacts(base);
|
|
89
|
+
writeLock(base, "execute-task", "M001/S01/T01");
|
|
90
|
+
|
|
91
|
+
const assessment = await assessInterruptedSession(base);
|
|
92
|
+
assert.equal(assessment.classification, "stale");
|
|
93
|
+
assert.equal(assessment.recoveryPrompt, null);
|
|
94
|
+
assert.equal(assessment.hasResumableDiskState, false);
|
|
95
|
+
} finally {
|
|
96
|
+
cleanup(base);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("direct /gsd auto paused-session metadata remains recoverable when work is unfinished", async () => {
|
|
101
|
+
const base = makeTmpBase();
|
|
102
|
+
try {
|
|
103
|
+
writeRoadmap(base, false);
|
|
104
|
+
writePausedSession(base, "M001", false);
|
|
105
|
+
writeLock(base, "execute-task", "M001/S01/T01");
|
|
106
|
+
|
|
107
|
+
const assessment = await assessInterruptedSession(base);
|
|
108
|
+
assert.equal(assessment.classification, "recoverable");
|
|
109
|
+
assert.equal(assessment.pausedSession?.milestoneId, "M001");
|
|
110
|
+
} finally {
|
|
111
|
+
cleanup(base);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("direct /gsd auto stale paused-session metadata is treated as stale when no resumable work remains", async () => {
|
|
116
|
+
const base = makeTmpBase();
|
|
117
|
+
try {
|
|
118
|
+
writeRoadmap(base, true);
|
|
119
|
+
writeCompleteArtifacts(base);
|
|
120
|
+
writePausedSession(base, "M999", true);
|
|
121
|
+
|
|
122
|
+
const assessment = await assessInterruptedSession(base);
|
|
123
|
+
assert.equal(assessment.classification, "stale");
|
|
124
|
+
assert.equal(assessment.hasResumableDiskState, false);
|
|
125
|
+
} finally {
|
|
126
|
+
cleanup(base);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("direct /gsd auto source only resumes paused-session metadata for recoverable state with real recovery signals", async () => {
|
|
131
|
+
const source = await import(`node:fs/promises`).then((fs) =>
|
|
132
|
+
fs.readFile(new URL("../auto.ts", import.meta.url), "utf-8")
|
|
133
|
+
);
|
|
134
|
+
assert.ok(source.includes('const shouldResumePausedSession ='));
|
|
135
|
+
assert.ok(source.includes('freshStartAssessment.classification === "recoverable"'));
|
|
136
|
+
assert.ok(source.includes('&& ('));
|
|
137
|
+
assert.ok(source.includes('freshStartAssessment.hasResumableDiskState'));
|
|
138
|
+
assert.ok(source.includes('|| !!freshStartAssessment.recoveryPrompt'));
|
|
139
|
+
assert.ok(source.includes('|| !!freshStartAssessment.lock'));
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("auto module imports successfully after interrupted-session changes", async () => {
|
|
143
|
+
const mod = await import(`../auto.ts?ts=${Date.now()}-${Math.random()}`);
|
|
144
|
+
assert.equal(typeof mod.startAuto, "function");
|
|
145
|
+
assert.equal(typeof mod.pauseAuto, "function");
|
|
146
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
|
|
8
|
+
import { assessInterruptedSession } from "../interrupted-session.ts";
|
|
9
|
+
|
|
10
|
+
function makeTmpBase(): string {
|
|
11
|
+
const base = join(tmpdir(), `gsd-smart-entry-${randomUUID()}`);
|
|
12
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
13
|
+
return base;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function cleanup(base: string): void {
|
|
17
|
+
try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function writeRoadmap(base: string, checked = false): void {
|
|
21
|
+
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
22
|
+
mkdirSync(join(milestoneDir, "slices", "S01", "tasks"), { recursive: true });
|
|
23
|
+
writeFileSync(
|
|
24
|
+
join(milestoneDir, "M001-ROADMAP.md"),
|
|
25
|
+
[
|
|
26
|
+
"# M001: Test Milestone",
|
|
27
|
+
"",
|
|
28
|
+
"## Vision",
|
|
29
|
+
"",
|
|
30
|
+
"Test milestone.",
|
|
31
|
+
"",
|
|
32
|
+
"## Success Criteria",
|
|
33
|
+
"",
|
|
34
|
+
"- It works.",
|
|
35
|
+
"",
|
|
36
|
+
"## Slices",
|
|
37
|
+
"",
|
|
38
|
+
`- [${checked ? "x" : " "}] **S01: Test slice** \`risk:low\``,
|
|
39
|
+
" After this: Demo",
|
|
40
|
+
"",
|
|
41
|
+
"## Boundary Map",
|
|
42
|
+
"",
|
|
43
|
+
"- S01 → terminal",
|
|
44
|
+
" - Produces: done",
|
|
45
|
+
" - Consumes: nothing",
|
|
46
|
+
].join("\n"),
|
|
47
|
+
"utf-8",
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function writeCompleteArtifacts(base: string): void {
|
|
52
|
+
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
53
|
+
const sliceDir = join(milestoneDir, "slices", "S01");
|
|
54
|
+
mkdirSync(sliceDir, { recursive: true });
|
|
55
|
+
writeFileSync(join(sliceDir, "S01-SUMMARY.md"), "# Summary\nDone.\n", "utf-8");
|
|
56
|
+
writeFileSync(join(sliceDir, "S01-UAT.md"), "# UAT\nPassed.\n", "utf-8");
|
|
57
|
+
writeFileSync(join(milestoneDir, "M001-SUMMARY.md"), "# Milestone Summary\nDone.\n", "utf-8");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function writePausedSession(base: string, milestoneId = "M001", stepMode = false): void {
|
|
61
|
+
const runtimeDir = join(base, ".gsd", "runtime");
|
|
62
|
+
mkdirSync(runtimeDir, { recursive: true });
|
|
63
|
+
writeFileSync(
|
|
64
|
+
join(runtimeDir, "paused-session.json"),
|
|
65
|
+
JSON.stringify({ milestoneId, originalBasePath: base, stepMode }, null, 2),
|
|
66
|
+
"utf-8",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function writeLock(base: string, unitType: string, unitId: string): void {
|
|
71
|
+
writeFileSync(
|
|
72
|
+
join(base, ".gsd", "auto.lock"),
|
|
73
|
+
JSON.stringify({
|
|
74
|
+
pid: 999999999,
|
|
75
|
+
startedAt: new Date().toISOString(),
|
|
76
|
+
unitType,
|
|
77
|
+
unitId,
|
|
78
|
+
unitStartedAt: new Date().toISOString(),
|
|
79
|
+
}, null, 2),
|
|
80
|
+
"utf-8",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
test("guided-flow stale complete scenario classifies as stale so the resume prompt can be suppressed", async () => {
|
|
85
|
+
const base = makeTmpBase();
|
|
86
|
+
try {
|
|
87
|
+
writeRoadmap(base, true);
|
|
88
|
+
writeCompleteArtifacts(base);
|
|
89
|
+
writeLock(base, "execute-task", "M001/S01/T01");
|
|
90
|
+
|
|
91
|
+
const assessment = await assessInterruptedSession(base);
|
|
92
|
+
assert.equal(assessment.classification, "stale");
|
|
93
|
+
assert.equal(assessment.recoveryPrompt, null);
|
|
94
|
+
} finally {
|
|
95
|
+
cleanup(base);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("guided-flow paused-session scenario classifies as recoverable so resume remains available", async () => {
|
|
100
|
+
const base = makeTmpBase();
|
|
101
|
+
try {
|
|
102
|
+
writeRoadmap(base, false);
|
|
103
|
+
writePausedSession(base);
|
|
104
|
+
writeLock(base, "execute-task", "M001/S01/T01");
|
|
105
|
+
|
|
106
|
+
const assessment = await assessInterruptedSession(base);
|
|
107
|
+
assert.equal(assessment.classification, "recoverable");
|
|
108
|
+
assert.equal(assessment.pausedSession?.milestoneId, "M001");
|
|
109
|
+
} finally {
|
|
110
|
+
cleanup(base);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("guided-flow stale paused-session scenario is suppressed when no resumable work remains", async () => {
|
|
115
|
+
const base = makeTmpBase();
|
|
116
|
+
try {
|
|
117
|
+
writeRoadmap(base, true);
|
|
118
|
+
writeCompleteArtifacts(base);
|
|
119
|
+
writePausedSession(base, "M999", true);
|
|
120
|
+
|
|
121
|
+
const assessment = await assessInterruptedSession(base);
|
|
122
|
+
assert.equal(assessment.classification, "stale");
|
|
123
|
+
assert.equal(assessment.hasResumableDiskState, false);
|
|
124
|
+
} finally {
|
|
125
|
+
cleanup(base);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("guided-flow source uses step-aware resume and clears stale paused metadata without changing discuss handoff semantics", () => {
|
|
130
|
+
const source = readFileSync(join(import.meta.dirname, "..", "guided-flow.ts"), "utf-8");
|
|
131
|
+
assert.ok(source.includes('const interrupted = await assessInterruptedSession(basePath);'));
|
|
132
|
+
assert.ok(source.includes('resumeLabel = interrupted.pausedSession?.stepMode'));
|
|
133
|
+
assert.ok(source.includes('step: interrupted.pausedSession?.stepMode ?? false'));
|
|
134
|
+
assert.ok(source.includes('unlinkSync(join(gsdRoot(basePath), "runtime", "paused-session.json"))'));
|
|
135
|
+
assert.ok(source.includes('pendingAutoStartMap.set(basePath,'));
|
|
136
|
+
});
|