gsd-pi 2.80.0-dev.c5c38454b → 2.80.0-dev.cf9433f56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +2 -2
- package/dist/resources/extensions/gsd/auto/loop.js +32 -1
- package/dist/resources/extensions/gsd/auto/phases.js +66 -45
- package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +13 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +10 -10
- package/dist/resources/extensions/gsd/auto-prompts.js +124 -2
- package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
- package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
- package/dist/resources/extensions/gsd/auto.js +77 -5
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +21 -2
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +27 -20
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +21 -0
- package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
- package/dist/resources/extensions/gsd/commands/dispatcher.js +5 -0
- package/dist/resources/extensions/gsd/context-budget.js +37 -2
- package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
- 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/detection.js +106 -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 +80 -14
- package/dist/resources/extensions/gsd/memory-store.js +69 -12
- package/dist/resources/extensions/gsd/migrate/command.js +40 -1
- package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
- package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
- package/dist/resources/extensions/gsd/pre-execution-checks.js +7 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +19 -19
- 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/context-mode-tool-result.js +15 -0
- package/dist/resources/extensions/gsd/tools/exec-search-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +3 -15
- package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
- package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
- package/dist/resources/extensions/gsd/tools/resume-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
- package/dist/resources/extensions/gsd/unit-context-composer.js +12 -3
- package/dist/resources/extensions/gsd/unit-runtime.js +22 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
- package/dist/resources/extensions/gsd/worktree-resolver.js +33 -17
- 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 +7 -7
- 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 +7 -7
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +3 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +22 -17
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +75 -2
- package/packages/mcp-server/src/workflow-tools.ts +30 -16
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +8 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +32 -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 +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +15 -5
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/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-abort-order.test.ts +36 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +8 -0
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +38 -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 +2 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +3 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +4 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +24 -7
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -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 +18 -8
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/tui.ts +20 -8
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/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 +77 -48
- package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +18 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
- package/src/resources/extensions/gsd/auto-prompts.ts +133 -2
- package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
- package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
- package/src/resources/extensions/gsd/auto.ts +92 -4
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +21 -1
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +27 -19
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
- package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
- package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
- package/src/resources/extensions/gsd/context-budget.ts +44 -2
- package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
- 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/detection.ts +128 -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 +96 -14
- package/src/resources/extensions/gsd/memory-store.ts +77 -12
- package/src/resources/extensions/gsd/migrate/command.ts +47 -1
- package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
- package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
- package/src/resources/extensions/gsd/pre-execution-checks.ts +7 -0
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +19 -19
- 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 +72 -1
- 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/clean-root-preflight.test.ts +88 -2
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/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/detection.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
- package/src/resources/extensions/gsd/tests/exec-history.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +234 -0
- package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/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/schema-v27-v28-sequence.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +38 -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 +101 -2
- package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
- 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-resolver.test.ts +63 -1
- package/src/resources/extensions/gsd/tools/context-mode-tool-result.ts +25 -0
- package/src/resources/extensions/gsd/tools/exec-search-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -23
- package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
- package/src/resources/extensions/gsd/tools/resume-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
- package/src/resources/extensions/gsd/unit-context-composer.ts +19 -4
- package/src/resources/extensions/gsd/unit-runtime.ts +25 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
- package/src/resources/extensions/gsd/worktree-resolver.ts +36 -15
- /package/dist/web/standalone/.next/static/{TCSim36ZpcPu2WgeoC45g → -5nHJWzSdG-WkPMul_khA}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{TCSim36ZpcPu2WgeoC45g → -5nHJWzSdG-WkPMul_khA}/_ssgManifest.js +0 -0
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
13
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
-
import { join } from "node:path";
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { isAbsolute, join, relative } from "node:path";
|
|
15
15
|
import { loadPrompt } from "./prompt-loader.js";
|
|
16
16
|
import { gsdRoot } from "./paths.js";
|
|
17
17
|
import { GitServiceImpl, runGit } from "./git-service.js";
|
|
@@ -77,6 +77,40 @@ function ensureQuickDir(basePath: string, taskNum: number, slug: string): string
|
|
|
77
77
|
return taskDir;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
function isPathInside(parent: string, child: string): boolean {
|
|
81
|
+
const rel = relative(parent, child);
|
|
82
|
+
return rel === "" || (!!rel && !rel.startsWith("..") && !isAbsolute(rel));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isExternalGsdRoot(basePath: string, root: string): boolean {
|
|
86
|
+
try {
|
|
87
|
+
return !isPathInside(realpathSync(basePath), realpathSync(root));
|
|
88
|
+
} catch {
|
|
89
|
+
return !isPathInside(basePath, root);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function buildQuickCommitInstruction(basePath: string, root: string): string {
|
|
94
|
+
const externalState = isExternalGsdRoot(basePath, root);
|
|
95
|
+
if (externalState) {
|
|
96
|
+
return [
|
|
97
|
+
"Commit repo changes atomically, but do not stage or commit `.gsd/quick/...`:",
|
|
98
|
+
" - `.gsd/` resolves outside this git repository, so Git cannot stage quick-task summary files from the project repo.",
|
|
99
|
+
" - Write the quick summary file directly at the requested path; that file is persisted by GSD external state.",
|
|
100
|
+
" - Stage and commit only implementation/test/docs files that live inside the repository.",
|
|
101
|
+
" - If the task only writes quick-task research/summary files and no repository files changed, do not run `git commit`; report that there was nothing in the project repo to commit.",
|
|
102
|
+
].join("\n");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return [
|
|
106
|
+
"Commit your changes atomically:",
|
|
107
|
+
" - Use conventional commit messages (feat:, fix:, refactor:, etc.)",
|
|
108
|
+
" - Stage only relevant files — never commit secrets or runtime files.",
|
|
109
|
+
" - Commit logical units separately if the task involves distinct changes.",
|
|
110
|
+
" - Quick tasks run outside the auto-mode lifecycle — there is no system auto-commit, so commit directly here.",
|
|
111
|
+
].join("\n");
|
|
112
|
+
}
|
|
113
|
+
|
|
80
114
|
function quickReturnStatePath(basePath: string): string {
|
|
81
115
|
return join(gsdRoot(basePath), "runtime", "quick-return.json");
|
|
82
116
|
}
|
|
@@ -246,6 +280,7 @@ export async function handleQuick(
|
|
|
246
280
|
taskDir: taskDirRel,
|
|
247
281
|
branch: actualBranch,
|
|
248
282
|
summaryPath,
|
|
283
|
+
commitInstruction: buildQuickCommitInstruction(basePath, root),
|
|
249
284
|
date,
|
|
250
285
|
taskNum: String(taskNum),
|
|
251
286
|
slug,
|
|
@@ -50,6 +50,15 @@ export interface FileEditEvidence {
|
|
|
50
50
|
|
|
51
51
|
export type EvidenceEntry = BashEvidence | FileWriteEvidence | FileEditEvidence;
|
|
52
52
|
|
|
53
|
+
const EXECUTION_TOOL_NAMES = new Set([
|
|
54
|
+
"bash",
|
|
55
|
+
"Bash",
|
|
56
|
+
"gsd_exec",
|
|
57
|
+
"gsd_exec_search",
|
|
58
|
+
"mcp__gsd-workflow__gsd_exec",
|
|
59
|
+
"mcp__gsd-workflow__gsd_exec_search",
|
|
60
|
+
]);
|
|
61
|
+
|
|
53
62
|
// ─── Module State ───────────────────────────────────────────────────────────
|
|
54
63
|
|
|
55
64
|
let unitEvidence: EvidenceEntry[] = [];
|
|
@@ -188,11 +197,11 @@ export function clearEvidenceFromDisk(
|
|
|
188
197
|
* Exit codes and output are filled in by recordToolResult after execution.
|
|
189
198
|
*/
|
|
190
199
|
export function recordToolCall(toolCallId: string, toolName: string, input: Record<string, unknown>): void {
|
|
191
|
-
if (toolName
|
|
200
|
+
if (EXECUTION_TOOL_NAMES.has(toolName)) {
|
|
192
201
|
unitEvidence.push({
|
|
193
202
|
kind: "bash",
|
|
194
203
|
toolCallId,
|
|
195
|
-
command: String(input.command ?? ""),
|
|
204
|
+
command: String(input.command ?? input.cmd ?? input.query ?? ""),
|
|
196
205
|
exitCode: -1,
|
|
197
206
|
outputSnippet: "",
|
|
198
207
|
timestamp: Date.now(),
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
_resetPendingResolve,
|
|
11
11
|
_hasPendingResolveForTest,
|
|
12
12
|
_setActiveSession,
|
|
13
|
+
_setSessionSwitchInFlight,
|
|
14
|
+
_consumePendingSwitchCancellation,
|
|
13
15
|
isSessionSwitchInFlight,
|
|
14
16
|
} from "../auto/resolve.js";
|
|
15
17
|
import { runUnit } from "../auto/run-unit.js";
|
|
@@ -206,6 +208,28 @@ test("runUnit returns cancelled when session creation fails", async () => {
|
|
|
206
208
|
assert.equal(pi.calls.length, 0);
|
|
207
209
|
});
|
|
208
210
|
|
|
211
|
+
test("runUnit clears queued switch cancellation when session creation fails", async () => {
|
|
212
|
+
_resetPendingResolve();
|
|
213
|
+
|
|
214
|
+
const ctx = makeMockCtx();
|
|
215
|
+
const pi = makeMockPi();
|
|
216
|
+
const s = makeMockSession({
|
|
217
|
+
newSessionThrows: "connection refused",
|
|
218
|
+
onNewSessionStart: () => {
|
|
219
|
+
resolveAgentEndCancelled({
|
|
220
|
+
message: "Claude Code process aborted by user",
|
|
221
|
+
category: "aborted",
|
|
222
|
+
isTransient: false,
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const result = await runUnit(ctx, pi, s, "task", "T01", "prompt");
|
|
228
|
+
|
|
229
|
+
assert.equal(result.status, "cancelled");
|
|
230
|
+
assert.equal(_consumePendingSwitchCancellation(), null);
|
|
231
|
+
});
|
|
232
|
+
|
|
209
233
|
test("runUnit returns cancelled when session creation times out", async () => {
|
|
210
234
|
_resetPendingResolve();
|
|
211
235
|
|
|
@@ -221,6 +245,34 @@ test("runUnit returns cancelled when session creation times out", async () => {
|
|
|
221
245
|
assert.equal(pi.calls.length, 0);
|
|
222
246
|
});
|
|
223
247
|
|
|
248
|
+
test("runUnit consumes a cancellation queued during session switch before dispatch", async () => {
|
|
249
|
+
_resetPendingResolve();
|
|
250
|
+
|
|
251
|
+
const ctx = makeMockCtx();
|
|
252
|
+
const pi = makeMockPi();
|
|
253
|
+
let cancellationQueued = false;
|
|
254
|
+
const s = makeMockSession({
|
|
255
|
+
newSessionDelayMs: 10,
|
|
256
|
+
onNewSessionStart: () => {
|
|
257
|
+
setTimeout(() => {
|
|
258
|
+
cancellationQueued = !resolveAgentEndCancelled({
|
|
259
|
+
message: "Claude Code process aborted by user",
|
|
260
|
+
category: "aborted",
|
|
261
|
+
isTransient: false,
|
|
262
|
+
});
|
|
263
|
+
}, 0);
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const result = await runUnit(ctx, pi, s, "plan-slice", "M009/S01", "prompt");
|
|
268
|
+
|
|
269
|
+
assert.equal(cancellationQueued, true);
|
|
270
|
+
assert.equal(result.status, "cancelled");
|
|
271
|
+
assert.equal(result.errorContext?.category, "aborted");
|
|
272
|
+
assert.equal(result.errorContext?.message, "Claude Code process aborted by user");
|
|
273
|
+
assert.equal(pi.calls.length, 0, "queued switch cancellation must prevent prompt dispatch");
|
|
274
|
+
});
|
|
275
|
+
|
|
224
276
|
test("runUnit keeps the session-switch guard across a late newSession settlement", async () => {
|
|
225
277
|
_resetPendingResolve();
|
|
226
278
|
mock.timers.enable();
|
|
@@ -2089,6 +2141,25 @@ test("resolveAgentEndCancelled without args produces no errorContext field", asy
|
|
|
2089
2141
|
assert.equal(resolved.errorContext, undefined, "errorContext must not be present when no args passed");
|
|
2090
2142
|
});
|
|
2091
2143
|
|
|
2144
|
+
test("resolveAgentEndCancelled queues cancellation that arrives during session switch", () => {
|
|
2145
|
+
_resetPendingResolve();
|
|
2146
|
+
|
|
2147
|
+
_setSessionSwitchInFlight(true);
|
|
2148
|
+
const resolved = resolveAgentEndCancelled({
|
|
2149
|
+
message: "Claude Code process aborted by user",
|
|
2150
|
+
category: "aborted",
|
|
2151
|
+
isTransient: false,
|
|
2152
|
+
});
|
|
2153
|
+
|
|
2154
|
+
assert.equal(resolved, false);
|
|
2155
|
+
const pending = _consumePendingSwitchCancellation();
|
|
2156
|
+
assert.ok(pending?.errorContext, "queued cancellation should preserve errorContext");
|
|
2157
|
+
assert.equal(pending.errorContext.category, "aborted");
|
|
2158
|
+
assert.equal(pending.errorContext.message, "Claude Code process aborted by user");
|
|
2159
|
+
assert.equal(_consumePendingSwitchCancellation(), null);
|
|
2160
|
+
_resetPendingResolve();
|
|
2161
|
+
});
|
|
2162
|
+
|
|
2092
2163
|
// ─── #1571: artifact verification retry ──────────────────────────────────────
|
|
2093
2164
|
|
|
2094
2165
|
test("autoLoop re-iterates when postUnitPreVerification returns retry (#1571)", async () => {
|
|
@@ -2556,7 +2627,7 @@ test("autoLoop warns but proceeds for greenfield project (no project files) (#18
|
|
|
2556
2627
|
"should not stop with health check failure for greenfield project",
|
|
2557
2628
|
);
|
|
2558
2629
|
const greenfieldWarning = notifications.find(
|
|
2559
|
-
(n) => n.includes("no
|
|
2630
|
+
(n) => n.includes("no project content yet") && n.includes("greenfield"),
|
|
2560
2631
|
);
|
|
2561
2632
|
assert.ok(
|
|
2562
2633
|
greenfieldWarning,
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
3
6
|
|
|
4
7
|
import { runFinalize } from "../auto/phases.ts";
|
|
5
8
|
import { AutoSession } from "../auto/session.ts";
|
|
9
|
+
import { readUnitRuntimeRecord, writeUnitRuntimeRecord } from "../unit-runtime.ts";
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
};
|
|
11
|
+
async function runSuccessfulFinalize(s: AutoSession) {
|
|
12
|
+
const unit = s.currentUnit;
|
|
13
|
+
assert.ok(unit, "test setup must provide currentUnit");
|
|
14
|
+
|
|
15
|
+
writeUnitRuntimeRecord(s.basePath, unit.type, unit.id, unit.startedAt, {
|
|
16
|
+
phase: "dispatched",
|
|
17
|
+
});
|
|
15
18
|
|
|
16
19
|
const deps = {
|
|
17
20
|
clearUnitTimeout() {},
|
|
@@ -26,7 +29,7 @@ test("runFinalize clears currentUnit after successful finalize", async () => {
|
|
|
26
29
|
postUnitPostVerification: async () => "continue",
|
|
27
30
|
};
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
return runFinalize(
|
|
30
33
|
{
|
|
31
34
|
ctx: { ui: { notify() {} } },
|
|
32
35
|
pi: {},
|
|
@@ -38,8 +41,8 @@ test("runFinalize clears currentUnit after successful finalize", async () => {
|
|
|
38
41
|
nextSeq: () => 1,
|
|
39
42
|
} as any,
|
|
40
43
|
{
|
|
41
|
-
unitType:
|
|
42
|
-
unitId:
|
|
44
|
+
unitType: unit.type,
|
|
45
|
+
unitId: unit.id,
|
|
43
46
|
prompt: "",
|
|
44
47
|
finalPrompt: "",
|
|
45
48
|
pauseAfterUatDispatch: false,
|
|
@@ -55,7 +58,47 @@ test("runFinalize clears currentUnit after successful finalize", async () => {
|
|
|
55
58
|
consecutiveFinalizeTimeouts: 0,
|
|
56
59
|
},
|
|
57
60
|
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
test("runFinalize clears currentUnit after successful finalize", async () => {
|
|
64
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-finalize-current-unit-"));
|
|
65
|
+
const s = new AutoSession();
|
|
66
|
+
s.basePath = base;
|
|
67
|
+
s.currentUnit = {
|
|
68
|
+
type: "execute-task",
|
|
69
|
+
id: "M001/S01/T01",
|
|
70
|
+
startedAt: Date.now(),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const result = await runSuccessfulFinalize(s);
|
|
75
|
+
|
|
76
|
+
assert.equal(result.action, "next");
|
|
77
|
+
assert.equal(s.currentUnit, null);
|
|
78
|
+
} finally {
|
|
79
|
+
rmSync(base, { recursive: true, force: true });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("runFinalize marks unit runtime finalized after successful finalize", async () => {
|
|
84
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-finalize-runtime-"));
|
|
85
|
+
const s = new AutoSession();
|
|
86
|
+
const startedAt = Date.now();
|
|
87
|
+
s.basePath = base;
|
|
88
|
+
s.currentUnit = {
|
|
89
|
+
type: "complete-milestone",
|
|
90
|
+
id: "M001",
|
|
91
|
+
startedAt,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const result = await runSuccessfulFinalize(s);
|
|
96
|
+
const runtime = readUnitRuntimeRecord(base, "complete-milestone", "M001");
|
|
58
97
|
|
|
59
|
-
|
|
60
|
-
|
|
98
|
+
assert.equal(result.action, "next");
|
|
99
|
+
assert.equal(runtime?.phase, "finalized");
|
|
100
|
+
assert.equal(runtime?.lastProgressKind, "finalize-success");
|
|
101
|
+
} finally {
|
|
102
|
+
rmSync(base, { recursive: true, force: true });
|
|
103
|
+
}
|
|
61
104
|
});
|
|
@@ -5,7 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
|
|
8
|
-
import { verifyExpectedArtifact, hasImplementationArtifacts, resolveExpectedArtifactPath, diagnoseExpectedArtifact, buildLoopRemediationSteps, writeBlockerPlaceholder } from "../auto-recovery.ts";
|
|
8
|
+
import { verifyExpectedArtifact, hasImplementationArtifacts, resolveExpectedArtifactPath, diagnoseExpectedArtifact, buildLoopRemediationSteps, writeBlockerPlaceholder, refreshRecoveryDbForArtifact } from "../auto-recovery.ts";
|
|
9
9
|
import { resolveMilestoneFile } from "../paths.ts";
|
|
10
10
|
import { openDatabase, closeDatabase, insertMilestone, insertSlice, insertGateRow, insertTask, getMilestoneCommitAttributionShas } from "../gsd-db.ts";
|
|
11
11
|
import { clearParseCache } from "../files.ts";
|
|
@@ -175,6 +175,19 @@ test("resolveExpectedArtifactPath returns correct path for all slice-level types
|
|
|
175
175
|
}
|
|
176
176
|
});
|
|
177
177
|
|
|
178
|
+
test("refreshRecoveryDbForArtifact treats missing execute-task DB rows as fatal mismatches", () => {
|
|
179
|
+
makeTmpProject();
|
|
180
|
+
|
|
181
|
+
const result = refreshRecoveryDbForArtifact("execute-task", "M001/S01/T01");
|
|
182
|
+
|
|
183
|
+
assert.deepEqual(result, {
|
|
184
|
+
ok: false,
|
|
185
|
+
fatal: true,
|
|
186
|
+
reason: "execute-task-artifact-db-missing",
|
|
187
|
+
message: "Stuck recovery found execute-task M001/S01/T01 artifacts, but no matching DB task row exists after refresh.",
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
178
191
|
// ─── diagnoseExpectedArtifact ─────────────────────────────────────────────
|
|
179
192
|
|
|
180
193
|
test("diagnoseExpectedArtifact returns description for known types", () => {
|
|
@@ -131,7 +131,7 @@ test("postflightPopStash — restores stashed changes and emits info notificatio
|
|
|
131
131
|
run('git commit -m "simulate merge"', repo);
|
|
132
132
|
|
|
133
133
|
const postNotifications: Array<{ msg: string; level: string }> = [];
|
|
134
|
-
postflightPopStash(repo, "M004", (msg, level) => {
|
|
134
|
+
postflightPopStash(repo, "M004", preflight.stashMarker, (msg, level) => {
|
|
135
135
|
postNotifications.push({ msg, level });
|
|
136
136
|
});
|
|
137
137
|
|
|
@@ -171,7 +171,7 @@ test("preflight + merge + postflight round-trip preserves uncommitted changes",
|
|
|
171
171
|
run('git commit -m "feat: add feature"', repo);
|
|
172
172
|
|
|
173
173
|
// Postflight: pop stash
|
|
174
|
-
postflightPopStash(repo, "M005", () => {});
|
|
174
|
+
postflightPopStash(repo, "M005", preflight.stashMarker, () => {});
|
|
175
175
|
|
|
176
176
|
// README.md must still have our local content
|
|
177
177
|
const restored = readFileSync(join(repo, "README.md"), "utf-8");
|
|
@@ -184,3 +184,89 @@ test("preflight + merge + postflight round-trip preserves uncommitted changes",
|
|
|
184
184
|
try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
|
|
185
185
|
}
|
|
186
186
|
});
|
|
187
|
+
|
|
188
|
+
test("postflightPopStash conflict warning names the exact stash ref", () => {
|
|
189
|
+
const repo = createTempRepo();
|
|
190
|
+
try {
|
|
191
|
+
writeFileSync(join(repo, "README.md"), "# local work\n");
|
|
192
|
+
const preflight = preflightCleanRoot(repo, "M005C", () => {});
|
|
193
|
+
assert.equal(preflight.stashPushed, true, "must have stashed");
|
|
194
|
+
|
|
195
|
+
writeFileSync(join(repo, "README.md"), "# merged work\n");
|
|
196
|
+
run("git add README.md", repo);
|
|
197
|
+
run('git commit -m "simulate conflicting merge"', repo);
|
|
198
|
+
|
|
199
|
+
const notifications: Array<{ msg: string; level: string }> = [];
|
|
200
|
+
postflightPopStash(repo, "M005C", preflight.stashMarker, (msg, level) => {
|
|
201
|
+
notifications.push({ msg, level });
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const warning = notifications.find((n) => n.level === "warning")?.msg ?? "";
|
|
205
|
+
assert.match(warning, /git stash pop stash@\{\d+\}/);
|
|
206
|
+
assert.match(warning, /git stash apply stash@\{\d+\}/);
|
|
207
|
+
} finally {
|
|
208
|
+
try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("postflightPopStash restores the matching GSD stash, not stash@{0}", () => {
|
|
213
|
+
const repo = createTempRepo();
|
|
214
|
+
try {
|
|
215
|
+
writeFileSync(join(repo, "README.md"), "# target stash\n");
|
|
216
|
+
const preflight = preflightCleanRoot(repo, "M006", () => {});
|
|
217
|
+
assert.equal(preflight.stashPushed, true, "must have stashed target change");
|
|
218
|
+
|
|
219
|
+
writeFileSync(join(repo, "other.txt"), "other stash\n");
|
|
220
|
+
run('git stash push --include-untracked -m "unrelated newer stash"', repo);
|
|
221
|
+
|
|
222
|
+
postflightPopStash(repo, "M006", preflight.stashMarker, () => {});
|
|
223
|
+
|
|
224
|
+
const content = readFileSync(join(repo, "README.md"), "utf-8");
|
|
225
|
+
assert.equal(content.replace(/\r\n/g, "\n"), "# target stash\n");
|
|
226
|
+
const stashList = run("git stash list", repo);
|
|
227
|
+
assert.ok(stashList.includes("unrelated newer stash"), "unrelated newer stash must remain");
|
|
228
|
+
assert.ok(!stashList.includes("gsd-preflight-stash [gsd-preflight-stash:M006"), "target stash should be consumed");
|
|
229
|
+
} finally {
|
|
230
|
+
try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("postflightPopStash restores the exact preflight marker when another same-milestone stash exists", () => {
|
|
235
|
+
const repo = createTempRepo();
|
|
236
|
+
try {
|
|
237
|
+
writeFileSync(join(repo, "README.md"), "# target stash\n");
|
|
238
|
+
const preflight = preflightCleanRoot(repo, "M007", () => {});
|
|
239
|
+
assert.equal(preflight.stashPushed, true, "must have stashed target change");
|
|
240
|
+
assert.ok(preflight.stashMarker, "preflight must expose exact stash marker");
|
|
241
|
+
|
|
242
|
+
writeFileSync(join(repo, "same-milestone.txt"), "newer same milestone stash\n");
|
|
243
|
+
run('git stash push --include-untracked -m "gsd-preflight-stash [gsd-preflight-stash:M007:other]"', repo);
|
|
244
|
+
|
|
245
|
+
postflightPopStash(repo, "M007", preflight.stashMarker, () => {});
|
|
246
|
+
|
|
247
|
+
const content = readFileSync(join(repo, "README.md"), "utf-8");
|
|
248
|
+
assert.equal(content.replace(/\r\n/g, "\n"), "# target stash\n");
|
|
249
|
+
const stashList = run("git stash list", repo);
|
|
250
|
+
assert.ok(stashList.includes("gsd-preflight-stash:M007:other"), "newer same-milestone stash must remain");
|
|
251
|
+
assert.ok(!stashList.includes(preflight.stashMarker), "exact target stash should be consumed");
|
|
252
|
+
} finally {
|
|
253
|
+
try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("postflightPopStash falls back to milestone marker prefix when exact marker is unavailable", () => {
|
|
258
|
+
const repo = createTempRepo();
|
|
259
|
+
try {
|
|
260
|
+
writeFileSync(join(repo, "README.md"), "# fallback stash\n");
|
|
261
|
+
run('git stash push --include-untracked -m "gsd-preflight-stash [gsd-preflight-stash:M008:fallback]"', repo);
|
|
262
|
+
|
|
263
|
+
postflightPopStash(repo, "M008", undefined, () => {});
|
|
264
|
+
|
|
265
|
+
const content = readFileSync(join(repo, "README.md"), "utf-8");
|
|
266
|
+
assert.equal(content.replace(/\r\n/g, "\n"), "# fallback stash\n");
|
|
267
|
+
const stashList = run("git stash list", repo);
|
|
268
|
+
assert.ok(!stashList.includes("gsd-preflight-stash:M008:fallback"), "fallback stash should be consumed");
|
|
269
|
+
} finally {
|
|
270
|
+
try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
|
|
271
|
+
}
|
|
272
|
+
});
|
|
@@ -29,7 +29,8 @@ test('buildSnapshot: renders memories, exec history, and active context', () =>
|
|
|
29
29
|
memories: [
|
|
30
30
|
{ id: 'MEM001', category: 'gotcha', content: 'FTS5 needs Porter tokenizer', confidence: 0.9,
|
|
31
31
|
source_unit_type: null, source_unit_id: null, created_at: '', updated_at: '',
|
|
32
|
-
superseded_by: null, hit_count: 0, scope: 'project', seq: 1, tags: [], structured_fields: null
|
|
32
|
+
superseded_by: null, hit_count: 0, scope: 'project', seq: 1, tags: [], structured_fields: null,
|
|
33
|
+
last_hit_at: null },
|
|
33
34
|
],
|
|
34
35
|
execHistory: [
|
|
35
36
|
{
|
|
@@ -65,6 +66,7 @@ test('buildSnapshot: enforces the byte cap with a truncation marker', () => {
|
|
|
65
66
|
seq: i,
|
|
66
67
|
tags: [] as string[],
|
|
67
68
|
structured_fields: null,
|
|
69
|
+
last_hit_at: null,
|
|
68
70
|
}));
|
|
69
71
|
const snap = buildSnapshot(
|
|
70
72
|
{ generatedAt: new Date(), memories: longMemories, execHistory: [] },
|
|
@@ -121,3 +123,14 @@ test('executeResume: reports friendly empty state when no snapshot exists', () =
|
|
|
121
123
|
cleanup(base);
|
|
122
124
|
}
|
|
123
125
|
});
|
|
126
|
+
|
|
127
|
+
test('executeResume: returns disabled error when context_mode.enabled=false', () => {
|
|
128
|
+
const base = freshBase();
|
|
129
|
+
try {
|
|
130
|
+
const result = executeResume({}, { baseDir: base, preferences: { context_mode: { enabled: false } } });
|
|
131
|
+
assert.equal(result.isError, true);
|
|
132
|
+
assert.equal((result.details as { error?: string }).error, 'context_mode_disabled');
|
|
133
|
+
} finally {
|
|
134
|
+
cleanup(base);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* No I/O, no extension context, no global state.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { describe, it } from "node:test";
|
|
7
|
+
import { describe, it, beforeEach } from "node:test";
|
|
8
8
|
import assert from "node:assert/strict";
|
|
9
9
|
|
|
10
10
|
import {
|
|
@@ -16,8 +16,17 @@ import {
|
|
|
16
16
|
computeBudgets,
|
|
17
17
|
truncateAtSectionBoundary,
|
|
18
18
|
resolveExecutorContextWindow,
|
|
19
|
+
_resetEmpiricalCacheForTest,
|
|
19
20
|
} from "../context-budget.js";
|
|
20
21
|
|
|
22
|
+
// Reset the per-provider empirical chars-per-token cache before each test.
|
|
23
|
+
// The hardcoded char-ratio assertions below assume the static fallback path
|
|
24
|
+
// (3.5 / 4.0 chars/token) is used. Without this guard, a prior test that
|
|
25
|
+
// warms tiktoken would populate the cache and silently break these tests.
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
_resetEmpiricalCacheForTest();
|
|
28
|
+
});
|
|
29
|
+
|
|
21
30
|
import type { TokenProvider } from "../token-counter.js";
|
|
22
31
|
|
|
23
32
|
// ─── Test helpers ─────────────────────────────────────────────────────────────
|
|
@@ -232,4 +232,59 @@ describe('emitCrashRecoveredUnitEnd (#3348)', () => {
|
|
|
232
232
|
rmSync(base, { recursive: true, force: true });
|
|
233
233
|
}
|
|
234
234
|
});
|
|
235
|
+
|
|
236
|
+
test('emitOpenUnitEndForUnit closes the latest open start with error context', async () => {
|
|
237
|
+
const base = makeTmpBase();
|
|
238
|
+
try {
|
|
239
|
+
const { emitJournalEvent, queryJournal } = await import('../journal.ts');
|
|
240
|
+
const { emitOpenUnitEndForUnit } = await import('../crash-recovery.ts');
|
|
241
|
+
|
|
242
|
+
const firstFlowId = randomUUID();
|
|
243
|
+
const secondFlowId = randomUUID();
|
|
244
|
+
emitJournalEvent(base, {
|
|
245
|
+
ts: new Date().toISOString(),
|
|
246
|
+
flowId: firstFlowId,
|
|
247
|
+
seq: 1,
|
|
248
|
+
eventType: 'unit-start',
|
|
249
|
+
data: { unitType: 'execute-task', unitId: 'M008/S04/T02' },
|
|
250
|
+
});
|
|
251
|
+
emitJournalEvent(base, {
|
|
252
|
+
ts: new Date().toISOString(),
|
|
253
|
+
flowId: firstFlowId,
|
|
254
|
+
seq: 2,
|
|
255
|
+
eventType: 'unit-end',
|
|
256
|
+
data: { unitType: 'execute-task', unitId: 'M008/S04/T02', status: 'completed' },
|
|
257
|
+
causedBy: { flowId: firstFlowId, seq: 1 },
|
|
258
|
+
});
|
|
259
|
+
emitJournalEvent(base, {
|
|
260
|
+
ts: new Date().toISOString(),
|
|
261
|
+
flowId: secondFlowId,
|
|
262
|
+
seq: 3,
|
|
263
|
+
eventType: 'unit-start',
|
|
264
|
+
data: { unitType: 'execute-task', unitId: 'M008/S04/T02' },
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const emitted = emitOpenUnitEndForUnit(
|
|
268
|
+
base,
|
|
269
|
+
'execute-task',
|
|
270
|
+
'M008/S04/T02',
|
|
271
|
+
'cancelled',
|
|
272
|
+
{ message: 'runUnitPhase exploded', category: 'unit-exception', isTransient: false },
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
assert.equal(emitted, true, 'open unit should be closed');
|
|
276
|
+
const ends = queryJournal(base).filter((e) => e.eventType === 'unit-end');
|
|
277
|
+
assert.equal(ends.length, 2, 'should preserve existing end and add one new end');
|
|
278
|
+
const newEnd = ends.find((e) => e.causedBy?.flowId === secondFlowId);
|
|
279
|
+
assert.ok(newEnd, 'new end should close the latest open start');
|
|
280
|
+
assert.equal(newEnd!.data?.status, 'cancelled');
|
|
281
|
+
assert.deepEqual(newEnd!.data?.errorContext, {
|
|
282
|
+
message: 'runUnitPhase exploded',
|
|
283
|
+
category: 'unit-exception',
|
|
284
|
+
isTransient: false,
|
|
285
|
+
});
|
|
286
|
+
} finally {
|
|
287
|
+
rmSync(base, { recursive: true, force: true });
|
|
288
|
+
}
|
|
289
|
+
});
|
|
235
290
|
});
|
|
@@ -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));
|