gsd-pi 2.80.0-dev.e146beb20 → 2.80.0-dev.e51d2c88c
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 +4 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/phases.js +29 -15
- package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +13 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +13 -1
- package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
- package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
- package/dist/resources/extensions/gsd/auto.js +66 -4
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +21 -2
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +27 -20
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +21 -0
- package/dist/resources/extensions/gsd/context-budget.js +37 -2
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
- package/dist/resources/extensions/gsd/db-base-schema.js +4 -2
- package/dist/resources/extensions/gsd/db-migration-steps.js +6 -0
- package/dist/resources/extensions/gsd/gsd-db.js +46 -13
- package/dist/resources/extensions/gsd/guided-flow.js +33 -4
- package/dist/resources/extensions/gsd/memory-store.js +69 -12
- package/dist/resources/extensions/gsd/migrate/command.js +40 -1
- package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +14 -13
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/dist/resources/extensions/gsd/quick.js +34 -2
- package/dist/resources/extensions/gsd/tools/context-mode-tool-result.js +15 -0
- package/dist/resources/extensions/gsd/tools/exec-search-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +3 -15
- package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
- package/dist/resources/extensions/gsd/tools/resume-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
- package/dist/resources/extensions/gsd/unit-context-composer.js +12 -3
- package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
- 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 +18 -18
- 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/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 +18 -18
- 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/package.json +3 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +22 -17
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +75 -2
- package/packages/mcp-server/src/workflow-tools.ts +30 -16
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +8 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +3 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- 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 +3 -0
- 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 +13 -5
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
- 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 +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +8 -0
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +3 -1
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
- package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +2 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +3 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +4 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +22 -7
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +18 -8
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/tui.ts +20 -8
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/phases.ts +35 -20
- package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +18 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +17 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
- package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
- package/src/resources/extensions/gsd/auto.ts +78 -3
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +21 -1
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +27 -19
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
- package/src/resources/extensions/gsd/context-budget.ts +44 -2
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
- package/src/resources/extensions/gsd/db-base-schema.ts +4 -2
- package/src/resources/extensions/gsd/db-migration-steps.ts +8 -0
- package/src/resources/extensions/gsd/gsd-db.ts +50 -13
- package/src/resources/extensions/gsd/guided-flow.ts +49 -4
- package/src/resources/extensions/gsd/memory-store.ts +77 -12
- package/src/resources/extensions/gsd/migrate/command.ts +47 -1
- package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +14 -13
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/src/resources/extensions/gsd/quick.ts +37 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +71 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
- package/src/resources/extensions/gsd/tests/exec-history.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +234 -0
- package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
- package/src/resources/extensions/gsd/tools/context-mode-tool-result.ts +25 -0
- package/src/resources/extensions/gsd/tools/exec-search-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -23
- package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
- package/src/resources/extensions/gsd/tools/resume-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
- package/src/resources/extensions/gsd/unit-context-composer.ts +19 -4
- package/src/resources/extensions/gsd/unit-runtime.ts +11 -0
- /package/dist/web/standalone/.next/static/{y73quA-XdLo9n41nxphjW → 8F5YpnZNBaooIWGF4GBV3}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{y73quA-XdLo9n41nxphjW → 8F5YpnZNBaooIWGF4GBV3}/_ssgManifest.js +0 -0
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
_resetPendingResolve,
|
|
11
11
|
_hasPendingResolveForTest,
|
|
12
12
|
_setActiveSession,
|
|
13
|
+
_setSessionSwitchInFlight,
|
|
14
|
+
_consumePendingSwitchCancellation,
|
|
13
15
|
isSessionSwitchInFlight,
|
|
14
16
|
} from "../auto/resolve.js";
|
|
15
17
|
import { runUnit } from "../auto/run-unit.js";
|
|
@@ -206,6 +208,28 @@ test("runUnit returns cancelled when session creation fails", async () => {
|
|
|
206
208
|
assert.equal(pi.calls.length, 0);
|
|
207
209
|
});
|
|
208
210
|
|
|
211
|
+
test("runUnit clears queued switch cancellation when session creation fails", async () => {
|
|
212
|
+
_resetPendingResolve();
|
|
213
|
+
|
|
214
|
+
const ctx = makeMockCtx();
|
|
215
|
+
const pi = makeMockPi();
|
|
216
|
+
const s = makeMockSession({
|
|
217
|
+
newSessionThrows: "connection refused",
|
|
218
|
+
onNewSessionStart: () => {
|
|
219
|
+
resolveAgentEndCancelled({
|
|
220
|
+
message: "Claude Code process aborted by user",
|
|
221
|
+
category: "aborted",
|
|
222
|
+
isTransient: false,
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const result = await runUnit(ctx, pi, s, "task", "T01", "prompt");
|
|
228
|
+
|
|
229
|
+
assert.equal(result.status, "cancelled");
|
|
230
|
+
assert.equal(_consumePendingSwitchCancellation(), null);
|
|
231
|
+
});
|
|
232
|
+
|
|
209
233
|
test("runUnit returns cancelled when session creation times out", async () => {
|
|
210
234
|
_resetPendingResolve();
|
|
211
235
|
|
|
@@ -221,6 +245,34 @@ test("runUnit returns cancelled when session creation times out", async () => {
|
|
|
221
245
|
assert.equal(pi.calls.length, 0);
|
|
222
246
|
});
|
|
223
247
|
|
|
248
|
+
test("runUnit consumes a cancellation queued during session switch before dispatch", async () => {
|
|
249
|
+
_resetPendingResolve();
|
|
250
|
+
|
|
251
|
+
const ctx = makeMockCtx();
|
|
252
|
+
const pi = makeMockPi();
|
|
253
|
+
let cancellationQueued = false;
|
|
254
|
+
const s = makeMockSession({
|
|
255
|
+
newSessionDelayMs: 10,
|
|
256
|
+
onNewSessionStart: () => {
|
|
257
|
+
setTimeout(() => {
|
|
258
|
+
cancellationQueued = !resolveAgentEndCancelled({
|
|
259
|
+
message: "Claude Code process aborted by user",
|
|
260
|
+
category: "aborted",
|
|
261
|
+
isTransient: false,
|
|
262
|
+
});
|
|
263
|
+
}, 0);
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const result = await runUnit(ctx, pi, s, "plan-slice", "M009/S01", "prompt");
|
|
268
|
+
|
|
269
|
+
assert.equal(cancellationQueued, true);
|
|
270
|
+
assert.equal(result.status, "cancelled");
|
|
271
|
+
assert.equal(result.errorContext?.category, "aborted");
|
|
272
|
+
assert.equal(result.errorContext?.message, "Claude Code process aborted by user");
|
|
273
|
+
assert.equal(pi.calls.length, 0, "queued switch cancellation must prevent prompt dispatch");
|
|
274
|
+
});
|
|
275
|
+
|
|
224
276
|
test("runUnit keeps the session-switch guard across a late newSession settlement", async () => {
|
|
225
277
|
_resetPendingResolve();
|
|
226
278
|
mock.timers.enable();
|
|
@@ -2089,6 +2141,25 @@ test("resolveAgentEndCancelled without args produces no errorContext field", asy
|
|
|
2089
2141
|
assert.equal(resolved.errorContext, undefined, "errorContext must not be present when no args passed");
|
|
2090
2142
|
});
|
|
2091
2143
|
|
|
2144
|
+
test("resolveAgentEndCancelled queues cancellation that arrives during session switch", () => {
|
|
2145
|
+
_resetPendingResolve();
|
|
2146
|
+
|
|
2147
|
+
_setSessionSwitchInFlight(true);
|
|
2148
|
+
const resolved = resolveAgentEndCancelled({
|
|
2149
|
+
message: "Claude Code process aborted by user",
|
|
2150
|
+
category: "aborted",
|
|
2151
|
+
isTransient: false,
|
|
2152
|
+
});
|
|
2153
|
+
|
|
2154
|
+
assert.equal(resolved, false);
|
|
2155
|
+
const pending = _consumePendingSwitchCancellation();
|
|
2156
|
+
assert.ok(pending?.errorContext, "queued cancellation should preserve errorContext");
|
|
2157
|
+
assert.equal(pending.errorContext.category, "aborted");
|
|
2158
|
+
assert.equal(pending.errorContext.message, "Claude Code process aborted by user");
|
|
2159
|
+
assert.equal(_consumePendingSwitchCancellation(), null);
|
|
2160
|
+
_resetPendingResolve();
|
|
2161
|
+
});
|
|
2162
|
+
|
|
2092
2163
|
// ─── #1571: artifact verification retry ──────────────────────────────────────
|
|
2093
2164
|
|
|
2094
2165
|
test("autoLoop re-iterates when postUnitPreVerification returns retry (#1571)", async () => {
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
3
6
|
|
|
4
7
|
import { runFinalize } from "../auto/phases.ts";
|
|
5
8
|
import { AutoSession } from "../auto/session.ts";
|
|
9
|
+
import { readUnitRuntimeRecord, writeUnitRuntimeRecord } from "../unit-runtime.ts";
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
};
|
|
11
|
+
async function runSuccessfulFinalize(s: AutoSession) {
|
|
12
|
+
const unit = s.currentUnit;
|
|
13
|
+
assert.ok(unit, "test setup must provide currentUnit");
|
|
14
|
+
|
|
15
|
+
writeUnitRuntimeRecord(s.basePath, unit.type, unit.id, unit.startedAt, {
|
|
16
|
+
phase: "dispatched",
|
|
17
|
+
});
|
|
15
18
|
|
|
16
19
|
const deps = {
|
|
17
20
|
clearUnitTimeout() {},
|
|
@@ -26,7 +29,7 @@ test("runFinalize clears currentUnit after successful finalize", async () => {
|
|
|
26
29
|
postUnitPostVerification: async () => "continue",
|
|
27
30
|
};
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
return runFinalize(
|
|
30
33
|
{
|
|
31
34
|
ctx: { ui: { notify() {} } },
|
|
32
35
|
pi: {},
|
|
@@ -38,8 +41,8 @@ test("runFinalize clears currentUnit after successful finalize", async () => {
|
|
|
38
41
|
nextSeq: () => 1,
|
|
39
42
|
} as any,
|
|
40
43
|
{
|
|
41
|
-
unitType:
|
|
42
|
-
unitId:
|
|
44
|
+
unitType: unit.type,
|
|
45
|
+
unitId: unit.id,
|
|
43
46
|
prompt: "",
|
|
44
47
|
finalPrompt: "",
|
|
45
48
|
pauseAfterUatDispatch: false,
|
|
@@ -55,7 +58,47 @@ test("runFinalize clears currentUnit after successful finalize", async () => {
|
|
|
55
58
|
consecutiveFinalizeTimeouts: 0,
|
|
56
59
|
},
|
|
57
60
|
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
test("runFinalize clears currentUnit after successful finalize", async () => {
|
|
64
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-finalize-current-unit-"));
|
|
65
|
+
const s = new AutoSession();
|
|
66
|
+
s.basePath = base;
|
|
67
|
+
s.currentUnit = {
|
|
68
|
+
type: "execute-task",
|
|
69
|
+
id: "M001/S01/T01",
|
|
70
|
+
startedAt: Date.now(),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const result = await runSuccessfulFinalize(s);
|
|
75
|
+
|
|
76
|
+
assert.equal(result.action, "next");
|
|
77
|
+
assert.equal(s.currentUnit, null);
|
|
78
|
+
} finally {
|
|
79
|
+
rmSync(base, { recursive: true, force: true });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("runFinalize marks unit runtime finalized after successful finalize", async () => {
|
|
84
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-finalize-runtime-"));
|
|
85
|
+
const s = new AutoSession();
|
|
86
|
+
const startedAt = Date.now();
|
|
87
|
+
s.basePath = base;
|
|
88
|
+
s.currentUnit = {
|
|
89
|
+
type: "complete-milestone",
|
|
90
|
+
id: "M001",
|
|
91
|
+
startedAt,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const result = await runSuccessfulFinalize(s);
|
|
96
|
+
const runtime = readUnitRuntimeRecord(base, "complete-milestone", "M001");
|
|
58
97
|
|
|
59
|
-
|
|
60
|
-
|
|
98
|
+
assert.equal(result.action, "next");
|
|
99
|
+
assert.equal(runtime?.phase, "finalized");
|
|
100
|
+
assert.equal(runtime?.lastProgressKind, "finalize-success");
|
|
101
|
+
} finally {
|
|
102
|
+
rmSync(base, { recursive: true, force: true });
|
|
103
|
+
}
|
|
61
104
|
});
|
|
@@ -5,7 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
|
|
8
|
-
import { verifyExpectedArtifact, hasImplementationArtifacts, resolveExpectedArtifactPath, diagnoseExpectedArtifact, buildLoopRemediationSteps, writeBlockerPlaceholder } from "../auto-recovery.ts";
|
|
8
|
+
import { verifyExpectedArtifact, hasImplementationArtifacts, resolveExpectedArtifactPath, diagnoseExpectedArtifact, buildLoopRemediationSteps, writeBlockerPlaceholder, refreshRecoveryDbForArtifact } from "../auto-recovery.ts";
|
|
9
9
|
import { resolveMilestoneFile } from "../paths.ts";
|
|
10
10
|
import { openDatabase, closeDatabase, insertMilestone, insertSlice, insertGateRow, insertTask, getMilestoneCommitAttributionShas } from "../gsd-db.ts";
|
|
11
11
|
import { clearParseCache } from "../files.ts";
|
|
@@ -175,6 +175,19 @@ test("resolveExpectedArtifactPath returns correct path for all slice-level types
|
|
|
175
175
|
}
|
|
176
176
|
});
|
|
177
177
|
|
|
178
|
+
test("refreshRecoveryDbForArtifact treats missing execute-task DB rows as fatal mismatches", () => {
|
|
179
|
+
makeTmpProject();
|
|
180
|
+
|
|
181
|
+
const result = refreshRecoveryDbForArtifact("execute-task", "M001/S01/T01");
|
|
182
|
+
|
|
183
|
+
assert.deepEqual(result, {
|
|
184
|
+
ok: false,
|
|
185
|
+
fatal: true,
|
|
186
|
+
reason: "execute-task-artifact-db-missing",
|
|
187
|
+
message: "Stuck recovery found execute-task M001/S01/T01 artifacts, but no matching DB task row exists after refresh.",
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
178
191
|
// ─── diagnoseExpectedArtifact ─────────────────────────────────────────────
|
|
179
192
|
|
|
180
193
|
test("diagnoseExpectedArtifact returns description for known types", () => {
|
|
@@ -29,7 +29,8 @@ test('buildSnapshot: renders memories, exec history, and active context', () =>
|
|
|
29
29
|
memories: [
|
|
30
30
|
{ id: 'MEM001', category: 'gotcha', content: 'FTS5 needs Porter tokenizer', confidence: 0.9,
|
|
31
31
|
source_unit_type: null, source_unit_id: null, created_at: '', updated_at: '',
|
|
32
|
-
superseded_by: null, hit_count: 0, scope: 'project', seq: 1, tags: [], structured_fields: null
|
|
32
|
+
superseded_by: null, hit_count: 0, scope: 'project', seq: 1, tags: [], structured_fields: null,
|
|
33
|
+
last_hit_at: null },
|
|
33
34
|
],
|
|
34
35
|
execHistory: [
|
|
35
36
|
{
|
|
@@ -65,6 +66,7 @@ test('buildSnapshot: enforces the byte cap with a truncation marker', () => {
|
|
|
65
66
|
seq: i,
|
|
66
67
|
tags: [] as string[],
|
|
67
68
|
structured_fields: null,
|
|
69
|
+
last_hit_at: null,
|
|
68
70
|
}));
|
|
69
71
|
const snap = buildSnapshot(
|
|
70
72
|
{ generatedAt: new Date(), memories: longMemories, execHistory: [] },
|
|
@@ -121,3 +123,14 @@ test('executeResume: reports friendly empty state when no snapshot exists', () =
|
|
|
121
123
|
cleanup(base);
|
|
122
124
|
}
|
|
123
125
|
});
|
|
126
|
+
|
|
127
|
+
test('executeResume: returns disabled error when context_mode.enabled=false', () => {
|
|
128
|
+
const base = freshBase();
|
|
129
|
+
try {
|
|
130
|
+
const result = executeResume({}, { baseDir: base, preferences: { context_mode: { enabled: false } } });
|
|
131
|
+
assert.equal(result.isError, true);
|
|
132
|
+
assert.equal((result.details as { error?: string }).error, 'context_mode_disabled');
|
|
133
|
+
} finally {
|
|
134
|
+
cleanup(base);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* No I/O, no extension context, no global state.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { describe, it } from "node:test";
|
|
7
|
+
import { describe, it, beforeEach } from "node:test";
|
|
8
8
|
import assert from "node:assert/strict";
|
|
9
9
|
|
|
10
10
|
import {
|
|
@@ -16,8 +16,17 @@ import {
|
|
|
16
16
|
computeBudgets,
|
|
17
17
|
truncateAtSectionBoundary,
|
|
18
18
|
resolveExecutorContextWindow,
|
|
19
|
+
_resetEmpiricalCacheForTest,
|
|
19
20
|
} from "../context-budget.js";
|
|
20
21
|
|
|
22
|
+
// Reset the per-provider empirical chars-per-token cache before each test.
|
|
23
|
+
// The hardcoded char-ratio assertions below assume the static fallback path
|
|
24
|
+
// (3.5 / 4.0 chars/token) is used. Without this guard, a prior test that
|
|
25
|
+
// warms tiktoken would populate the cache and silently break these tests.
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
_resetEmpiricalCacheForTest();
|
|
28
|
+
});
|
|
29
|
+
|
|
21
30
|
import type { TokenProvider } from "../token-counter.js";
|
|
22
31
|
|
|
23
32
|
// ─── Test helpers ─────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
// gsd-2 / dispatch rule coverage canary test
|
|
2
|
+
//
|
|
3
|
+
// Iterates DISPATCH_RULES in order against representative GSDState stubs and
|
|
4
|
+
// asserts that the first matching rule has the expected name and unitType
|
|
5
|
+
// (mirroring auto-dispatch's first-match-wins semantics). The goal is a
|
|
6
|
+
// canary: if a future PR adds a new rule in the wrong position and steals
|
|
7
|
+
// a match from an existing one, this test fails.
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
|
|
15
|
+
import { DISPATCH_RULES } from "../auto-dispatch.ts";
|
|
16
|
+
import type { DispatchContext, DispatchAction } from "../auto-dispatch.ts";
|
|
17
|
+
import type { GSDState } from "../types.ts";
|
|
18
|
+
|
|
19
|
+
// ─── State helpers ────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
function makeState(overrides: Partial<GSDState> = {}): GSDState {
|
|
22
|
+
return {
|
|
23
|
+
activeMilestone: { id: "M001", title: "Test Milestone" },
|
|
24
|
+
activeSlice: null,
|
|
25
|
+
activeTask: null,
|
|
26
|
+
phase: "pre-planning",
|
|
27
|
+
recentDecisions: [],
|
|
28
|
+
blockers: [],
|
|
29
|
+
nextAction: "",
|
|
30
|
+
registry: [],
|
|
31
|
+
...overrides,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function makeCtx(basePath: string, state: GSDState, mid = "M001"): DispatchContext {
|
|
36
|
+
return {
|
|
37
|
+
basePath,
|
|
38
|
+
mid,
|
|
39
|
+
midTitle: "Test Milestone",
|
|
40
|
+
state,
|
|
41
|
+
prefs: undefined,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── Disk scaffold helpers ────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
function writeMilestoneFile(basePath: string, mid: string, suffix: string, content = "stub\n"): void {
|
|
48
|
+
const dir = join(basePath, ".gsd", "milestones", mid);
|
|
49
|
+
mkdirSync(dir, { recursive: true });
|
|
50
|
+
writeFileSync(join(dir, `${mid}-${suffix}.md`), content);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function writeSliceFile(
|
|
54
|
+
basePath: string,
|
|
55
|
+
mid: string,
|
|
56
|
+
sid: string,
|
|
57
|
+
suffix: string,
|
|
58
|
+
content = "stub\n",
|
|
59
|
+
): void {
|
|
60
|
+
const dir = join(basePath, ".gsd", "milestones", mid, "slices", sid);
|
|
61
|
+
mkdirSync(dir, { recursive: true });
|
|
62
|
+
writeFileSync(join(dir, `${sid}-${suffix}.md`), content);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function writeTaskPlan(basePath: string, mid: string, sid: string, tid: string): void {
|
|
66
|
+
const dir = join(basePath, ".gsd", "milestones", mid, "slices", sid, "tasks");
|
|
67
|
+
mkdirSync(dir, { recursive: true });
|
|
68
|
+
writeFileSync(join(dir, `${tid}-PLAN.md`), `# ${tid}\n\n## Steps\n- [ ] Step\n`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ─── Rule evaluation ──────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
interface MatchEntry {
|
|
74
|
+
ruleName: string;
|
|
75
|
+
result: DispatchAction;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// First-match-wins semantics: walks DISPATCH_RULES in order and stops at the
|
|
79
|
+
// first non-null result. This mirrors the production resolver and is the
|
|
80
|
+
// canary against rule reordering or shadowing.
|
|
81
|
+
async function findFirstMatch(ctx: DispatchContext): Promise<MatchEntry | null> {
|
|
82
|
+
for (const rule of DISPATCH_RULES) {
|
|
83
|
+
const result = await rule.match(ctx);
|
|
84
|
+
if (result) return { ruleName: rule.name, result };
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function assertMatch(
|
|
90
|
+
match: MatchEntry | null,
|
|
91
|
+
expected: { ruleName: string; action: DispatchAction["action"]; unitType?: string },
|
|
92
|
+
scenario: string,
|
|
93
|
+
): void {
|
|
94
|
+
assert.ok(match, `${scenario}: no rule matched`);
|
|
95
|
+
assert.equal(match.ruleName, expected.ruleName, `${scenario}: matched rule mismatch`);
|
|
96
|
+
assert.equal(match.result.action, expected.action, `${scenario}: action mismatch`);
|
|
97
|
+
if (expected.action === "dispatch" && expected.unitType) {
|
|
98
|
+
assert.ok(
|
|
99
|
+
match.result.action === "dispatch" && match.result.unitType === expected.unitType,
|
|
100
|
+
`${scenario}: unitType mismatch (got ${
|
|
101
|
+
match.result.action === "dispatch" ? match.result.unitType : match.result.action
|
|
102
|
+
})`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ─── Tests ────────────────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
test("dispatch-rule-coverage: escalating-task → stop (info)", async (t) => {
|
|
110
|
+
const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-esc-"));
|
|
111
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
112
|
+
|
|
113
|
+
const ctx = makeCtx(
|
|
114
|
+
tmp,
|
|
115
|
+
makeState({
|
|
116
|
+
phase: "escalating-task",
|
|
117
|
+
activeSlice: { id: "S01", title: "Slice" },
|
|
118
|
+
nextAction: "Resolve escalation X",
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
const match = await findFirstMatch(ctx);
|
|
122
|
+
assertMatch(
|
|
123
|
+
match,
|
|
124
|
+
{ ruleName: "escalating-task → pause-for-escalation", action: "stop" },
|
|
125
|
+
"escalating-task",
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("dispatch-rule-coverage: pre-planning, no CONTEXT → discuss-milestone", async (t) => {
|
|
130
|
+
const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-disc-"));
|
|
131
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
132
|
+
|
|
133
|
+
// Bare milestone dir, no CONTEXT/RESEARCH/ROADMAP files.
|
|
134
|
+
mkdirSync(join(tmp, ".gsd", "milestones", "M001"), { recursive: true });
|
|
135
|
+
|
|
136
|
+
const ctx = makeCtx(tmp, makeState({ phase: "pre-planning" }));
|
|
137
|
+
const match = await findFirstMatch(ctx);
|
|
138
|
+
assertMatch(
|
|
139
|
+
match,
|
|
140
|
+
{
|
|
141
|
+
ruleName: "pre-planning (no context) → discuss-milestone",
|
|
142
|
+
action: "dispatch",
|
|
143
|
+
unitType: "discuss-milestone",
|
|
144
|
+
},
|
|
145
|
+
"pre-planning no context",
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("dispatch-rule-coverage: pre-planning, has CONTEXT, no RESEARCH → research-milestone", async (t) => {
|
|
150
|
+
const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-res-"));
|
|
151
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
152
|
+
|
|
153
|
+
writeMilestoneFile(tmp, "M001", "CONTEXT", "# Context\n");
|
|
154
|
+
|
|
155
|
+
const ctx = makeCtx(tmp, makeState({ phase: "pre-planning" }));
|
|
156
|
+
const match = await findFirstMatch(ctx);
|
|
157
|
+
assertMatch(
|
|
158
|
+
match,
|
|
159
|
+
{
|
|
160
|
+
ruleName: "pre-planning (no research) → research-milestone",
|
|
161
|
+
action: "dispatch",
|
|
162
|
+
unitType: "research-milestone",
|
|
163
|
+
},
|
|
164
|
+
"pre-planning no research",
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("dispatch-rule-coverage: pre-planning, has CONTEXT + RESEARCH → plan-milestone", async (t) => {
|
|
169
|
+
const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-plan-m-"));
|
|
170
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
171
|
+
|
|
172
|
+
writeMilestoneFile(tmp, "M001", "CONTEXT", "# Context\n");
|
|
173
|
+
writeMilestoneFile(tmp, "M001", "RESEARCH", "# Research\n");
|
|
174
|
+
|
|
175
|
+
const ctx = makeCtx(tmp, makeState({ phase: "pre-planning" }));
|
|
176
|
+
const match = await findFirstMatch(ctx);
|
|
177
|
+
assertMatch(
|
|
178
|
+
match,
|
|
179
|
+
{
|
|
180
|
+
ruleName: "pre-planning (has research) → plan-milestone",
|
|
181
|
+
action: "dispatch",
|
|
182
|
+
unitType: "plan-milestone",
|
|
183
|
+
},
|
|
184
|
+
"pre-planning has research",
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("dispatch-rule-coverage: planning with active slice and skip_research → plan-slice", async (t) => {
|
|
189
|
+
const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-plan-s-"));
|
|
190
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
191
|
+
|
|
192
|
+
writeMilestoneFile(tmp, "M001", "CONTEXT", "# Context\n");
|
|
193
|
+
writeMilestoneFile(tmp, "M001", "ROADMAP", "# Roadmap\n");
|
|
194
|
+
|
|
195
|
+
const state = makeState({
|
|
196
|
+
phase: "planning",
|
|
197
|
+
activeSlice: { id: "S01", title: "First Slice" },
|
|
198
|
+
});
|
|
199
|
+
const ctx: DispatchContext = {
|
|
200
|
+
basePath: tmp,
|
|
201
|
+
mid: "M001",
|
|
202
|
+
midTitle: "Test Milestone",
|
|
203
|
+
state,
|
|
204
|
+
// Skip slice research so the parallel/single research rules fall through.
|
|
205
|
+
prefs: { phases: { skip_slice_research: true } } as DispatchContext["prefs"],
|
|
206
|
+
};
|
|
207
|
+
const match = await findFirstMatch(ctx);
|
|
208
|
+
assertMatch(
|
|
209
|
+
match,
|
|
210
|
+
{
|
|
211
|
+
ruleName: "planning → plan-slice",
|
|
212
|
+
action: "dispatch",
|
|
213
|
+
unitType: "plan-slice",
|
|
214
|
+
},
|
|
215
|
+
"planning → plan-slice",
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("dispatch-rule-coverage: executing with task plan present → execute-task", async (t) => {
|
|
220
|
+
const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-exec-"));
|
|
221
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
222
|
+
|
|
223
|
+
writeMilestoneFile(tmp, "M001", "CONTEXT", "# Context\n");
|
|
224
|
+
writeSliceFile(tmp, "M001", "S01", "PLAN", "# Plan\n");
|
|
225
|
+
writeTaskPlan(tmp, "M001", "S01", "T01");
|
|
226
|
+
|
|
227
|
+
const state = makeState({
|
|
228
|
+
phase: "executing",
|
|
229
|
+
activeSlice: { id: "S01", title: "First Slice" },
|
|
230
|
+
activeTask: { id: "T01", title: "First Task" },
|
|
231
|
+
});
|
|
232
|
+
// Disable reactive dispatch so the parallel batching rule falls through.
|
|
233
|
+
const ctx: DispatchContext = {
|
|
234
|
+
basePath: tmp,
|
|
235
|
+
mid: "M001",
|
|
236
|
+
midTitle: "Test Milestone",
|
|
237
|
+
state,
|
|
238
|
+
prefs: { reactive_execution: { enabled: false } } as DispatchContext["prefs"],
|
|
239
|
+
};
|
|
240
|
+
const match = await findFirstMatch(ctx);
|
|
241
|
+
assertMatch(
|
|
242
|
+
match,
|
|
243
|
+
{
|
|
244
|
+
ruleName: "executing → execute-task",
|
|
245
|
+
action: "dispatch",
|
|
246
|
+
unitType: "execute-task",
|
|
247
|
+
},
|
|
248
|
+
"executing → execute-task",
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("dispatch-rule-coverage: summarizing → complete-slice", async (t) => {
|
|
253
|
+
const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-sum-"));
|
|
254
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
255
|
+
|
|
256
|
+
// Rule "execution-entry phase (no context)" fires for summarizing if CONTEXT
|
|
257
|
+
// is missing — write it so the summarizing rule wins.
|
|
258
|
+
writeMilestoneFile(tmp, "M001", "CONTEXT", "# Context\n");
|
|
259
|
+
|
|
260
|
+
const ctx = makeCtx(
|
|
261
|
+
tmp,
|
|
262
|
+
makeState({
|
|
263
|
+
phase: "summarizing",
|
|
264
|
+
activeSlice: { id: "S01", title: "First Slice" },
|
|
265
|
+
}),
|
|
266
|
+
);
|
|
267
|
+
const match = await findFirstMatch(ctx);
|
|
268
|
+
assertMatch(
|
|
269
|
+
match,
|
|
270
|
+
{
|
|
271
|
+
ruleName: "summarizing → complete-slice",
|
|
272
|
+
action: "dispatch",
|
|
273
|
+
unitType: "complete-slice",
|
|
274
|
+
},
|
|
275
|
+
"summarizing → complete-slice",
|
|
276
|
+
);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("dispatch-rule-coverage: complete phase → stop", async (t) => {
|
|
280
|
+
const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-done-"));
|
|
281
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
282
|
+
|
|
283
|
+
const ctx = makeCtx(
|
|
284
|
+
tmp,
|
|
285
|
+
makeState({
|
|
286
|
+
phase: "complete",
|
|
287
|
+
activeMilestone: null,
|
|
288
|
+
lastCompletedMilestone: { id: "M001", title: "Test Milestone" },
|
|
289
|
+
}),
|
|
290
|
+
);
|
|
291
|
+
const match = await findFirstMatch(ctx);
|
|
292
|
+
assertMatch(
|
|
293
|
+
match,
|
|
294
|
+
{ ruleName: "complete → stop", action: "stop" },
|
|
295
|
+
"complete → stop",
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// ─── Ordering canary: every scenario above resolves to exactly one rule ────
|
|
300
|
+
|
|
301
|
+
test("dispatch-rule-coverage: rule registry has the expected size", () => {
|
|
302
|
+
// Sanity check that complements the per-state assertions: if someone adds a
|
|
303
|
+
// new rule, this number changes — prompting them to add a state stub above.
|
|
304
|
+
// Exact count is a brittle but useful canary; update when adding rules
|
|
305
|
+
// intentionally.
|
|
306
|
+
assert.equal(
|
|
307
|
+
DISPATCH_RULES.length,
|
|
308
|
+
29,
|
|
309
|
+
`DISPATCH_RULES length changed (got ${DISPATCH_RULES.length}). ` +
|
|
310
|
+
"If you added a rule, add a state stub to dispatch-rule-coverage.test.ts " +
|
|
311
|
+
"and update this expected count.",
|
|
312
|
+
);
|
|
313
|
+
});
|
|
@@ -108,6 +108,21 @@ test('executeExecSearch: returns helpful empty-state message when no matches', (
|
|
|
108
108
|
}
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
+
test('executeExecSearch: returns disabled error when context_mode.enabled=false', () => {
|
|
112
|
+
const base = freshBase();
|
|
113
|
+
try {
|
|
114
|
+
writeRun(base, 'should-not-surface', { stdout: 'hidden\n' });
|
|
115
|
+
const result = executeExecSearch(
|
|
116
|
+
{ query: 'hidden' },
|
|
117
|
+
{ baseDir: base, preferences: { context_mode: { enabled: false } } },
|
|
118
|
+
);
|
|
119
|
+
assert.equal(result.isError, true);
|
|
120
|
+
assert.equal((result.details as { error?: string }).error, 'context_mode_disabled');
|
|
121
|
+
} finally {
|
|
122
|
+
cleanup(base);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
111
126
|
test('executeExecSearch: includes stdout_path and preview in details', () => {
|
|
112
127
|
const base = freshBase();
|
|
113
128
|
try {
|