gsd-pi 2.80.0-dev.96f4d89d5 → 2.80.0-dev.b2a374d5b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +2 -2
- package/dist/resources/extensions/gsd/auto/loop.js +32 -1
- package/dist/resources/extensions/gsd/auto/phases.js +37 -30
- package/dist/resources/extensions/gsd/auto-dispatch.js +10 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +10 -10
- package/dist/resources/extensions/gsd/auto-prompts.js +111 -1
- package/dist/resources/extensions/gsd/auto-recovery.js +154 -8
- package/dist/resources/extensions/gsd/auto-start.js +2 -3
- package/dist/resources/extensions/gsd/auto.js +16 -3
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +129 -1
- package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
- package/dist/resources/extensions/gsd/commands/dispatcher.js +5 -0
- package/dist/resources/extensions/gsd/commands-extract-learnings.js +17 -12
- package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
- package/dist/resources/extensions/gsd/db-base-schema.js +14 -0
- package/dist/resources/extensions/gsd/db-migration-steps.js +16 -0
- package/dist/resources/extensions/gsd/detection.js +106 -0
- package/dist/resources/extensions/gsd/gsd-db.js +102 -2
- package/dist/resources/extensions/gsd/guided-flow.js +63 -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/planning-path-scope.js +26 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.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/safety/evidence-collector.js +10 -2
- package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
- package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
- package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
- 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 +13 -13
- 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 +13 -13
- 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 +4 -2
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +3 -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 +76 -0
- 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/db-snapshot.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -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 +17 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +99 -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/__tests__/chat-frame-compaction-tone.test.js +6 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +50 -19
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +7 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +13 -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 +77 -13
- 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 +68 -2
- 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/dist/modes/interactive/theme/themes.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +36 -27
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +21 -0
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +3 -0
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +89 -0
- 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/db-snapshot.test.ts +32 -0
- package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +110 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +19 -1
- 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/__tests__/chat-frame-compaction-tone.test.ts +7 -5
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +68 -19
- package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +2 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +10 -9
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +88 -13
- 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 +91 -3
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +36 -27
- package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +25 -0
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
- 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 +17 -7
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/tui.ts +19 -7
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/themes.js +36 -27
- package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
- package/src/resources/GSD-WORKFLOW.md +2 -2
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/loop.ts +50 -8
- package/src/resources/extensions/gsd/auto/phases.ts +42 -28
- package/src/resources/extensions/gsd/auto-dispatch.ts +17 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
- package/src/resources/extensions/gsd/auto-prompts.ts +116 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +153 -7
- package/src/resources/extensions/gsd/auto-start.ts +7 -6
- package/src/resources/extensions/gsd/auto.ts +23 -3
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +39 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +135 -1
- package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
- package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
- package/src/resources/extensions/gsd/commands-extract-learnings.ts +17 -12
- package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
- package/src/resources/extensions/gsd/db-base-schema.ts +15 -0
- package/src/resources/extensions/gsd/db-migration-steps.ts +17 -0
- package/src/resources/extensions/gsd/detection.ts +128 -0
- package/src/resources/extensions/gsd/gsd-db.ts +119 -1
- package/src/resources/extensions/gsd/guided-flow.ts +66 -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/planning-path-scope.ts +35 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.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/safety/evidence-collector.ts +11 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +170 -1
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
- package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/db-schema-metadata.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/has-pending-deep-stage.test.ts +33 -1
- 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/plan-slice.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +63 -2
- package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
- package/src/resources/extensions/gsd/tests/worktree-write-gate.test.ts +179 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
- package/src/resources/extensions/gsd/unit-runtime.ts +14 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
- /package/dist/web/standalone/.next/static/{y7d--cnc6Cibuva-Lzzw_ → FU0iL9dhdc2IBzEYBDOlY}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{y7d--cnc6Cibuva-Lzzw_ → FU0iL9dhdc2IBzEYBDOlY}/_ssgManifest.js +0 -0
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
isLockProcessAlive,
|
|
31
31
|
} from "../crash-recovery.ts";
|
|
32
32
|
import { normalizeRealPath } from "../paths.ts";
|
|
33
|
+
import { writeUnitRuntimeRecord } from "../unit-runtime.ts";
|
|
33
34
|
|
|
34
35
|
function makeBase(): string {
|
|
35
36
|
const base = mkdtempSync(join(tmpdir(), "gsd-crash-recovery-"));
|
|
@@ -102,6 +103,27 @@ test("readCrashLock synthesizes LockData from a stale dead worker (no dispatches
|
|
|
102
103
|
assert.ok(lock!.startedAt, "startedAt populated from workers.started_at");
|
|
103
104
|
});
|
|
104
105
|
|
|
106
|
+
test("readCrashLock falls back to latest in-flight runtime record when dispatch claim is missing", (t) => {
|
|
107
|
+
const base = makeBase();
|
|
108
|
+
t.after(() => cleanup(base));
|
|
109
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
110
|
+
const projectRoot = normalizeRealPath(base);
|
|
111
|
+
const workerId = registerAutoWorker({ projectRootRealpath: projectRoot });
|
|
112
|
+
writeUnitRuntimeRecord(base, "execute-task", "M008/S04/T02", 1778069087937, {
|
|
113
|
+
phase: "dispatched",
|
|
114
|
+
lastProgressAt: 1778069087937,
|
|
115
|
+
lastProgressKind: "dispatch",
|
|
116
|
+
});
|
|
117
|
+
setWorkerPid(workerId, 99999);
|
|
118
|
+
expireWorker(workerId);
|
|
119
|
+
|
|
120
|
+
const lock = readCrashLock(base);
|
|
121
|
+
assert.ok(lock, "stale worker surfaced as a crash lock");
|
|
122
|
+
assert.equal(lock!.unitType, "execute-task");
|
|
123
|
+
assert.equal(lock!.unitId, "M008/S04/T02");
|
|
124
|
+
assert.equal(lock!.unitStartedAt, new Date(1778069087937).toISOString());
|
|
125
|
+
});
|
|
126
|
+
|
|
105
127
|
test("readCrashLock includes the most recent dispatch as unitType/unitId", (t) => {
|
|
106
128
|
const base = makeBase();
|
|
107
129
|
t.after(() => cleanup(base));
|
|
@@ -106,10 +106,10 @@ describe("db-schema-metadata", () => {
|
|
|
106
106
|
test("records schema version rows with timestamps", () => {
|
|
107
107
|
const db = new FakeAdapter();
|
|
108
108
|
|
|
109
|
-
recordSchemaVersion(db,
|
|
109
|
+
recordSchemaVersion(db, 26);
|
|
110
110
|
|
|
111
111
|
assert.equal(db.runCalls.length, 1);
|
|
112
|
-
assert.equal((db.runCalls[0][0] as Record<string, unknown>)[":version"],
|
|
112
|
+
assert.equal((db.runCalls[0][0] as Record<string, unknown>)[":version"], 26);
|
|
113
113
|
assert.equal(typeof (db.runCalls[0][0] as Record<string, unknown>)[":applied_at"], "string");
|
|
114
114
|
});
|
|
115
115
|
});
|
|
@@ -11,12 +11,14 @@
|
|
|
11
11
|
import test from "node:test";
|
|
12
12
|
import assert from "node:assert/strict";
|
|
13
13
|
import { mkdirSync, writeFileSync, rmSync, existsSync } from "node:fs";
|
|
14
|
+
import { execFileSync } from "node:child_process";
|
|
14
15
|
import { join } from "node:path";
|
|
15
16
|
import { tmpdir } from "node:os";
|
|
16
17
|
import {
|
|
17
18
|
detectProjectState,
|
|
18
19
|
detectV1Planning,
|
|
19
20
|
detectProjectSignals,
|
|
21
|
+
classifyProject,
|
|
20
22
|
scanProjectFiles,
|
|
21
23
|
} from "../detection.ts";
|
|
22
24
|
|
|
@@ -37,6 +39,18 @@ function cleanup(dir: string): void {
|
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
function git(dir: string, args: string[]): void {
|
|
43
|
+
execFileSync("git", args, { cwd: dir, stdio: "ignore" });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function makeGitRepo(prefix: string): string {
|
|
47
|
+
const dir = makeTempDir(prefix);
|
|
48
|
+
git(dir, ["init"]);
|
|
49
|
+
git(dir, ["config", "user.email", "test@example.com"]);
|
|
50
|
+
git(dir, ["config", "user.name", "Test User"]);
|
|
51
|
+
return dir;
|
|
52
|
+
}
|
|
53
|
+
|
|
40
54
|
// ─── detectProjectState ─────────────────────────────────────────────────────────
|
|
41
55
|
|
|
42
56
|
test("detectProjectState: empty directory returns state=none", (t) => {
|
|
@@ -49,6 +63,132 @@ test("detectProjectState: empty directory returns state=none", (t) => {
|
|
|
49
63
|
assert.equal(result.v2, undefined);
|
|
50
64
|
});
|
|
51
65
|
|
|
66
|
+
test("classifyProject: no git repo is invalid", (t) => {
|
|
67
|
+
const dir = makeTempDir("classify-invalid");
|
|
68
|
+
t.after(() => cleanup(dir));
|
|
69
|
+
|
|
70
|
+
const classification = classifyProject(dir);
|
|
71
|
+
assert.equal(classification.kind, "invalid-repo");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("classifyProject: empty git repo is greenfield", (t) => {
|
|
75
|
+
const dir = makeGitRepo("classify-greenfield");
|
|
76
|
+
t.after(() => cleanup(dir));
|
|
77
|
+
|
|
78
|
+
const classification = classifyProject(dir);
|
|
79
|
+
assert.equal(classification.kind, "greenfield");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("classifyProject: nested empty git repo does not inherit ancestor markers", (t) => {
|
|
83
|
+
const parent = makeGitRepo("classify-parent-marker");
|
|
84
|
+
t.after(() => cleanup(parent));
|
|
85
|
+
|
|
86
|
+
writeFileSync(join(parent, "package.json"), JSON.stringify({ name: "parent" }), "utf-8");
|
|
87
|
+
git(parent, ["add", "package.json"]);
|
|
88
|
+
git(parent, ["commit", "-m", "add parent marker"]);
|
|
89
|
+
const child = join(parent, "nested");
|
|
90
|
+
mkdirSync(child, { recursive: true });
|
|
91
|
+
git(child, ["init"]);
|
|
92
|
+
git(child, ["config", "user.email", "test@example.com"]);
|
|
93
|
+
git(child, ["config", "user.name", "Test User"]);
|
|
94
|
+
|
|
95
|
+
const classification = classifyProject(child);
|
|
96
|
+
assert.equal(classification.kind, "greenfield");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("classifyProject: tracked static HTML is existing untyped content", (t) => {
|
|
100
|
+
const dir = makeGitRepo("classify-index");
|
|
101
|
+
t.after(() => cleanup(dir));
|
|
102
|
+
|
|
103
|
+
writeFileSync(join(dir, "index.html"), "<main></main>\n", "utf-8");
|
|
104
|
+
git(dir, ["add", "index.html"]);
|
|
105
|
+
git(dir, ["commit", "-m", "add static page"]);
|
|
106
|
+
|
|
107
|
+
const classification = classifyProject(dir);
|
|
108
|
+
assert.equal(classification.kind, "untyped-existing");
|
|
109
|
+
assert.deepEqual(classification.contentFiles, ["index.html"]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("classifyProject: README-only repo is existing untyped content", (t) => {
|
|
113
|
+
const dir = makeGitRepo("classify-readme");
|
|
114
|
+
t.after(() => cleanup(dir));
|
|
115
|
+
|
|
116
|
+
writeFileSync(join(dir, "README.md"), "# docs\n", "utf-8");
|
|
117
|
+
git(dir, ["add", "README.md"]);
|
|
118
|
+
git(dir, ["commit", "-m", "add docs"]);
|
|
119
|
+
|
|
120
|
+
const classification = classifyProject(dir);
|
|
121
|
+
assert.equal(classification.kind, "untyped-existing");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("classifyProject: src-only content is untyped existing, not typed marker", (t) => {
|
|
125
|
+
const dir = makeGitRepo("classify-src-only");
|
|
126
|
+
t.after(() => cleanup(dir));
|
|
127
|
+
|
|
128
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
129
|
+
writeFileSync(join(dir, "src", "index.txt"), "content\n", "utf-8");
|
|
130
|
+
git(dir, ["add", "src/index.txt"]);
|
|
131
|
+
git(dir, ["commit", "-m", "add source content"]);
|
|
132
|
+
|
|
133
|
+
const classification = classifyProject(dir);
|
|
134
|
+
assert.equal(classification.kind, "untyped-existing");
|
|
135
|
+
assert.deepEqual(classification.contentFiles, ["src/index.txt"]);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("classifyProject: nested untracked files count as project content", (t) => {
|
|
139
|
+
const dir = makeGitRepo("classify-untracked-nested");
|
|
140
|
+
t.after(() => cleanup(dir));
|
|
141
|
+
|
|
142
|
+
mkdirSync(join(dir, "docs"), { recursive: true });
|
|
143
|
+
writeFileSync(join(dir, "docs", "index.html"), "<main></main>\n", "utf-8");
|
|
144
|
+
|
|
145
|
+
const classification = classifyProject(dir);
|
|
146
|
+
assert.equal(classification.kind, "untyped-existing");
|
|
147
|
+
assert.deepEqual(classification.untrackedFiles, ["docs/index.html"]);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("classifyProject: known markers produce typed existing project", (t) => {
|
|
151
|
+
const dir = makeGitRepo("classify-typed");
|
|
152
|
+
t.after(() => cleanup(dir));
|
|
153
|
+
|
|
154
|
+
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "typed" }), "utf-8");
|
|
155
|
+
git(dir, ["add", "package.json"]);
|
|
156
|
+
git(dir, ["commit", "-m", "add package"]);
|
|
157
|
+
|
|
158
|
+
const classification = classifyProject(dir);
|
|
159
|
+
assert.equal(classification.kind, "typed-existing");
|
|
160
|
+
assert.ok(classification.markers.includes("package.json"));
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("classifyProject: ignored build/cache-only files do not count as content", (t) => {
|
|
164
|
+
const dir = makeGitRepo("classify-ignored");
|
|
165
|
+
t.after(() => cleanup(dir));
|
|
166
|
+
|
|
167
|
+
writeFileSync(join(dir, ".gitignore"), "dist/\n.cache/\n", "utf-8");
|
|
168
|
+
git(dir, ["add", ".gitignore"]);
|
|
169
|
+
git(dir, ["commit", "-m", "ignore generated files"]);
|
|
170
|
+
mkdirSync(join(dir, "dist"), { recursive: true });
|
|
171
|
+
writeFileSync(join(dir, "dist", "bundle.js"), "generated\n", "utf-8");
|
|
172
|
+
mkdirSync(join(dir, ".cache"), { recursive: true });
|
|
173
|
+
writeFileSync(join(dir, ".cache", "x"), "cache\n", "utf-8");
|
|
174
|
+
|
|
175
|
+
const classification = classifyProject(dir);
|
|
176
|
+
assert.equal(classification.kind, "greenfield");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("classifyProject: generated framework/cache dirs do not count as content", (t) => {
|
|
180
|
+
const dir = makeGitRepo("classify-generated-dirs");
|
|
181
|
+
t.after(() => cleanup(dir));
|
|
182
|
+
|
|
183
|
+
mkdirSync(join(dir, ".next", "server"), { recursive: true });
|
|
184
|
+
writeFileSync(join(dir, ".next", "server", "page.js"), "generated\n", "utf-8");
|
|
185
|
+
mkdirSync(join(dir, ".venv", "lib"), { recursive: true });
|
|
186
|
+
writeFileSync(join(dir, ".venv", "lib", "site.py"), "generated\n", "utf-8");
|
|
187
|
+
|
|
188
|
+
const classification = classifyProject(dir);
|
|
189
|
+
assert.equal(classification.kind, "greenfield");
|
|
190
|
+
});
|
|
191
|
+
|
|
52
192
|
test("detectProjectState: directory with .gsd/milestones/M001 returns v2-gsd", (t) => {
|
|
53
193
|
const dir = makeTempDir("v2-gsd");
|
|
54
194
|
t.after(() => cleanup(dir));
|
|
@@ -30,6 +30,10 @@ import {
|
|
|
30
30
|
insertTask,
|
|
31
31
|
getTask,
|
|
32
32
|
getSliceTasks,
|
|
33
|
+
deleteMilestone,
|
|
34
|
+
clearEngineHierarchy,
|
|
35
|
+
recordMilestoneCommitAttribution,
|
|
36
|
+
getMilestoneCommitAttributionShas,
|
|
33
37
|
checkpointDatabase,
|
|
34
38
|
refreshOpenDatabaseFromDisk,
|
|
35
39
|
tryCreateMemoriesFts,
|
|
@@ -1047,6 +1051,46 @@ describe('gsd-db', () => {
|
|
|
1047
1051
|
});
|
|
1048
1052
|
});
|
|
1049
1053
|
|
|
1054
|
+
// ─── milestone_commit_attributions teardown ───────────────────────────────
|
|
1055
|
+
|
|
1056
|
+
describe('milestone commit attribution teardown', () => {
|
|
1057
|
+
test('deleteMilestone removes persisted milestone commit attributions', () => {
|
|
1058
|
+
openDatabase(':memory:');
|
|
1059
|
+
insertMilestone({ id: 'M001', title: 'Milestone', status: 'active' });
|
|
1060
|
+
recordMilestoneCommitAttribution({
|
|
1061
|
+
commitSha: '0123456789abcdef0123456789abcdef01234567',
|
|
1062
|
+
milestoneId: 'M001',
|
|
1063
|
+
source: 'backfill',
|
|
1064
|
+
confidence: 0.8,
|
|
1065
|
+
files: ['app.js'],
|
|
1066
|
+
createdAt: '2026-05-05T00:00:00.000Z',
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
assert.deepEqual(getMilestoneCommitAttributionShas('M001'), ['0123456789abcdef0123456789abcdef01234567']);
|
|
1070
|
+
deleteMilestone('M001');
|
|
1071
|
+
assert.deepEqual(getMilestoneCommitAttributionShas('M001'), []);
|
|
1072
|
+
closeDatabase();
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
test('clearEngineHierarchy removes persisted milestone commit attributions', () => {
|
|
1076
|
+
openDatabase(':memory:');
|
|
1077
|
+
insertMilestone({ id: 'M001', title: 'Milestone', status: 'active' });
|
|
1078
|
+
recordMilestoneCommitAttribution({
|
|
1079
|
+
commitSha: 'fedcba9876543210fedcba9876543210fedcba98',
|
|
1080
|
+
milestoneId: 'M001',
|
|
1081
|
+
source: 'backfill',
|
|
1082
|
+
confidence: 0.8,
|
|
1083
|
+
files: ['app.js'],
|
|
1084
|
+
createdAt: '2026-05-05T00:00:00.000Z',
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
assert.deepEqual(getMilestoneCommitAttributionShas('M001'), ['fedcba9876543210fedcba9876543210fedcba98']);
|
|
1088
|
+
clearEngineHierarchy();
|
|
1089
|
+
assert.deepEqual(getMilestoneCommitAttributionShas('M001'), []);
|
|
1090
|
+
closeDatabase();
|
|
1091
|
+
});
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1050
1094
|
// ─── getDbStatus ───────────────────────────────────────────────────────────
|
|
1051
1095
|
|
|
1052
1096
|
describe('getDbStatus', () => {
|
|
@@ -15,7 +15,7 @@ import { tmpdir } from "node:os";
|
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import { randomUUID } from "node:crypto";
|
|
17
17
|
|
|
18
|
-
import { hasPendingDeepStage } from "../auto-dispatch.ts";
|
|
18
|
+
import { hasPendingDeepStage, shouldRunDeepProjectSetup } from "../auto-dispatch.ts";
|
|
19
19
|
import type { GSDPreferences } from "../preferences.ts";
|
|
20
20
|
import { loadEffectiveGSDPreferences } from "../preferences.ts";
|
|
21
21
|
|
|
@@ -57,6 +57,38 @@ test("hasPendingDeepStage: returns true in deep mode when nothing has been captu
|
|
|
57
57
|
}
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
test("shouldRunDeepProjectSetup: complete state wins over pending deep setup", async () => {
|
|
61
|
+
const base = makeBase();
|
|
62
|
+
try {
|
|
63
|
+
assert.equal(hasPendingDeepStage(deepPrefs, base), true);
|
|
64
|
+
assert.equal(
|
|
65
|
+
shouldRunDeepProjectSetup({ phase: "complete" }, deepPrefs, base),
|
|
66
|
+
false,
|
|
67
|
+
"completed projects must not restart deep setup and loop through auto-mode",
|
|
68
|
+
);
|
|
69
|
+
} finally {
|
|
70
|
+
rmSync(base, { recursive: true, force: true });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("shouldRunDeepProjectSetup: only setup phases can trigger pending deep setup", () => {
|
|
75
|
+
const base = makeBase();
|
|
76
|
+
try {
|
|
77
|
+
assert.equal(hasPendingDeepStage(deepPrefs, base), true);
|
|
78
|
+
assert.equal(shouldRunDeepProjectSetup({ phase: "pre-planning" }, deepPrefs, base), true);
|
|
79
|
+
assert.equal(shouldRunDeepProjectSetup({ phase: "needs-discussion" }, deepPrefs, base), true);
|
|
80
|
+
assert.equal(shouldRunDeepProjectSetup({ phase: "planning" }, deepPrefs, base), true);
|
|
81
|
+
assert.equal(shouldRunDeepProjectSetup({ phase: "executing" }, deepPrefs, base), false);
|
|
82
|
+
assert.equal(shouldRunDeepProjectSetup({ phase: "blocked" }, deepPrefs, base), false);
|
|
83
|
+
assert.equal(
|
|
84
|
+
shouldRunDeepProjectSetup({ phase: "pre-planning" }, deepPrefs, base, { hasSurvivorBranch: true }),
|
|
85
|
+
false,
|
|
86
|
+
);
|
|
87
|
+
} finally {
|
|
88
|
+
rmSync(base, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
60
92
|
test("hasPendingDeepStage: returns true in deep mode when only some gates pass", () => {
|
|
61
93
|
// workflow-preferences captured but PROJECT.md still missing.
|
|
62
94
|
const base = makeBase();
|
|
@@ -13,6 +13,9 @@ import { parseRoadmap, parsePlan } from '../parsers-legacy.ts';
|
|
|
13
13
|
import { parseSummary } from '../files.ts';
|
|
14
14
|
import { deriveState } from '../state.ts';
|
|
15
15
|
import { invalidateAllCaches } from '../cache.ts';
|
|
16
|
+
import { ensureDbOpen } from '../bootstrap/dynamic-tools.ts';
|
|
17
|
+
import { closeDatabase, getAllMilestones } from '../gsd-db.ts';
|
|
18
|
+
import { importWrittenMigrationToDb } from '../migrate/command.ts';
|
|
16
19
|
import type {
|
|
17
20
|
GSDProject,
|
|
18
21
|
GSDMilestone,
|
|
@@ -250,6 +253,51 @@ test('Scenario 1: Incomplete project — write, parse, deriveState', async () =>
|
|
|
250
253
|
}
|
|
251
254
|
});
|
|
252
255
|
|
|
256
|
+
test('Scenario 1b: written migration imports into authoritative DB state', async () => {
|
|
257
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-writer-db-import-'));
|
|
258
|
+
try {
|
|
259
|
+
const project = buildIncompleteProject();
|
|
260
|
+
await writeGSDDirectory(project, base);
|
|
261
|
+
|
|
262
|
+
assert.equal(await ensureDbOpen(base), true, 'db import: ensureDbOpen creates authoritative DB');
|
|
263
|
+
|
|
264
|
+
invalidateAllCaches();
|
|
265
|
+
const before = await deriveState(base);
|
|
266
|
+
assert.equal(before.activeMilestone, null, 'db import: markdown-only migration is invisible before DB import');
|
|
267
|
+
|
|
268
|
+
const imported = await importWrittenMigrationToDb(base);
|
|
269
|
+
assert.deepStrictEqual(imported.hierarchy, { milestones: 1, slices: 2, tasks: 3 }, 'db import: hierarchy counts');
|
|
270
|
+
|
|
271
|
+
invalidateAllCaches();
|
|
272
|
+
const after = await deriveState(base);
|
|
273
|
+
assert.deepStrictEqual(after.phase, 'executing', 'db import: deriveState sees imported DB hierarchy');
|
|
274
|
+
assert.deepStrictEqual(after.activeMilestone?.id, 'M001', 'db import: active milestone');
|
|
275
|
+
assert.deepStrictEqual(after.activeSlice?.id, 'S02', 'db import: active slice');
|
|
276
|
+
assert.deepStrictEqual(after.activeTask?.id, 'T03', 'db import: active task');
|
|
277
|
+
} finally {
|
|
278
|
+
closeDatabase();
|
|
279
|
+
rmSync(base, { recursive: true, force: true });
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('Scenario 1c: DB import verification fails when preview counts do not match', async () => {
|
|
284
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-writer-db-check-'));
|
|
285
|
+
try {
|
|
286
|
+
const project = buildIncompleteProject();
|
|
287
|
+
await writeGSDDirectory(project, base);
|
|
288
|
+
|
|
289
|
+
const preview = generatePreview(project);
|
|
290
|
+
await assert.rejects(
|
|
291
|
+
() => importWrittenMigrationToDb(base, { ...preview, totalTasks: preview.totalTasks + 1 }),
|
|
292
|
+
/migration DB import verification failed: tasks 3\/4/,
|
|
293
|
+
);
|
|
294
|
+
assert.deepStrictEqual(getAllMilestones(), [], 'db import: failed verification rolls back hierarchy rewrite');
|
|
295
|
+
} finally {
|
|
296
|
+
closeDatabase();
|
|
297
|
+
rmSync(base, { recursive: true, force: true });
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
253
301
|
// ─── Scenario 2: Fully complete project ────────────────────────────────
|
|
254
302
|
|
|
255
303
|
test('Scenario 2: Fully complete project — deriveState phase', async () => {
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import { ensureDbOpen } from "../bootstrap/dynamic-tools.ts";
|
|
8
|
+
import {
|
|
9
|
+
_getAdapter,
|
|
10
|
+
closeDatabase,
|
|
11
|
+
getAllMilestones,
|
|
12
|
+
getSliceTasks,
|
|
13
|
+
} from "../gsd-db.ts";
|
|
14
|
+
import {
|
|
15
|
+
autoImportMarkdownHierarchyIfDbMismatch,
|
|
16
|
+
countMarkdownHierarchy,
|
|
17
|
+
} from "../migration-auto-check.ts";
|
|
18
|
+
import { writeGSDDirectory } from "../migrate/writer.ts";
|
|
19
|
+
import type { GSDProject } from "../migrate/types.ts";
|
|
20
|
+
|
|
21
|
+
function makeBase(): string {
|
|
22
|
+
return mkdtempSync(join(tmpdir(), "gsd-migration-auto-check-"));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function cleanup(base: string): void {
|
|
26
|
+
closeDatabase();
|
|
27
|
+
rmSync(base, { recursive: true, force: true });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function projectFixture(): GSDProject {
|
|
31
|
+
return {
|
|
32
|
+
projectContent: "# Legacy Project\n",
|
|
33
|
+
decisionsContent: "",
|
|
34
|
+
requirements: [],
|
|
35
|
+
milestones: [
|
|
36
|
+
{
|
|
37
|
+
id: "M001",
|
|
38
|
+
title: "Legacy Milestone",
|
|
39
|
+
vision: "Carry forward previous work",
|
|
40
|
+
successCriteria: ["Existing task is visible"],
|
|
41
|
+
research: null,
|
|
42
|
+
boundaryMap: [],
|
|
43
|
+
slices: [
|
|
44
|
+
{
|
|
45
|
+
id: "S01",
|
|
46
|
+
title: "Legacy Slice",
|
|
47
|
+
risk: "medium",
|
|
48
|
+
depends: [],
|
|
49
|
+
done: false,
|
|
50
|
+
demo: "Legacy slice demo",
|
|
51
|
+
goal: "Legacy slice demo",
|
|
52
|
+
research: null,
|
|
53
|
+
summary: null,
|
|
54
|
+
tasks: [
|
|
55
|
+
{
|
|
56
|
+
id: "T01",
|
|
57
|
+
title: "Legacy Task",
|
|
58
|
+
description: "Task carried from markdown",
|
|
59
|
+
done: false,
|
|
60
|
+
estimate: "",
|
|
61
|
+
files: ["src/index.ts"],
|
|
62
|
+
mustHaves: [],
|
|
63
|
+
summary: null,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
test("migration auto-check imports markdown hierarchy when DB is empty", async () => {
|
|
74
|
+
const base = makeBase();
|
|
75
|
+
try {
|
|
76
|
+
await writeGSDDirectory(projectFixture(), base);
|
|
77
|
+
assert.deepEqual(countMarkdownHierarchy(base), { milestones: 1, slices: 1, tasks: 1 });
|
|
78
|
+
|
|
79
|
+
assert.equal(await ensureDbOpen(base), true);
|
|
80
|
+
assert.equal(getAllMilestones().length, 0, "fresh authoritative DB starts empty");
|
|
81
|
+
|
|
82
|
+
const result = await autoImportMarkdownHierarchyIfDbMismatch(base);
|
|
83
|
+
assert.equal(result.action, "imported");
|
|
84
|
+
assert.equal(result.reason, "db-empty");
|
|
85
|
+
assert.deepEqual(result.afterDb, { milestones: 1, slices: 1, tasks: 1 });
|
|
86
|
+
assert.equal(getAllMilestones().length, 1);
|
|
87
|
+
assert.equal(getSliceTasks("M001", "S01").length, 1);
|
|
88
|
+
} finally {
|
|
89
|
+
cleanup(base);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("migration auto-check repairs DB hierarchy count mismatch", async () => {
|
|
94
|
+
const base = makeBase();
|
|
95
|
+
try {
|
|
96
|
+
await writeGSDDirectory(projectFixture(), base);
|
|
97
|
+
await autoImportMarkdownHierarchyIfDbMismatch(base);
|
|
98
|
+
|
|
99
|
+
_getAdapter()!.prepare("DELETE FROM tasks WHERE milestone_id = ? AND slice_id = ? AND id = ?").run("M001", "S01", "T01");
|
|
100
|
+
assert.equal(getSliceTasks("M001", "S01").length, 0, "test fixture simulates stale DB task count");
|
|
101
|
+
|
|
102
|
+
const result = await autoImportMarkdownHierarchyIfDbMismatch(base);
|
|
103
|
+
assert.equal(result.action, "imported");
|
|
104
|
+
assert.equal(result.reason, "count-mismatch");
|
|
105
|
+
assert.deepEqual(result.beforeDb, { milestones: 1, slices: 1, tasks: 0 });
|
|
106
|
+
assert.deepEqual(result.afterDb, { milestones: 1, slices: 1, tasks: 1 });
|
|
107
|
+
assert.equal(getSliceTasks("M001", "S01").length, 1);
|
|
108
|
+
} finally {
|
|
109
|
+
cleanup(base);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("migration auto-check leaves matching DB hierarchy alone", async () => {
|
|
114
|
+
const base = makeBase();
|
|
115
|
+
try {
|
|
116
|
+
await writeGSDDirectory(projectFixture(), base);
|
|
117
|
+
await autoImportMarkdownHierarchyIfDbMismatch(base);
|
|
118
|
+
|
|
119
|
+
const result = await autoImportMarkdownHierarchyIfDbMismatch(base);
|
|
120
|
+
assert.equal(result.action, "none");
|
|
121
|
+
assert.equal(result.reason, "in-sync");
|
|
122
|
+
assert.deepEqual(result.markdown, { milestones: 1, slices: 1, tasks: 1 });
|
|
123
|
+
assert.deepEqual(result.afterDb, { milestones: 1, slices: 1, tasks: 1 });
|
|
124
|
+
} finally {
|
|
125
|
+
cleanup(base);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
@@ -172,6 +172,56 @@ test('handlePlanSlice rejects invalid payloads', async () => {
|
|
|
172
172
|
}
|
|
173
173
|
});
|
|
174
174
|
|
|
175
|
+
test('handlePlanSlice rejects absolute task IO paths outside the active worktree', async () => {
|
|
176
|
+
const base = makeTmpBase();
|
|
177
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
seedParentSlice();
|
|
181
|
+
const outside = join(tmpdir(), 'outside-checkout', 'index.html');
|
|
182
|
+
const result = await handlePlanSlice({
|
|
183
|
+
...validParams(),
|
|
184
|
+
tasks: [
|
|
185
|
+
{
|
|
186
|
+
...validParams().tasks[0],
|
|
187
|
+
inputs: [outside],
|
|
188
|
+
expectedOutput: [outside],
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
}, base);
|
|
192
|
+
|
|
193
|
+
assert.ok('error' in result);
|
|
194
|
+
assert.match(result.error, /validation failed: tasks\[0\]\.inputs contains absolute path outside working directory/);
|
|
195
|
+
assert.equal(getSliceTasks('M001', 'S02').length, 0, 'invalid planning IO must not persist tasks');
|
|
196
|
+
} finally {
|
|
197
|
+
cleanup(base);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('handlePlanSlice accepts absolute task IO paths inside the active worktree', async () => {
|
|
202
|
+
const base = makeTmpBase();
|
|
203
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
seedParentSlice();
|
|
207
|
+
const inside = join(base, 'index.html');
|
|
208
|
+
const result = await handlePlanSlice({
|
|
209
|
+
...validParams(),
|
|
210
|
+
tasks: [
|
|
211
|
+
{
|
|
212
|
+
...validParams().tasks[0],
|
|
213
|
+
inputs: [inside],
|
|
214
|
+
expectedOutput: [inside],
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
}, base);
|
|
218
|
+
|
|
219
|
+
assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
|
|
220
|
+
} finally {
|
|
221
|
+
cleanup(base);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
175
225
|
test('handlePlanSlice rejects missing parent slice', async () => {
|
|
176
226
|
const base = makeTmpBase();
|
|
177
227
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
@@ -79,6 +79,27 @@ test('handlePlanTask rejects invalid payloads', async () => {
|
|
|
79
79
|
}
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
+
test('handlePlanTask rejects absolute task IO paths outside the active worktree', async () => {
|
|
83
|
+
const base = makeTmpBase();
|
|
84
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
seedParent();
|
|
88
|
+
const outside = join(tmpdir(), 'outside-checkout', 'index.html');
|
|
89
|
+
const result = await handlePlanTask({
|
|
90
|
+
...validParams(),
|
|
91
|
+
inputs: [outside],
|
|
92
|
+
expectedOutput: [outside],
|
|
93
|
+
}, base);
|
|
94
|
+
|
|
95
|
+
assert.ok('error' in result);
|
|
96
|
+
assert.match(result.error, /validation failed: inputs contains absolute path outside working directory/);
|
|
97
|
+
assert.equal(getTask('M001', 'S02', 'T02'), null, 'invalid planning IO must not persist the task');
|
|
98
|
+
} finally {
|
|
99
|
+
cleanup(base);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
82
103
|
test('handlePlanTask rejects missing parent slice', async () => {
|
|
83
104
|
const base = makeTmpBase();
|
|
84
105
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { dirname } from "node:path";
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
const promptsDir = join(__dirname, "..", "prompts");
|
|
11
|
+
|
|
12
|
+
test("prompt templates do not reference legacy milestone-root .gsd paths", () => {
|
|
13
|
+
const offenders: string[] = [];
|
|
14
|
+
for (const file of readdirSync(promptsDir)) {
|
|
15
|
+
if (!file.endsWith(".md")) continue;
|
|
16
|
+
const content = readFileSync(join(promptsDir, file), "utf-8");
|
|
17
|
+
const legacyPatterns = [
|
|
18
|
+
/\.gsd\/\{\{(?:milestoneId|mid)\}\}\//g,
|
|
19
|
+
/\.gsd\/<milestone-id>\//g,
|
|
20
|
+
/\.gsd\/<ID>\//g,
|
|
21
|
+
];
|
|
22
|
+
for (const pattern of legacyPatterns) {
|
|
23
|
+
if (pattern.test(content)) {
|
|
24
|
+
offenders.push(`${file}: ${pattern.source}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
assert.deepEqual(
|
|
30
|
+
offenders,
|
|
31
|
+
[],
|
|
32
|
+
"Milestone artifacts must use .gsd/milestones/<MID>/..., not legacy .gsd/<MID>/...",
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("quick task prompt delegates commit policy to quick.ts", () => {
|
|
37
|
+
const content = readFileSync(join(promptsDir, "quick-task.md"), "utf-8");
|
|
38
|
+
assert.match(content, /\{\{commitInstruction\}\}/);
|
|
39
|
+
assert.doesNotMatch(content, /Stage only relevant files/);
|
|
40
|
+
});
|
|
@@ -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
|
+
});
|