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
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, mkdirSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { buildQuickCommitInstruction } from "../quick.ts";
|
|
8
|
+
|
|
9
|
+
function git(cwd: string, args: string[]): void {
|
|
10
|
+
const result = spawnSync("git", args, { cwd, encoding: "utf-8" });
|
|
11
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
test("quick task commit instruction does not ask agents to stage external .gsd quick files", { skip: process.platform === "win32" }, () => {
|
|
15
|
+
const tempRoot = realpathSync(mkdtempSync(join(tmpdir(), "gsd-quick-ext-")));
|
|
16
|
+
const repo = join(tempRoot, "repo");
|
|
17
|
+
const externalGsd = join(tempRoot, "state");
|
|
18
|
+
mkdirSync(repo);
|
|
19
|
+
mkdirSync(externalGsd);
|
|
20
|
+
|
|
21
|
+
const previousCwd = process.cwd();
|
|
22
|
+
try {
|
|
23
|
+
git(repo, ["init"]);
|
|
24
|
+
git(repo, ["config", "user.email", "test@example.com"]);
|
|
25
|
+
git(repo, ["config", "user.name", "Test User"]);
|
|
26
|
+
writeFileSync(join(repo, "README.md"), "# Test\n", "utf-8");
|
|
27
|
+
git(repo, ["add", "README.md"]);
|
|
28
|
+
git(repo, ["commit", "-m", "init"]);
|
|
29
|
+
symlinkSync(externalGsd, join(repo, ".gsd"), "dir");
|
|
30
|
+
|
|
31
|
+
const instruction = buildQuickCommitInstruction(repo, join(repo, ".gsd"));
|
|
32
|
+
|
|
33
|
+
assert.match(instruction, /do not stage or commit `\.gsd\/quick\/\.\.\.`/);
|
|
34
|
+
assert.match(instruction, /nothing in the project repo to commit/);
|
|
35
|
+
assert.match(instruction, /Write the quick summary file directly/);
|
|
36
|
+
} finally {
|
|
37
|
+
process.chdir(previousCwd);
|
|
38
|
+
rmSync(tempRoot, { recursive: true, force: true });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// gsd-2 / V27 + V28 schema migration regression tests
|
|
2
|
+
//
|
|
3
|
+
// Same bug class as #4591 (schema-v21-sequence): a migration block can be
|
|
4
|
+
// added but the SCHEMA_VERSION constant left unchanged, causing fresh-install
|
|
5
|
+
// + upgrade paths to silently skip the column add. This file pins both V27
|
|
6
|
+
// (artifacts.content_hash) and V28 (memories.last_hit_at) at the schema and
|
|
7
|
+
// write-path level on fresh-install DBs.
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import * as os from "node:os";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
openDatabase,
|
|
17
|
+
closeDatabase,
|
|
18
|
+
_getAdapter,
|
|
19
|
+
insertArtifact,
|
|
20
|
+
insertMemoryRow,
|
|
21
|
+
incrementMemoryHitCount,
|
|
22
|
+
SCHEMA_VERSION,
|
|
23
|
+
} from "../gsd-db.ts";
|
|
24
|
+
|
|
25
|
+
function makeTmp(): string {
|
|
26
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), "gsd-v27v28-"));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function cleanup(base: string): void {
|
|
30
|
+
try { closeDatabase(); } catch { /* noop */ }
|
|
31
|
+
try { fs.rmSync(base, { recursive: true, force: true }); } catch { /* noop */ }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
test("SCHEMA_VERSION constant is at least 28 (V28 migration committed)", () => {
|
|
35
|
+
assert.ok(
|
|
36
|
+
SCHEMA_VERSION >= 28,
|
|
37
|
+
`SCHEMA_VERSION must be ≥ 28 after V28 migration; got ${SCHEMA_VERSION}`,
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("fresh-install DB has artifacts.content_hash column (V27)", () => {
|
|
42
|
+
const base = makeTmp();
|
|
43
|
+
const dbPath = path.join(base, "gsd.db");
|
|
44
|
+
try {
|
|
45
|
+
openDatabase(dbPath);
|
|
46
|
+
const db = _getAdapter()!;
|
|
47
|
+
const cols = db.prepare("PRAGMA table_info(artifacts)").all() as Array<Record<string, unknown>>;
|
|
48
|
+
const colNames = new Set(cols.map((c) => c["name"] as string));
|
|
49
|
+
assert.ok(
|
|
50
|
+
colNames.has("content_hash"),
|
|
51
|
+
"V27 must add content_hash column to artifacts on fresh install",
|
|
52
|
+
);
|
|
53
|
+
} finally {
|
|
54
|
+
cleanup(base);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("fresh-install DB has memories.last_hit_at column (V28)", () => {
|
|
59
|
+
const base = makeTmp();
|
|
60
|
+
const dbPath = path.join(base, "gsd.db");
|
|
61
|
+
try {
|
|
62
|
+
openDatabase(dbPath);
|
|
63
|
+
const db = _getAdapter()!;
|
|
64
|
+
const cols = db.prepare("PRAGMA table_info(memories)").all() as Array<Record<string, unknown>>;
|
|
65
|
+
const colNames = new Set(cols.map((c) => c["name"] as string));
|
|
66
|
+
assert.ok(
|
|
67
|
+
colNames.has("last_hit_at"),
|
|
68
|
+
"V28 must add last_hit_at column to memories on fresh install",
|
|
69
|
+
);
|
|
70
|
+
} finally {
|
|
71
|
+
cleanup(base);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("fresh-install DB stamps SCHEMA_VERSION (≥28) in schema_version table", () => {
|
|
76
|
+
const base = makeTmp();
|
|
77
|
+
const dbPath = path.join(base, "gsd.db");
|
|
78
|
+
try {
|
|
79
|
+
openDatabase(dbPath);
|
|
80
|
+
const db = _getAdapter()!;
|
|
81
|
+
const row = db.prepare("SELECT MAX(version) as v FROM schema_version").get() as Record<string, unknown> | undefined;
|
|
82
|
+
const max = (row?.["v"] as number) ?? 0;
|
|
83
|
+
assert.ok(max >= 28, `fresh install must record schema_version ≥ 28; got ${max}`);
|
|
84
|
+
} finally {
|
|
85
|
+
cleanup(base);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("insertArtifact populates content_hash with SHA-256 of full_content (V27 write-path)", () => {
|
|
90
|
+
const base = makeTmp();
|
|
91
|
+
const dbPath = path.join(base, "gsd.db");
|
|
92
|
+
try {
|
|
93
|
+
openDatabase(dbPath);
|
|
94
|
+
insertArtifact({
|
|
95
|
+
path: "M001/PROJECT.md",
|
|
96
|
+
artifact_type: "PROJECT",
|
|
97
|
+
milestone_id: "M001",
|
|
98
|
+
slice_id: null,
|
|
99
|
+
task_id: null,
|
|
100
|
+
full_content: "hello world",
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const db = _getAdapter()!;
|
|
104
|
+
const row = db
|
|
105
|
+
.prepare("SELECT content_hash FROM artifacts WHERE path = :p")
|
|
106
|
+
.get({ ":p": "M001/PROJECT.md" }) as Record<string, unknown> | undefined;
|
|
107
|
+
const hash = row?.["content_hash"] as string | null | undefined;
|
|
108
|
+
|
|
109
|
+
// SHA-256 of "hello world" hex-encoded:
|
|
110
|
+
const expected = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9";
|
|
111
|
+
assert.equal(hash, expected, "content_hash must be SHA-256 hex of full_content");
|
|
112
|
+
} finally {
|
|
113
|
+
cleanup(base);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("incrementMemoryHitCount sets last_hit_at alongside hit_count (V28 write-path)", () => {
|
|
118
|
+
const base = makeTmp();
|
|
119
|
+
const dbPath = path.join(base, "gsd.db");
|
|
120
|
+
try {
|
|
121
|
+
openDatabase(dbPath);
|
|
122
|
+
|
|
123
|
+
const created = "2026-01-01T00:00:00.000Z";
|
|
124
|
+
insertMemoryRow({
|
|
125
|
+
id: "MEM001",
|
|
126
|
+
category: "gotcha",
|
|
127
|
+
content: "test memory",
|
|
128
|
+
confidence: 0.9,
|
|
129
|
+
sourceUnitType: null,
|
|
130
|
+
sourceUnitId: null,
|
|
131
|
+
createdAt: created,
|
|
132
|
+
updatedAt: created,
|
|
133
|
+
scope: "project",
|
|
134
|
+
tags: [],
|
|
135
|
+
structuredFields: null,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Before increment: last_hit_at should be NULL
|
|
139
|
+
const db = _getAdapter()!;
|
|
140
|
+
const before = db
|
|
141
|
+
.prepare("SELECT last_hit_at FROM memories WHERE id = :id")
|
|
142
|
+
.get({ ":id": "MEM001" }) as Record<string, unknown>;
|
|
143
|
+
assert.equal(before["last_hit_at"], null, "last_hit_at starts NULL on fresh insert");
|
|
144
|
+
|
|
145
|
+
const hitTime = "2026-02-01T00:00:00.000Z";
|
|
146
|
+
incrementMemoryHitCount("MEM001", hitTime);
|
|
147
|
+
|
|
148
|
+
const after = db
|
|
149
|
+
.prepare("SELECT hit_count, last_hit_at FROM memories WHERE id = :id")
|
|
150
|
+
.get({ ":id": "MEM001" }) as Record<string, unknown>;
|
|
151
|
+
assert.equal(after["hit_count"], 1, "hit_count increments");
|
|
152
|
+
assert.equal(after["last_hit_at"], hitTime, "last_hit_at set to provided timestamp");
|
|
153
|
+
} finally {
|
|
154
|
+
cleanup(base);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
@@ -101,3 +101,30 @@ test("registerSigtermHandler deregisters previous handler from all signals", ()
|
|
|
101
101
|
// Clean up
|
|
102
102
|
deregisterSigtermHandler(handler2);
|
|
103
103
|
});
|
|
104
|
+
|
|
105
|
+
test("registered signal handler runs best-effort cleanup before exiting", () => {
|
|
106
|
+
let cleanupCalled = false;
|
|
107
|
+
let exitCode: number | string | null | undefined;
|
|
108
|
+
const originalExit = process.exit;
|
|
109
|
+
const handler = registerSigtermHandler(
|
|
110
|
+
"/tmp/test-signal-cleanup",
|
|
111
|
+
null,
|
|
112
|
+
() => {
|
|
113
|
+
cleanupCalled = true;
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
(process as any).exit = ((code?: number | string | null) => {
|
|
118
|
+
exitCode = code;
|
|
119
|
+
throw new Error("process.exit intercepted");
|
|
120
|
+
}) as never;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
assert.throws(() => handler(), /process\.exit intercepted/);
|
|
124
|
+
assert.equal(cleanupCalled, true);
|
|
125
|
+
assert.equal(exitCode, 0);
|
|
126
|
+
} finally {
|
|
127
|
+
(process as any).exit = originalExit;
|
|
128
|
+
deregisterSigtermHandler(handler);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
* (the fix) and verifies it does not crash.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
|
|
18
|
+
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
19
19
|
import { join } from "node:path";
|
|
20
20
|
import { tmpdir } from "node:os";
|
|
21
21
|
import { recoverTimedOutUnit, type RecoveryContext } from "../auto-timeout-recovery.ts";
|
|
22
|
+
import { closeDatabase, insertMilestone, insertSlice, insertTask, openDatabase } from "../gsd-db.ts";
|
|
22
23
|
import { test } from 'node:test';
|
|
23
24
|
import assert from 'node:assert/strict';
|
|
24
25
|
|
|
@@ -39,6 +40,14 @@ function makeMockPi() {
|
|
|
39
40
|
} as any;
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
function makeRecordingPi() {
|
|
44
|
+
const messages: unknown[] = [];
|
|
45
|
+
return {
|
|
46
|
+
messages,
|
|
47
|
+
sendMessage: (message: unknown) => { messages.push(message); },
|
|
48
|
+
} as any;
|
|
49
|
+
}
|
|
50
|
+
|
|
42
51
|
// ═══ #1855: empty RecoveryContext (basePath undefined) crashes ════════════════
|
|
43
52
|
|
|
44
53
|
{
|
|
@@ -63,6 +72,45 @@ function makeMockPi() {
|
|
|
63
72
|
assert.ok(crashed, "should crash when basePath is undefined (reproduces #1855)");
|
|
64
73
|
}
|
|
65
74
|
|
|
75
|
+
// ═══ DB-complete execute-task recovery advances without steering ═════════════
|
|
76
|
+
|
|
77
|
+
{
|
|
78
|
+
console.log("\n=== execute-task timeout recovery trusts closed DB status ===");
|
|
79
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-timeout-db-complete-"));
|
|
80
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks"), { recursive: true });
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
84
|
+
insertMilestone({ id: "M001", title: "Milestone", status: "active" });
|
|
85
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "in_progress" });
|
|
86
|
+
insertTask({ id: "T01", milestoneId: "M001", sliceId: "S01", title: "Task", status: "complete" });
|
|
87
|
+
writeFileSync(
|
|
88
|
+
join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md"),
|
|
89
|
+
"# S01\n\n## Tasks\n\n- [ ] **T01: Task** `est:10m`\n",
|
|
90
|
+
"utf-8",
|
|
91
|
+
);
|
|
92
|
+
writeFileSync(join(base, ".gsd", "STATE.md"), "## Next Action\nExecute T01 for S01: Task\n", "utf-8");
|
|
93
|
+
|
|
94
|
+
const ctx = makeMockCtx();
|
|
95
|
+
const pi = makeRecordingPi();
|
|
96
|
+
const result = await recoverTimedOutUnit(ctx, pi, "execute-task", "M001/S01/T01", "idle", {
|
|
97
|
+
basePath: base,
|
|
98
|
+
verbose: false,
|
|
99
|
+
currentUnitStartedAt: Date.now(),
|
|
100
|
+
unitRecoveryCount: new Map(),
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
assert.equal(result, "recovered", "db-complete task should recover immediately");
|
|
104
|
+
assert.equal(pi.messages.length, 0, "db-complete task should not send steering recovery");
|
|
105
|
+
const runtime = JSON.parse(readFileSync(join(base, ".gsd", "runtime", "units", "execute-task-M001-S01-T01.json"), "utf-8"));
|
|
106
|
+
assert.equal(runtime.phase, "finalized", "db-complete task should be finalized");
|
|
107
|
+
assert.equal(runtime.recovery.dbComplete, true, "runtime recovery should record DB completion");
|
|
108
|
+
} finally {
|
|
109
|
+
closeDatabase();
|
|
110
|
+
rmSync(base, { recursive: true, force: true });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
66
114
|
// ═══ #1855: valid RecoveryContext does not crash ═════════════════════════════
|
|
67
115
|
|
|
68
116
|
{
|
|
@@ -4,6 +4,7 @@ import { readFileSync } from "node:fs";
|
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
|
|
6
6
|
import { _withDetachedAutoKeepaliveForTest } from "../auto.ts";
|
|
7
|
+
import { _scheduleAutoStartAfterIdleForTest } from "../guided-flow.ts";
|
|
7
8
|
|
|
8
9
|
const gsdDir = resolve(import.meta.dirname, "..");
|
|
9
10
|
|
|
@@ -96,6 +97,23 @@ test("auto bootstrap validates blocked directories before touching .gsd migratio
|
|
|
96
97
|
);
|
|
97
98
|
});
|
|
98
99
|
|
|
100
|
+
test("fresh start registers the auto worker before bootstrap enters worktree flow (#5405)", () => {
|
|
101
|
+
const autoSrc = readGsdFile("auto.ts");
|
|
102
|
+
const startAutoIdx = autoSrc.indexOf("export async function startAuto(");
|
|
103
|
+
const startAutoBody = autoSrc.slice(startAutoIdx);
|
|
104
|
+
|
|
105
|
+
const preBootstrapRegisterIdx = startAutoBody.indexOf("registerAutoWorkerForSession(s, base);");
|
|
106
|
+
const bootstrapCallIdx = startAutoBody.indexOf("const ready = await bootstrapAutoSession(");
|
|
107
|
+
|
|
108
|
+
assert.ok(startAutoIdx > -1, "startAuto should exist");
|
|
109
|
+
assert.ok(preBootstrapRegisterIdx > -1, "startAuto should register worker before bootstrap");
|
|
110
|
+
assert.ok(bootstrapCallIdx > -1, "startAuto should call bootstrapAutoSession");
|
|
111
|
+
assert.ok(
|
|
112
|
+
preBootstrapRegisterIdx < bootstrapCallIdx,
|
|
113
|
+
"worker registration must happen before bootstrap so enterMilestone can claim milestone leases on first entry",
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
99
117
|
test("startAutoDetached reports failures asynchronously (#3733)", () => {
|
|
100
118
|
const autoSrc = readGsdFile("auto.ts");
|
|
101
119
|
|
|
@@ -185,3 +203,40 @@ test("detached auto-start preserves milestone lock across pause/stop cleanup (#3
|
|
|
185
203
|
"AutoSession should track the detached milestone lock explicitly",
|
|
186
204
|
);
|
|
187
205
|
});
|
|
206
|
+
|
|
207
|
+
test("discussion auto-start waits for the current command context to become idle", async () => {
|
|
208
|
+
let releaseIdle!: () => void;
|
|
209
|
+
const idle = new Promise<void>((resolveIdle) => {
|
|
210
|
+
releaseIdle = resolveIdle;
|
|
211
|
+
});
|
|
212
|
+
const launches: unknown[][] = [];
|
|
213
|
+
const ctx = {
|
|
214
|
+
waitForIdle: () => idle,
|
|
215
|
+
ui: {
|
|
216
|
+
notify: () => {},
|
|
217
|
+
},
|
|
218
|
+
} as any;
|
|
219
|
+
|
|
220
|
+
_scheduleAutoStartAfterIdleForTest(
|
|
221
|
+
ctx,
|
|
222
|
+
{} as any,
|
|
223
|
+
"/tmp/gsd-auto-start-idle-test",
|
|
224
|
+
false,
|
|
225
|
+
{ step: true },
|
|
226
|
+
(...args: unknown[]) => {
|
|
227
|
+
launches.push(args);
|
|
228
|
+
},
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
await Promise.resolve();
|
|
232
|
+
assert.equal(launches.length, 0, "auto-start must not launch before waitForIdle resolves");
|
|
233
|
+
|
|
234
|
+
releaseIdle();
|
|
235
|
+
await Promise.resolve();
|
|
236
|
+
assert.equal(launches.length, 0, "auto-start should defer launch to the next timer turn");
|
|
237
|
+
|
|
238
|
+
await new Promise((resolveTimer) => setTimeout(resolveTimer, 0));
|
|
239
|
+
assert.equal(launches.length, 1);
|
|
240
|
+
assert.equal(launches[0][2], "/tmp/gsd-auto-start-idle-test");
|
|
241
|
+
assert.deepEqual(launches[0][4], { step: true });
|
|
242
|
+
});
|
|
@@ -44,4 +44,13 @@ describe('status opens DB before deriveState (#3691)', () => {
|
|
|
44
44
|
assert.match(quickSrc, /getIsolationMode\(\)\s*!==\s*"none"/,
|
|
45
45
|
'quick.ts should compare isolation mode against "none"');
|
|
46
46
|
});
|
|
47
|
+
|
|
48
|
+
test('quick task prompt handles external .gsd without staging quick files', () => {
|
|
49
|
+
assert.match(quickSrc, /isExternalGsdRoot/,
|
|
50
|
+
'quick.ts should detect whether .gsd resolves outside the project repo');
|
|
51
|
+
assert.match(quickSrc, /do not stage or commit `\.gsd\/quick\/\.\.\.`/,
|
|
52
|
+
'external-state quick tasks must tell the agent not to stage .gsd/quick files');
|
|
53
|
+
assert.match(quickSrc, /nothing in the project repo to commit/,
|
|
54
|
+
'external-state quick tasks should allow summary-only work without a git commit');
|
|
55
|
+
});
|
|
47
56
|
});
|
|
@@ -21,12 +21,18 @@ import type {
|
|
|
21
21
|
ComputedArtifactRegistry,
|
|
22
22
|
UnitContextManifest,
|
|
23
23
|
} from "../unit-context-manifest.ts";
|
|
24
|
-
import { UNIT_MANIFESTS } from "../unit-context-manifest.ts";
|
|
25
|
-
import {
|
|
24
|
+
import { KNOWN_UNIT_TYPES, UNIT_MANIFESTS } from "../unit-context-manifest.ts";
|
|
25
|
+
import {
|
|
26
|
+
buildExecuteTaskPrompt,
|
|
27
|
+
buildGateEvaluatePrompt,
|
|
28
|
+
buildReassessRoadmapPrompt,
|
|
29
|
+
buildWorkflowPreferencesPrompt,
|
|
30
|
+
} from "../auto-prompts.ts";
|
|
26
31
|
import { invalidateAllCaches } from "../cache.ts";
|
|
27
32
|
import {
|
|
28
33
|
openDatabase,
|
|
29
34
|
closeDatabase,
|
|
35
|
+
insertGateRow,
|
|
30
36
|
insertMilestone,
|
|
31
37
|
upsertMilestonePlanning,
|
|
32
38
|
insertSlice,
|
|
@@ -121,7 +127,7 @@ test("Context Mode composer: standalone output starts with heading and includes
|
|
|
121
127
|
assert.ok(out.startsWith("## Context Mode"));
|
|
122
128
|
assert.match(out, /execution lane/i);
|
|
123
129
|
assert.match(out, /`gsd_exec`/);
|
|
124
|
-
assert.match(out, /
|
|
130
|
+
assert.match(out, /builds, tests, and diagnostics/);
|
|
125
131
|
assert.match(out, /`gsd_exec_search`/);
|
|
126
132
|
assert.match(out, /before reruns/);
|
|
127
133
|
assert.match(out, /`gsd_resume`/);
|
|
@@ -136,7 +142,45 @@ test("Context Mode composer: nested output is compact single sentence", () => {
|
|
|
136
142
|
assert.match(out, /`gsd_exec`/);
|
|
137
143
|
assert.match(out, /`gsd_exec_search`/);
|
|
138
144
|
assert.match(out, /`gsd_resume`/);
|
|
139
|
-
assert.ok(out.length <
|
|
145
|
+
assert.ok(out.length < 240, `nested guidance should stay compact, got ${out.length} chars`);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const laneLabelByMode: Record<string, string> = {
|
|
149
|
+
interview: "interview",
|
|
150
|
+
research: "research",
|
|
151
|
+
planning: "planning",
|
|
152
|
+
execution: "execution",
|
|
153
|
+
verification: "verification",
|
|
154
|
+
orchestration: "orchestration",
|
|
155
|
+
docs: "documentation",
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
test("Context Mode composer: every known eligible unit renders its configured lane and required tools", () => {
|
|
159
|
+
for (const unitType of KNOWN_UNIT_TYPES) {
|
|
160
|
+
const manifest = UNIT_MANIFESTS[unitType];
|
|
161
|
+
assert.ok(manifest, `missing manifest for ${unitType}`);
|
|
162
|
+
const out = composeContextModeInstructions(unitType, { enabled: true, renderMode: "standalone" });
|
|
163
|
+
if (manifest.contextMode === "none") {
|
|
164
|
+
assert.strictEqual(out, "", `${unitType} should not render Context Mode`);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
assert.ok(out.startsWith("## Context Mode"), `${unitType} should render standalone Context Mode heading`);
|
|
168
|
+
assert.match(out, new RegExp(`Lane: \\*\\*${laneLabelByMode[manifest.contextMode]} lane\\*\\*\\.`, "i"));
|
|
169
|
+
assert.match(out, /`gsd_exec`/, `${unitType} should mention gsd_exec`);
|
|
170
|
+
assert.match(out, /`gsd_exec_search`/, `${unitType} should mention gsd_exec_search`);
|
|
171
|
+
assert.match(out, /`gsd_resume`/, `${unitType} should mention gsd_resume`);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("Context Mode composer: workflow-preferences and research-decision render no Context Mode block", () => {
|
|
176
|
+
assert.strictEqual(
|
|
177
|
+
composeContextModeInstructions("workflow-preferences", { enabled: true, renderMode: "standalone" }),
|
|
178
|
+
"",
|
|
179
|
+
);
|
|
180
|
+
assert.strictEqual(
|
|
181
|
+
composeContextModeInstructions("research-decision", { enabled: true, renderMode: "standalone" }),
|
|
182
|
+
"",
|
|
183
|
+
);
|
|
140
184
|
});
|
|
141
185
|
|
|
142
186
|
// ─── Integration: migrated buildReassessRoadmapPrompt ─────────────────────
|
|
@@ -220,6 +264,94 @@ test("#4782 phase 2: buildReassessRoadmapPrompt emits composer-shaped context wi
|
|
|
220
264
|
assert.ok(!prompt.includes("Slice Context (from discussion)"));
|
|
221
265
|
});
|
|
222
266
|
|
|
267
|
+
test("Context Mode resume injection: eligible prompts include one bounded snapshot block above inlined context", async (t) => {
|
|
268
|
+
const base = makeFixtureBase();
|
|
269
|
+
t.after(() => cleanup(base));
|
|
270
|
+
invalidateAllCaches();
|
|
271
|
+
|
|
272
|
+
seed(base, "M001");
|
|
273
|
+
writeArtifacts(base);
|
|
274
|
+
writeFileSync(
|
|
275
|
+
join(base, ".gsd", "last-snapshot.md"),
|
|
276
|
+
"# GSD context snapshot\n\nResume evidence.\n",
|
|
277
|
+
"utf-8",
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const prompt = await buildReassessRoadmapPrompt("M001", "Test", "S01", base);
|
|
281
|
+
|
|
282
|
+
assert.equal(prompt.match(/## Context Snapshot/g)?.length, 1);
|
|
283
|
+
assert.match(prompt, /Source: `\.gsd\/last-snapshot\.md`/);
|
|
284
|
+
assert.match(prompt, /Resume evidence/);
|
|
285
|
+
assert.ok(prompt.indexOf("## Context Mode") < prompt.indexOf("## Context Snapshot"));
|
|
286
|
+
assert.ok(prompt.indexOf("## Context Snapshot") < prompt.indexOf("## Inlined Context"));
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("Context Mode resume injection: missing snapshot does not add an empty block", async (t) => {
|
|
290
|
+
const base = makeFixtureBase();
|
|
291
|
+
t.after(() => cleanup(base));
|
|
292
|
+
invalidateAllCaches();
|
|
293
|
+
|
|
294
|
+
seed(base, "M001");
|
|
295
|
+
writeArtifacts(base);
|
|
296
|
+
|
|
297
|
+
const prompt = await buildReassessRoadmapPrompt("M001", "Test", "S01", base);
|
|
298
|
+
|
|
299
|
+
assert.match(prompt, /## Context Mode/);
|
|
300
|
+
assert.doesNotMatch(prompt, /## Context Snapshot/);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("Context Mode resume injection: disabled mode suppresses guidance and snapshot reads", async (t) => {
|
|
304
|
+
const base = makeFixtureBase();
|
|
305
|
+
t.after(() => cleanup(base));
|
|
306
|
+
invalidateAllCaches();
|
|
307
|
+
|
|
308
|
+
seed(base, "M001");
|
|
309
|
+
writeArtifacts(base);
|
|
310
|
+
writeFileSync(join(base, ".gsd", "PREFERENCES.md"), "---\ncontext_mode:\n enabled: false\n---\n", "utf-8");
|
|
311
|
+
writeFileSync(join(base, ".gsd", "last-snapshot.md"), "# GSD context snapshot\n\nDo not inject.\n", "utf-8");
|
|
312
|
+
|
|
313
|
+
const prompt = await buildReassessRoadmapPrompt("M001", "Test", "S01", base);
|
|
314
|
+
|
|
315
|
+
assert.doesNotMatch(prompt, /## Context Mode/);
|
|
316
|
+
assert.doesNotMatch(prompt, /## Context Snapshot/);
|
|
317
|
+
assert.doesNotMatch(prompt, /Do not inject/);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("Context Mode resume injection: none-mode units do not inject snapshots", async () => {
|
|
321
|
+
const base = makeFixtureBase();
|
|
322
|
+
try {
|
|
323
|
+
writeFileSync(join(base, ".gsd", "last-snapshot.md"), "# GSD context snapshot\n\nNo lane.\n", "utf-8");
|
|
324
|
+
const prompt = await buildWorkflowPreferencesPrompt(base);
|
|
325
|
+
assert.doesNotMatch(prompt, /## Context Mode/);
|
|
326
|
+
assert.doesNotMatch(prompt, /## Context Snapshot/);
|
|
327
|
+
assert.doesNotMatch(prompt, /No lane/);
|
|
328
|
+
} finally {
|
|
329
|
+
cleanup(base);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test("Context Mode prompt suppression: disabled inlined, phase-anchor, and nested prompts omit Context Mode", async (t) => {
|
|
334
|
+
const base = makeFixtureBase();
|
|
335
|
+
t.after(() => cleanup(base));
|
|
336
|
+
invalidateAllCaches();
|
|
337
|
+
|
|
338
|
+
seed(base, "M001");
|
|
339
|
+
writeArtifacts(base);
|
|
340
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
|
|
341
|
+
writeFileSync(join(base, ".gsd", "PREFERENCES.md"), "---\ncontext_mode:\n enabled: false\n---\n", "utf-8");
|
|
342
|
+
writeFileSync(join(base, ".gsd", "last-snapshot.md"), "# GSD context snapshot\n\nDo not inject.\n", "utf-8");
|
|
343
|
+
|
|
344
|
+
const inlinedPrompt = await buildReassessRoadmapPrompt("M001", "Test", "S01", base);
|
|
345
|
+
assert.doesNotMatch(inlinedPrompt, /## Context Mode|Context Mode \(|## Context Snapshot/);
|
|
346
|
+
|
|
347
|
+
const phaseAnchorPrompt = await buildExecuteTaskPrompt("M001", "S01", "First", "T01", "Task", base);
|
|
348
|
+
assert.doesNotMatch(phaseAnchorPrompt, /## Context Mode|Context Mode \(|## Context Snapshot/);
|
|
349
|
+
|
|
350
|
+
const nestedPrompt = await buildGateEvaluatePrompt("M001", "Test", "S01", "First", base);
|
|
351
|
+
assert.match(nestedPrompt, /Use this as the prompt for a `subagent` call/);
|
|
352
|
+
assert.doesNotMatch(nestedPrompt, /## Context Mode|Context Mode \(|## Context Snapshot/);
|
|
353
|
+
});
|
|
354
|
+
|
|
223
355
|
// ─── v2 surface (#4924) ───────────────────────────────────────────────────
|
|
224
356
|
|
|
225
357
|
const fakeBase: BaseResolverContext = {
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
markFailed,
|
|
22
22
|
markStuck,
|
|
23
23
|
markCanceled,
|
|
24
|
+
markLatestActiveForWorkerCanceled,
|
|
24
25
|
getRecentForUnit,
|
|
25
26
|
getLatestForUnit,
|
|
26
27
|
} from "../db/unit-dispatches.ts";
|
|
@@ -196,6 +197,35 @@ test("markStuck and markCanceled set their respective statuses", (t) => {
|
|
|
196
197
|
assert.equal(getLatestForUnit("M001/S01/T01")!.status, "canceled");
|
|
197
198
|
});
|
|
198
199
|
|
|
200
|
+
test("markLatestActiveForWorkerCanceled cancels only the latest active dispatch for a worker", (t) => {
|
|
201
|
+
const base = makeBase();
|
|
202
|
+
t.after(() => cleanup(base));
|
|
203
|
+
const { workerId, leaseToken } = setup(base);
|
|
204
|
+
|
|
205
|
+
const first = recordDispatchClaim({
|
|
206
|
+
traceId: "tc-1", workerId, milestoneLeaseToken: leaseToken,
|
|
207
|
+
milestoneId: "M001", unitType: "plan-slice", unitId: "M001/S01",
|
|
208
|
+
});
|
|
209
|
+
assert.equal(first.ok, true);
|
|
210
|
+
if (!first.ok) return;
|
|
211
|
+
markCompleted(first.dispatchId);
|
|
212
|
+
|
|
213
|
+
const second = recordDispatchClaim({
|
|
214
|
+
traceId: "tc-2", workerId, milestoneLeaseToken: leaseToken,
|
|
215
|
+
milestoneId: "M001", unitType: "run-task", unitId: "M001/S01/T01",
|
|
216
|
+
});
|
|
217
|
+
assert.equal(second.ok, true);
|
|
218
|
+
if (!second.ok) return;
|
|
219
|
+
markRunning(second.dispatchId);
|
|
220
|
+
|
|
221
|
+
assert.equal(markLatestActiveForWorkerCanceled(workerId, "signal-exit"), true);
|
|
222
|
+
assert.equal(getLatestForUnit("M001/S01")!.status, "completed");
|
|
223
|
+
const latest = getLatestForUnit("M001/S01/T01")!;
|
|
224
|
+
assert.equal(latest.status, "canceled");
|
|
225
|
+
assert.equal(latest.exit_reason, "signal-exit");
|
|
226
|
+
assert.equal(markLatestActiveForWorkerCanceled(workerId, "signal-exit"), false);
|
|
227
|
+
});
|
|
228
|
+
|
|
199
229
|
test("terminal transitions do not overwrite an already terminal dispatch", (t) => {
|
|
200
230
|
const base = makeBase();
|
|
201
231
|
t.after(() => cleanup(base));
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
readUnitRuntimeRecord,
|
|
10
10
|
writeUnitRuntimeRecord,
|
|
11
11
|
} from "../unit-runtime.ts";
|
|
12
|
+
import { closeDatabase, insertMilestone, insertSlice, insertTask, openDatabase } from "../gsd-db.ts";
|
|
12
13
|
import { clearPathCache } from '../paths.ts';
|
|
13
14
|
import { test } from 'node:test';
|
|
14
15
|
import assert from 'node:assert/strict';
|
|
@@ -72,6 +73,35 @@ console.log("\n=== runtime record cleanup ===");
|
|
|
72
73
|
assert.deepStrictEqual(loaded, null, "record removed");
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
console.log("\n=== execute-task durability trusts closed DB task status ===");
|
|
77
|
+
{
|
|
78
|
+
const dbBase = mkdtempSync(join(tmpdir(), "gsd-unit-runtime-db-test-"));
|
|
79
|
+
mkdirSync(join(dbBase, ".gsd", "milestones", "M300", "slices", "S01", "tasks"), { recursive: true });
|
|
80
|
+
try {
|
|
81
|
+
openDatabase(join(dbBase, ".gsd", "gsd.db"));
|
|
82
|
+
insertMilestone({ id: "M300", title: "DB Milestone", status: "active" });
|
|
83
|
+
insertSlice({ id: "S01", milestoneId: "M300", title: "DB Slice", status: "in_progress" });
|
|
84
|
+
insertTask({ id: "T01", milestoneId: "M300", sliceId: "S01", title: "DB Task", status: "complete" });
|
|
85
|
+
writeFileSync(
|
|
86
|
+
join(dbBase, ".gsd", "milestones", "M300", "slices", "S01", "S01-PLAN.md"),
|
|
87
|
+
"# S01\n\n## Tasks\n\n- [ ] **T01: DB Task** `est:10m`\n",
|
|
88
|
+
"utf-8",
|
|
89
|
+
);
|
|
90
|
+
writeFileSync(join(dbBase, ".gsd", "STATE.md"), "## Next Action\nExecute T01 for S01: DB task\n", "utf-8");
|
|
91
|
+
|
|
92
|
+
const status = await inspectExecuteTaskDurability(dbBase, "M300/S01/T01");
|
|
93
|
+
assert.ok(status !== null, "db-complete: status exists");
|
|
94
|
+
assert.equal(status!.dbComplete, true, "db-complete: closed DB status is captured");
|
|
95
|
+
assert.equal(status!.summaryExists, false, "db-complete: summary can still be missing");
|
|
96
|
+
assert.equal(status!.taskChecked, false, "db-complete: checkbox can still be unchecked");
|
|
97
|
+
assert.equal(status!.nextActionAdvanced, false, "db-complete: next action can still point at task");
|
|
98
|
+
assert.equal(formatExecuteTaskRecoveryStatus(status!), "DB task status is closed");
|
|
99
|
+
} finally {
|
|
100
|
+
closeDatabase();
|
|
101
|
+
rmSync(dbBase, { recursive: true, force: true });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
75
105
|
console.log("\n=== hook unit type sanitization (slash in unitType) ===");
|
|
76
106
|
{
|
|
77
107
|
// Hook units have unitType like "hook/code-review" with a slash
|
|
@@ -165,6 +165,9 @@ test("executeMilestoneStatus returns milestone metadata and slice counts", async
|
|
|
165
165
|
assert.equal(parsed.sliceCount, 1);
|
|
166
166
|
assert.equal(parsed.slices[0].id, "S01");
|
|
167
167
|
assert.equal(parsed.slices[0].taskCounts.pending, 1);
|
|
168
|
+
assert.equal(result.details.status, "active");
|
|
169
|
+
assert.equal(result.details.title, "Milestone One");
|
|
170
|
+
assert.deepEqual(result.details.slices, parsed.slices);
|
|
168
171
|
} finally {
|
|
169
172
|
closeDatabase();
|
|
170
173
|
cleanup(base);
|