gsd-pi 2.80.0-dev.2be7caf18 → 2.80.0-dev.3065f7bfa
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/phases.js +178 -108
- package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +20 -30
- package/dist/resources/extensions/gsd/auto/session.js +8 -0
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +5 -32
- package/dist/resources/extensions/gsd/auto-dispatch.js +16 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +17 -4
- package/dist/resources/extensions/gsd/auto-prompts.js +90 -15
- package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
- package/dist/resources/extensions/gsd/auto-start.js +197 -6
- 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-worktree.js +111 -1
- package/dist/resources/extensions/gsd/auto.js +79 -24
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +42 -2
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +49 -36
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +15 -5
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +9 -3
- package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +7 -1
- package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +9 -3
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +8 -2
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +238 -28
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +82 -23
- package/dist/resources/extensions/gsd/clean-root-preflight.js +24 -6
- package/dist/resources/extensions/gsd/commands-handlers.js +23 -9
- 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/ecosystem/gsd-extension-api.js +2 -0
- package/dist/resources/extensions/gsd/git-service.js +36 -4
- package/dist/resources/extensions/gsd/gsd-db.js +46 -13
- package/dist/resources/extensions/gsd/guided-flow.js +66 -32
- package/dist/resources/extensions/gsd/memory-store.js +69 -12
- package/dist/resources/extensions/gsd/native-git-bridge.js +32 -8
- package/dist/resources/extensions/gsd/orphan-stash-audit.js +101 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +13 -3
- package/dist/resources/extensions/gsd/pre-execution-checks.js +22 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +4 -2
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
- package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
- package/dist/resources/extensions/gsd/workflow-protocol.js +131 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +68 -21
- 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 +15 -15
- 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 +15 -15
- 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/src/workflow-tools.test.ts +9 -2
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +4 -1
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +5 -0
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +2 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/dist/index.d.ts +1 -0
- package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/index.js +2 -0
- package/packages/pi-agent-core/dist/index.js.map +1 -1
- package/packages/pi-agent-core/dist/token-audit.d.ts +47 -0
- package/packages/pi-agent-core/dist/token-audit.d.ts.map +1 -0
- package/packages/pi-agent-core/dist/token-audit.js +221 -0
- package/packages/pi-agent-core/dist/token-audit.js.map +1 -0
- package/packages/pi-agent-core/dist/types.d.ts +9 -0
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +128 -0
- package/packages/pi-agent-core/src/agent-loop.ts +4 -1
- package/packages/pi-agent-core/src/agent.ts +8 -0
- package/packages/pi-agent-core/src/index.ts +2 -0
- package/packages/pi-agent-core/src/token-audit.test.ts +189 -0
- package/packages/pi-agent-core/src/token-audit.ts +287 -0
- package/packages/pi-agent-core/src/types.ts +14 -0
- package/packages/pi-agent-core/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-tool-refresh.test.js +18 -0
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +41 -7
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +8 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -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 -6
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +3 -3
- 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 +32 -1
- 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/hooks-runner.test.js +2 -0
- package/packages/pi-coding-agent/dist/core/hooks-runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js +46 -0
- package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +10 -2
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +74 -2
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js +22 -0
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +6 -7
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -3
- package/packages/pi-coding-agent/dist/core/system-prompt.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-tool-refresh.test.ts +25 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +45 -7
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +10 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +3 -3
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +5 -5
- package/packages/pi-coding-agent/src/core/extensions/types.ts +35 -1
- package/packages/pi-coding-agent/src/core/hooks-runner.test.ts +2 -0
- package/packages/pi-coding-agent/src/core/sdk-tool-filter.test.ts +60 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +85 -3
- package/packages/pi-coding-agent/src/core/skill-tool.test.ts +28 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +8 -10
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -2
- package/src/resources/extensions/gsd/auto/phases.ts +251 -172
- package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +25 -30
- package/src/resources/extensions/gsd/auto/session.ts +8 -0
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +8 -34
- package/src/resources/extensions/gsd/auto-dispatch.ts +16 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +18 -4
- package/src/resources/extensions/gsd/auto-prompts.ts +95 -14
- package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
- package/src/resources/extensions/gsd/auto-start.ts +230 -9
- 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-worktree.ts +123 -0
- package/src/resources/extensions/gsd/auto.ts +87 -19
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +49 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +50 -36
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +16 -5
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +10 -3
- package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +8 -1
- package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +10 -3
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +9 -2
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +283 -28
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +90 -22
- package/src/resources/extensions/gsd/clean-root-preflight.ts +32 -7
- package/src/resources/extensions/gsd/commands-handlers.ts +34 -15
- 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/ecosystem/gsd-extension-api.ts +3 -0
- package/src/resources/extensions/gsd/git-service.ts +46 -8
- package/src/resources/extensions/gsd/gsd-db.ts +50 -13
- package/src/resources/extensions/gsd/guided-flow.ts +84 -39
- package/src/resources/extensions/gsd/memory-store.ts +77 -12
- package/src/resources/extensions/gsd/native-git-bridge.ts +39 -6
- package/src/resources/extensions/gsd/orphan-stash-audit.ts +117 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +13 -3
- package/src/resources/extensions/gsd/pre-execution-checks.ts +23 -0
- package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +4 -2
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +417 -10
- 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/auto-wrapup-inflight-guard.test.ts +168 -6
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +15 -6
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/context-store.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
- package/src/resources/extensions/gsd/tests/execute-task-rendering.test.ts +5 -2
- package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +219 -0
- package/src/resources/extensions/gsd/tests/finalize-survivor-branch.test.ts +132 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +239 -1
- package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +34 -2
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/orphan-merge-bootstrap.test.ts +133 -0
- package/src/resources/extensions/gsd/tests/orphan-stash-audit.test.ts +201 -0
- package/src/resources/extensions/gsd/tests/parallel-orchestrator-fast-forward.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +45 -5
- package/src/resources/extensions/gsd/tests/prompt-duplication-cuts.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +38 -17
- package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/select-resumable-milestone.test.ts +96 -0
- package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +106 -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 +38 -0
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/system-context-memory.test.ts +112 -0
- package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +7 -9
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +291 -0
- 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/unstructured-continue-context-injection.test.ts +5 -4
- package/src/resources/extensions/gsd/tests/workflow-protocol-excerpt.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +22 -19
- package/src/resources/extensions/gsd/tests/worktree-project-root-degrade.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +167 -4
- package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
- package/src/resources/extensions/gsd/unit-runtime.ts +11 -0
- package/src/resources/extensions/gsd/workflow-protocol.ts +160 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +85 -19
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +0 -97
- /package/dist/web/standalone/.next/static/{3EDDd9ULaybSupPoA_vf- → O2WvnXROue6l7nAp_4dhC}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{3EDDd9ULaybSupPoA_vf- → O2WvnXROue6l7nAp_4dhC}/_ssgManifest.js +0 -0
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// GSD-2 + src/resources/extensions/gsd/tests/select-resumable-milestone.test.ts
|
|
2
|
+
// Regression: bootstrap must rederive `currentMilestoneId` from an unmerged
|
|
3
|
+
// completed milestone branch when in-memory state was lost across a process
|
|
4
|
+
// restart (#5538-followup).
|
|
5
|
+
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
|
|
9
|
+
import { _selectResumableMilestone } from "../auto-start.js";
|
|
10
|
+
|
|
11
|
+
const ALL_COMPLETE = (_id: string) => true;
|
|
12
|
+
const NEVER_COMPLETE = (_id: string) => false;
|
|
13
|
+
const HAS_COMMITS = (_branch: string) => 1;
|
|
14
|
+
const NO_COMMITS = (_branch: string) => 0;
|
|
15
|
+
|
|
16
|
+
test("returns null when no branches exist", () => {
|
|
17
|
+
const result = _selectResumableMilestone([], new Set(), ALL_COMPLETE, HAS_COMMITS);
|
|
18
|
+
assert.equal(result, null);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("returns null when every milestone branch is already merged", () => {
|
|
22
|
+
const branches = ["milestone/M001", "milestone/M002"];
|
|
23
|
+
const merged = new Set(branches);
|
|
24
|
+
const result = _selectResumableMilestone(branches, merged, ALL_COMPLETE, HAS_COMMITS);
|
|
25
|
+
assert.equal(result, null);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("returns null when no candidate milestones are complete", () => {
|
|
29
|
+
const branches = ["milestone/M002"];
|
|
30
|
+
const result = _selectResumableMilestone(branches, new Set(), NEVER_COMPLETE, HAS_COMMITS);
|
|
31
|
+
assert.equal(result, null);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("returns null when no branch has commits ahead", () => {
|
|
35
|
+
const branches = ["milestone/M002"];
|
|
36
|
+
const result = _selectResumableMilestone(branches, new Set(), ALL_COMPLETE, NO_COMMITS);
|
|
37
|
+
assert.equal(result, null);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("returns the unmerged completed milestone (regression: M002 stranded after restart)", () => {
|
|
41
|
+
// Repro of test12345 state: M001 ✅ merged, M002 ✅ unmerged, M003 🔄.
|
|
42
|
+
// Bootstrap must seed currentMilestoneId = "M002" so the loop's transition
|
|
43
|
+
// guard fires when the next iteration sees mid="M003".
|
|
44
|
+
const branches = ["milestone/M002", "milestone/M003"];
|
|
45
|
+
const merged = new Set<string>(); // none merged from this list
|
|
46
|
+
const isComplete = (id: string) => id === "M002"; // M003 still in progress
|
|
47
|
+
const result = _selectResumableMilestone(branches, merged, isComplete, HAS_COMMITS);
|
|
48
|
+
assert.equal(result, "M002");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("picks the lex-greatest milestone when multiple candidates exist", () => {
|
|
52
|
+
// M001, M002 both unmerged complete -> pick M002 (the most recent).
|
|
53
|
+
const branches = ["milestone/M001", "milestone/M002"];
|
|
54
|
+
const result = _selectResumableMilestone(
|
|
55
|
+
branches,
|
|
56
|
+
new Set(),
|
|
57
|
+
ALL_COMPLETE,
|
|
58
|
+
HAS_COMMITS,
|
|
59
|
+
);
|
|
60
|
+
assert.equal(result, "M002");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("ignores branch names that don't follow the milestone/ prefix", () => {
|
|
64
|
+
const branches = ["feature/something", "milestone/M002", "fix/bug-1"];
|
|
65
|
+
const result = _selectResumableMilestone(
|
|
66
|
+
branches,
|
|
67
|
+
new Set(),
|
|
68
|
+
ALL_COMPLETE,
|
|
69
|
+
HAS_COMMITS,
|
|
70
|
+
);
|
|
71
|
+
assert.equal(result, "M002");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("isComplete callback throwing skips that milestone but does not crash", () => {
|
|
75
|
+
const branches = ["milestone/M001", "milestone/M002"];
|
|
76
|
+
const isComplete = (id: string) => {
|
|
77
|
+
if (id === "M001") throw new Error("db unavailable for M001");
|
|
78
|
+
return true;
|
|
79
|
+
};
|
|
80
|
+
// Implementation choice: a thrown isComplete is propagated. Wrapping in
|
|
81
|
+
// try/catch happens at the production wrapper level (findUnmergedCompletedMilestone).
|
|
82
|
+
// Verify the helper itself surfaces the error so callers see real failures.
|
|
83
|
+
assert.throws(() => _selectResumableMilestone(branches, new Set(), isComplete, HAS_COMMITS));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("commitsAhead callback throwing for one branch falls through to others", () => {
|
|
87
|
+
// Production wrapper: commits-ahead failures should not abort the search.
|
|
88
|
+
// The helper catches throws from commitsAhead per-branch and treats as 0.
|
|
89
|
+
const branches = ["milestone/M001", "milestone/M002"];
|
|
90
|
+
const commitsAhead = (branch: string) => {
|
|
91
|
+
if (branch === "milestone/M001") throw new Error("rev-walk failed");
|
|
92
|
+
return 5;
|
|
93
|
+
};
|
|
94
|
+
const result = _selectResumableMilestone(branches, new Set(), ALL_COMPLETE, commitsAhead);
|
|
95
|
+
assert.equal(result, "M002");
|
|
96
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// GSD-2 + src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts
|
|
2
|
+
// Regression: session-transition aborts must not be classified as user cancellations.
|
|
3
|
+
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
|
|
7
|
+
import { _handleSessionSwitchAgentEnd } from "../bootstrap/agent-end-recovery.js";
|
|
8
|
+
import type { ErrorContext } from "../auto/types.js";
|
|
9
|
+
|
|
10
|
+
test("user-abort message during session-switch is dropped (not propagated as cancellation)", () => {
|
|
11
|
+
// The Anthropic SDK emits this exact string when newSession() aborts an
|
|
12
|
+
// in-flight stream during a unit-to-unit session transition. Before the fix
|
|
13
|
+
// this was misclassified as a user cancellation and killed auto-mode with
|
|
14
|
+
// "Auto-mode stopped — Unit aborted: Claude Code process aborted by user".
|
|
15
|
+
let cancelledWith: ErrorContext | null = null;
|
|
16
|
+
const resolveCancelled = (ctx: ErrorContext) => {
|
|
17
|
+
cancelledWith = ctx;
|
|
18
|
+
return true;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
_handleSessionSwitchAgentEnd(
|
|
22
|
+
{ stopReason: "error", errorMessage: "Claude Code process aborted by user" },
|
|
23
|
+
resolveCancelled,
|
|
24
|
+
);
|
|
25
|
+
assert.equal(cancelledWith, null, "SDK user-abort during session-switch must not propagate cancellation");
|
|
26
|
+
|
|
27
|
+
_handleSessionSwitchAgentEnd(
|
|
28
|
+
{ stopReason: "error", errorMessage: "Request aborted by user" },
|
|
29
|
+
resolveCancelled,
|
|
30
|
+
);
|
|
31
|
+
assert.equal(cancelledWith, null, "proxy user-abort during session-switch must not propagate cancellation");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("genuine stopReason='aborted' with errorMessage during session-switch still propagates", () => {
|
|
35
|
+
// Regression guard for prior behavior: genuine aborts with diagnostic content
|
|
36
|
+
// continue to surface as cancellations so transient-pause recovery can run.
|
|
37
|
+
let cancelledWith: { message: string; category: string; isTransient?: boolean } | null = null;
|
|
38
|
+
const resolveCancelled = (ctx: ErrorContext) => {
|
|
39
|
+
cancelledWith = ctx;
|
|
40
|
+
return true;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
_handleSessionSwitchAgentEnd(
|
|
44
|
+
{
|
|
45
|
+
stopReason: "aborted",
|
|
46
|
+
errorMessage: "stream torn down mid-flight",
|
|
47
|
+
content: [{ type: "text", text: "partial output" }],
|
|
48
|
+
},
|
|
49
|
+
resolveCancelled,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
assert.deepEqual(cancelledWith, {
|
|
53
|
+
message: "stream torn down mid-flight",
|
|
54
|
+
category: "aborted",
|
|
55
|
+
isTransient: true,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("empty-content aborted during session-switch is silently ignored", () => {
|
|
60
|
+
// Empty-content aborted is a non-fatal LLM stop; we must not pause/cancel.
|
|
61
|
+
let cancelledWith: unknown = null;
|
|
62
|
+
const resolveCancelled = (ctx: ErrorContext) => {
|
|
63
|
+
cancelledWith = ctx;
|
|
64
|
+
return true;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
_handleSessionSwitchAgentEnd(
|
|
68
|
+
{ stopReason: "aborted", content: [] },
|
|
69
|
+
resolveCancelled,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
assert.equal(cancelledWith, null);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("non-abort errors during session-switch are not propagated through this helper", () => {
|
|
76
|
+
// Real provider errors (rate-limit, network, unsupported-model) are handled
|
|
77
|
+
// by the post-switch retry pipeline — not by the in-flight switch handler.
|
|
78
|
+
let cancelledWith: unknown = null;
|
|
79
|
+
const resolveCancelled = (ctx: ErrorContext) => {
|
|
80
|
+
cancelledWith = ctx;
|
|
81
|
+
return true;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
_handleSessionSwitchAgentEnd(
|
|
85
|
+
{ stopReason: "error", errorMessage: "rate limit exceeded" },
|
|
86
|
+
resolveCancelled,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
assert.equal(cancelledWith, null);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("malformed lastMsg is rejected gracefully", () => {
|
|
93
|
+
let calls = 0;
|
|
94
|
+
const resolveCancelled = (_ctx: unknown) => {
|
|
95
|
+
calls += 1;
|
|
96
|
+
return true;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
_handleSessionSwitchAgentEnd(undefined, resolveCancelled as never);
|
|
100
|
+
_handleSessionSwitchAgentEnd(null, resolveCancelled as never);
|
|
101
|
+
_handleSessionSwitchAgentEnd("not an object", resolveCancelled as never);
|
|
102
|
+
_handleSessionSwitchAgentEnd({}, resolveCancelled as never);
|
|
103
|
+
_handleSessionSwitchAgentEnd({ stopReason: "completed" }, resolveCancelled as never);
|
|
104
|
+
|
|
105
|
+
assert.equal(calls, 0, "malformed or non-abort lastMsg must not invoke cancellation");
|
|
106
|
+
});
|
|
@@ -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
|
|
|
@@ -202,3 +203,40 @@ test("detached auto-start preserves milestone lock across pause/stop cleanup (#3
|
|
|
202
203
|
"AutoSession should track the detached milestone lock explicitly",
|
|
203
204
|
);
|
|
204
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
|
+
});
|
|
@@ -291,6 +291,7 @@ describe("#2945 Bug 3: mergeAndExit must teardown worktree after successful merg
|
|
|
291
291
|
getAutoWorktreePath: () => null,
|
|
292
292
|
autoCommitCurrentBranch: () => {},
|
|
293
293
|
getCurrentBranch: () => "main",
|
|
294
|
+
checkoutBranch: () => {},
|
|
294
295
|
autoWorktreeBranch: () => "gsd/M001",
|
|
295
296
|
resolveMilestoneFile: () => "/mock/roadmap.md",
|
|
296
297
|
readFileSync: () => "# Roadmap content",
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: System context memory gating regression tests.
|
|
3
|
+
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
|
|
7
|
+
import { buildContextMessage, isLowEntropyResumePrompt, loadMemoryBlock } from "../bootstrap/system-context.ts";
|
|
8
|
+
import { closeDatabase, openDatabase } from "../gsd-db.ts";
|
|
9
|
+
import { createMemory } from "../memory-store.ts";
|
|
10
|
+
|
|
11
|
+
test("buildContextMessage marks hidden guided context when memory is supplied", () => {
|
|
12
|
+
const message = buildContextMessage({
|
|
13
|
+
memoryBlock: "\n\n[MEMORY]\n\n- keep this",
|
|
14
|
+
injection: "[GSD Guided Execute Context]\nUse the task plan.",
|
|
15
|
+
forensicsInjection: null,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
assert.ok(message, "expected hidden context message");
|
|
19
|
+
assert.equal(message.customType, "gsd-guided-context");
|
|
20
|
+
assert.equal(message.display, false);
|
|
21
|
+
assert.match(message.content, /\[GSD Context Metadata\]\n- Memory supplied: yes/);
|
|
22
|
+
assert.ok(
|
|
23
|
+
message.content.indexOf("Memory supplied: yes") < message.content.indexOf("[GSD Guided Execute Context]"),
|
|
24
|
+
"memory marker should appear before guided context",
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("buildContextMessage caps hidden context by default", () => {
|
|
29
|
+
const original = process.env.PI_GSD_CONTEXT_MAX_CHARS;
|
|
30
|
+
delete process.env.PI_GSD_CONTEXT_MAX_CHARS;
|
|
31
|
+
try {
|
|
32
|
+
const message = buildContextMessage({
|
|
33
|
+
memoryBlock: "",
|
|
34
|
+
injection: `[GSD Guided Execute Context]\n${"large context\n".repeat(500)}`,
|
|
35
|
+
forensicsInjection: null,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
assert.ok(message, "expected hidden context message");
|
|
39
|
+
assert.equal(message.customType, "gsd-guided-context");
|
|
40
|
+
assert.ok(message.content.length <= 4000);
|
|
41
|
+
assert.match(message.content, /\[GSD Context Truncated\]/);
|
|
42
|
+
} finally {
|
|
43
|
+
if (original === undefined) delete process.env.PI_GSD_CONTEXT_MAX_CHARS;
|
|
44
|
+
else process.env.PI_GSD_CONTEXT_MAX_CHARS = original;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("buildContextMessage supports explicit context cap override", () => {
|
|
49
|
+
const original = process.env.PI_GSD_CONTEXT_MAX_CHARS;
|
|
50
|
+
process.env.PI_GSD_CONTEXT_MAX_CHARS = "1200";
|
|
51
|
+
try {
|
|
52
|
+
const message = buildContextMessage({
|
|
53
|
+
memoryBlock: "",
|
|
54
|
+
injection: `[GSD Guided Execute Context]\n${"large context\n".repeat(200)}`,
|
|
55
|
+
forensicsInjection: null,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
assert.ok(message, "expected hidden context message");
|
|
59
|
+
assert.equal(message.customType, "gsd-guided-context");
|
|
60
|
+
assert.ok(message.content.length <= 1200);
|
|
61
|
+
assert.match(message.content, /\[GSD Context Truncated\]/);
|
|
62
|
+
} finally {
|
|
63
|
+
if (original === undefined) delete process.env.PI_GSD_CONTEXT_MAX_CHARS;
|
|
64
|
+
else process.env.PI_GSD_CONTEXT_MAX_CHARS = original;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("buildContextMessage does not add memory marker when only guided context is supplied", () => {
|
|
69
|
+
const message = buildContextMessage({
|
|
70
|
+
memoryBlock: "",
|
|
71
|
+
injection: "[GSD Guided Execute Context]\nUse the task plan.",
|
|
72
|
+
forensicsInjection: null,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
assert.ok(message, "expected guided context message");
|
|
76
|
+
assert.equal(message.customType, "gsd-guided-context");
|
|
77
|
+
assert.doesNotMatch(message.content, /Memory supplied: yes/);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("loadMemoryBlock keeps critical memories while gating prompt-relevant query hits", async () => {
|
|
81
|
+
closeDatabase();
|
|
82
|
+
assert.equal(openDatabase(":memory:"), true);
|
|
83
|
+
try {
|
|
84
|
+
createMemory({
|
|
85
|
+
category: "gotcha",
|
|
86
|
+
content: "Always preserve critical resume safety context.",
|
|
87
|
+
confidence: 0.95,
|
|
88
|
+
});
|
|
89
|
+
createMemory({
|
|
90
|
+
category: "preference",
|
|
91
|
+
content: "React dashboard preference should only appear for a React prompt query.",
|
|
92
|
+
confidence: 0.95,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const withPromptRelevant = await loadMemoryBlock("React dashboard", { includePromptRelevant: true });
|
|
96
|
+
assert.match(withPromptRelevant, /critical resume safety context/);
|
|
97
|
+
assert.match(withPromptRelevant, /React dashboard preference/);
|
|
98
|
+
|
|
99
|
+
const withoutPromptRelevant = await loadMemoryBlock("React dashboard", { includePromptRelevant: false });
|
|
100
|
+
assert.match(withoutPromptRelevant, /critical resume safety context/);
|
|
101
|
+
assert.doesNotMatch(withoutPromptRelevant, /React dashboard preference/);
|
|
102
|
+
} finally {
|
|
103
|
+
closeDatabase();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("isLowEntropyResumePrompt identifies bare resume prompts only", () => {
|
|
108
|
+
assert.equal(isLowEntropyResumePrompt("continue"), true);
|
|
109
|
+
assert.equal(isLowEntropyResumePrompt("Go ahead."), true);
|
|
110
|
+
assert.equal(isLowEntropyResumePrompt("run the tests"), false);
|
|
111
|
+
assert.equal(isLowEntropyResumePrompt("/gsd auto"), false);
|
|
112
|
+
});
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
// GSD
|
|
2
|
-
//
|
|
3
|
-
// per call; embedding it in the cached system prefix invalidates Anthropic
|
|
4
|
-
// prompt-cache hits on every request. The fix routes memory through the
|
|
5
|
-
// existing context-message channel (volatile user-message suffix) and combines
|
|
6
|
-
// it with any active guided-execute or forensics injection.
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Regression coverage for volatile system-context message routing.
|
|
7
3
|
|
|
8
4
|
import { describe, test } from "node:test";
|
|
9
5
|
import assert from "node:assert/strict";
|
|
@@ -11,6 +7,8 @@ import assert from "node:assert/strict";
|
|
|
11
7
|
import { buildContextMessage } from "../bootstrap/system-context.ts";
|
|
12
8
|
|
|
13
9
|
describe("buildContextMessage (#5019 — memory routing)", () => {
|
|
10
|
+
const markedMemory = "[GSD Context Metadata]\n- Memory supplied: yes\n\n[MEMORY]\nrule one";
|
|
11
|
+
|
|
14
12
|
test("returns null when nothing to inject", () => {
|
|
15
13
|
const result = buildContextMessage({
|
|
16
14
|
memoryBlock: "",
|
|
@@ -37,7 +35,7 @@ describe("buildContextMessage (#5019 — memory routing)", () => {
|
|
|
37
35
|
});
|
|
38
36
|
assert.ok(result, "expected a context message");
|
|
39
37
|
assert.equal(result.customType, "gsd-memory");
|
|
40
|
-
assert.equal(result.content, "[MEMORY]\nrule one\nrule two");
|
|
38
|
+
assert.equal(result.content, "[GSD Context Metadata]\n- Memory supplied: yes\n\n[MEMORY]\nrule one\nrule two");
|
|
41
39
|
assert.equal(result.display, false);
|
|
42
40
|
});
|
|
43
41
|
|
|
@@ -71,7 +69,7 @@ describe("buildContextMessage (#5019 — memory routing)", () => {
|
|
|
71
69
|
});
|
|
72
70
|
assert.ok(result);
|
|
73
71
|
assert.equal(result.customType, "gsd-guided-context");
|
|
74
|
-
assert.equal(result.content,
|
|
72
|
+
assert.equal(result.content, `${markedMemory}\n\n[GUIDED]\nexecute T01`);
|
|
75
73
|
});
|
|
76
74
|
|
|
77
75
|
test("memory + forensics: memory prepended, customType is gsd-forensics", () => {
|
|
@@ -82,7 +80,7 @@ describe("buildContextMessage (#5019 — memory routing)", () => {
|
|
|
82
80
|
});
|
|
83
81
|
assert.ok(result);
|
|
84
82
|
assert.equal(result.customType, "gsd-forensics");
|
|
85
|
-
assert.equal(result.content,
|
|
83
|
+
assert.equal(result.content, `${markedMemory}\n\n[FORENSICS]\ninvestigation context`);
|
|
86
84
|
});
|
|
87
85
|
|
|
88
86
|
test("guided takes precedence over forensics when both are somehow present", () => {
|