gsd-pi 2.80.0-dev.2848d9009 → 2.80.0-dev.42bd34838
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +32 -1
- package/dist/resources/extensions/gsd/auto/phases.js +29 -15
- package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +13 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +13 -1
- package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
- package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
- package/dist/resources/extensions/gsd/auto.js +68 -4
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +21 -2
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +27 -20
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +21 -0
- package/dist/resources/extensions/gsd/context-budget.js +37 -2
- package/dist/resources/extensions/gsd/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/git-service.js +36 -4
- package/dist/resources/extensions/gsd/gsd-db.js +46 -13
- package/dist/resources/extensions/gsd/guided-flow.js +52 -10
- 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/prompt-loader.js +28 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +14 -13
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/dist/resources/extensions/gsd/prompts/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/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-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 +11 -11
- 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/required-server-files.json +1 -1
- 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 +11 -11
- 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/dist/web/standalone/server.js +1 -1
- package/package.json +3 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +22 -17
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +75 -2
- package/packages/mcp-server/src/workflow-tools.ts +30 -16
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +8 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +3 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/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 +13 -5
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/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 +3 -1
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
- package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
- package/packages/pi-coding-agent/src/core/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 +22 -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/extensions/gsd/auto/loop.ts +50 -8
- package/src/resources/extensions/gsd/auto/phases.ts +35 -20
- package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +18 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +17 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
- package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
- package/src/resources/extensions/gsd/auto.ts +80 -3
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +21 -1
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +27 -19
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
- package/src/resources/extensions/gsd/context-budget.ts +44 -2
- package/src/resources/extensions/gsd/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/git-service.ts +46 -8
- package/src/resources/extensions/gsd/gsd-db.ts +50 -13
- package/src/resources/extensions/gsd/guided-flow.ts +71 -10
- 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/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +14 -13
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/src/resources/extensions/gsd/prompts/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/tests/auto-loop.test.ts +71 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/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/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/prompt-path-audit.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
- 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-resolver.ts +36 -15
- /package/dist/web/standalone/.next/static/{vaQ47E8aNlbbNEDoeME6G → tht0ltP8EIjsohPOA_WZf}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{vaQ47E8aNlbbNEDoeME6G → tht0ltP8EIjsohPOA_WZf}/_ssgManifest.js +0 -0
|
@@ -3,21 +3,34 @@
|
|
|
3
3
|
// Exposes the Context Mode runtime tools in-process. Default-on; opt out with
|
|
4
4
|
// `context_mode.enabled: false` in preferences.
|
|
5
5
|
import { Type } from "@sinclair/typebox";
|
|
6
|
+
async function loadContextModePreferences(baseDir) {
|
|
7
|
+
const [{ loadEffectiveGSDPreferences }, { logWarning }] = await Promise.all([
|
|
8
|
+
import("../preferences.js"),
|
|
9
|
+
import("../workflow-logger.js"),
|
|
10
|
+
]);
|
|
11
|
+
try {
|
|
12
|
+
return loadEffectiveGSDPreferences(baseDir)?.preferences ?? null;
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
logWarning("tool", `Context Mode tool could not load preferences: ${err instanceof Error ? err.message : String(err)}`);
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
6
19
|
export function registerExecTools(pi) {
|
|
7
20
|
pi.registerTool({
|
|
8
21
|
name: "gsd_exec",
|
|
9
22
|
label: "Exec (Sandboxed)",
|
|
10
|
-
description: "Run a short script (bash/node/python) in a subprocess.
|
|
23
|
+
description: "Run a short script (bash/node/python) in a subprocess. Capped stdout/stderr and metadata persist to " +
|
|
11
24
|
".gsd/exec/<id>.{stdout,stderr,meta.json}; only a short digest returns in context. Use " +
|
|
12
25
|
"this instead of reading many files or emitting large tool outputs — e.g. have the script " +
|
|
13
26
|
"count/grep/summarize and log the finding. Enabled by default; opt out via " +
|
|
14
27
|
"preferences.context_mode.enabled=false.",
|
|
15
|
-
promptSnippet: "Run a bash/node/python script in a sandbox;
|
|
28
|
+
promptSnippet: "Run a bash/node/python script in a sandbox; capped output is saved to disk and only a digest returns",
|
|
16
29
|
promptGuidelines: [
|
|
17
30
|
"Prefer gsd_exec for analyses that would otherwise read >3 files or produce large tool output.",
|
|
18
31
|
"Write scripts that log the finding (counts, matches, summaries) rather than raw dumps.",
|
|
19
32
|
"The digest is the last ~300 chars of stdout — size your log output accordingly.",
|
|
20
|
-
"Need
|
|
33
|
+
"Need persisted output? Read the stdout_path returned in details (file on local disk).",
|
|
21
34
|
],
|
|
22
35
|
parameters: Type.Object({
|
|
23
36
|
runtime: Type.Union([Type.Literal("bash"), Type.Literal("node"), Type.Literal("python")], { description: "Interpreter: bash (-c), node (-e), or python3 (-c)." }),
|
|
@@ -30,21 +43,11 @@ export function registerExecTools(pi) {
|
|
|
30
43
|
})),
|
|
31
44
|
}),
|
|
32
45
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
import("../preferences.js"),
|
|
36
|
-
import("../workflow-logger.js"),
|
|
37
|
-
]);
|
|
38
|
-
let prefs = null;
|
|
39
|
-
try {
|
|
40
|
-
prefs = loadEffectiveGSDPreferences();
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
logWarning("tool", `gsd_exec could not load preferences: ${err instanceof Error ? err.message : String(err)}`);
|
|
44
|
-
}
|
|
46
|
+
const { executeGsdExec } = await import("../tools/exec-tool.js");
|
|
47
|
+
const baseDir = process.cwd();
|
|
45
48
|
return executeGsdExec(params, {
|
|
46
|
-
baseDir
|
|
47
|
-
preferences:
|
|
49
|
+
baseDir,
|
|
50
|
+
preferences: await loadContextModePreferences(baseDir),
|
|
48
51
|
});
|
|
49
52
|
},
|
|
50
53
|
});
|
|
@@ -56,7 +59,7 @@ export function registerExecTools(pi) {
|
|
|
56
59
|
promptSnippet: "Search prior gsd_exec runs by substring, runtime, or failing-only filter",
|
|
57
60
|
promptGuidelines: [
|
|
58
61
|
"Use this before re-running an expensive analysis — the prior run's stdout file may still answer.",
|
|
59
|
-
"The preview shows the trailing ~300 chars of stdout; read stdout_path for
|
|
62
|
+
"The preview shows the trailing ~300 chars of stdout; read stdout_path for persisted output.",
|
|
60
63
|
],
|
|
61
64
|
parameters: Type.Object({
|
|
62
65
|
query: Type.Optional(Type.String({ description: "Substring matched against id and purpose (case-insensitive)." })),
|
|
@@ -68,8 +71,10 @@ export function registerExecTools(pi) {
|
|
|
68
71
|
}),
|
|
69
72
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
70
73
|
const { executeExecSearch } = await import("../tools/exec-search-tool.js");
|
|
74
|
+
const baseDir = process.cwd();
|
|
71
75
|
return executeExecSearch(params, {
|
|
72
|
-
baseDir
|
|
76
|
+
baseDir,
|
|
77
|
+
preferences: await loadContextModePreferences(baseDir),
|
|
73
78
|
});
|
|
74
79
|
},
|
|
75
80
|
});
|
|
@@ -87,8 +92,10 @@ export function registerExecTools(pi) {
|
|
|
87
92
|
parameters: Type.Object({}),
|
|
88
93
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
89
94
|
const { executeResume } = await import("../tools/resume-tool.js");
|
|
95
|
+
const baseDir = process.cwd();
|
|
90
96
|
return executeResume(params, {
|
|
91
|
-
baseDir
|
|
97
|
+
baseDir,
|
|
98
|
+
preferences: await loadContextModePreferences(baseDir),
|
|
92
99
|
});
|
|
93
100
|
},
|
|
94
101
|
});
|
|
@@ -52,6 +52,25 @@ async function applyDisabledModelProviderPolicy(ctx) {
|
|
|
52
52
|
// Non-fatal: keep default provider visibility if preferences cannot be loaded.
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Bridge `context_management.compaction_threshold_percent` from GSD preferences
|
|
57
|
+
* into the agent's runtime compaction settings (#5475). The preference is
|
|
58
|
+
* validated to (0.5, 0.95) at load time, but defense-in-depth normalization
|
|
59
|
+
* here protects against a stale or hand-edited prefs file. Calling with
|
|
60
|
+
* `undefined` clears any prior override so a removed preference does not leak.
|
|
61
|
+
*/
|
|
62
|
+
async function applyCompactionThresholdOverride(ctx) {
|
|
63
|
+
try {
|
|
64
|
+
const { loadEffectiveGSDPreferences } = await import("../preferences.js");
|
|
65
|
+
const prefs = loadEffectiveGSDPreferences();
|
|
66
|
+
const raw = prefs?.preferences.context_management?.compaction_threshold_percent;
|
|
67
|
+
const value = typeof raw === "number" && Number.isFinite(raw) && raw > 0 && raw < 1 ? raw : undefined;
|
|
68
|
+
ctx.setCompactionThresholdOverride(value);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Non-fatal: leave any existing override in place.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
55
74
|
export function resolveNotificationStoreBasePath(cwd = process.cwd()) {
|
|
56
75
|
return resolveWorktreeProjectRoot(cwd);
|
|
57
76
|
}
|
|
@@ -101,6 +120,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
101
120
|
await resetAskUserQuestionsTurnCache();
|
|
102
121
|
await syncServiceTierStatus(ctx);
|
|
103
122
|
await applyDisabledModelProviderPolicy(ctx);
|
|
123
|
+
await applyCompactionThresholdOverride(ctx);
|
|
104
124
|
// Skip MCP auto-prep when running inside an auto-worktree (see session_switch below).
|
|
105
125
|
const { isInAutoWorktree } = await import("../auto-worktree.js");
|
|
106
126
|
if (!isInAutoWorktree(process.cwd())) {
|
|
@@ -149,6 +169,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
149
169
|
clearDiscussionFlowState(process.cwd());
|
|
150
170
|
await syncServiceTierStatus(ctx);
|
|
151
171
|
await applyDisabledModelProviderPolicy(ctx);
|
|
172
|
+
await applyCompactionThresholdOverride(ctx);
|
|
152
173
|
// Skip MCP auto-prep when running inside an auto-worktree. The worktree
|
|
153
174
|
// already has .mcp.json from createAutoWorktree, and re-running the writer
|
|
154
175
|
// post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @see D001 (module location), D002 (200K fallback), D003 (section-boundary truncation)
|
|
9
9
|
*/
|
|
10
|
-
import { getCharsPerToken } from "./token-counter.js";
|
|
10
|
+
import { getCharsPerToken, isAccurateCountingAvailable, countTokensSync, } from "./token-counter.js";
|
|
11
11
|
// ─── Budget ratio constants ──────────────────────────────────────────────────
|
|
12
12
|
// Percentages of total context window allocated to each budget category.
|
|
13
13
|
// These are applied after tokens→chars conversion.
|
|
@@ -23,6 +23,22 @@ const CHARS_PER_TOKEN = 4;
|
|
|
23
23
|
const DEFAULT_CONTEXT_WINDOW = 200_000;
|
|
24
24
|
/** Conservative effective context for Claude Code subscription routing (#4676) */
|
|
25
25
|
const CLAUDE_CODE_EFFECTIVE_CONTEXT_WINDOW = 200_000;
|
|
26
|
+
/**
|
|
27
|
+
* Cached empirical chars-per-token from a tiktoken probe, keyed by provider.
|
|
28
|
+
* countTokensSync's fallback path is provider-aware, so we cache per-provider
|
|
29
|
+
* to preserve that distinction once the encoder warms. The cl100k_base encoder
|
|
30
|
+
* itself gives a stable ratio for ASCII English so a single probe per provider
|
|
31
|
+
* key is sufficient. Empty map means "not yet probed" or "encoder unavailable".
|
|
32
|
+
*/
|
|
33
|
+
const _empiricalCharsPerTokenByProvider = new Map();
|
|
34
|
+
/**
|
|
35
|
+
* Test hook — clears the empirical chars-per-token cache so test cases that
|
|
36
|
+
* assert against the static char-ratio fallback aren't polluted by a prior
|
|
37
|
+
* tiktoken-warmed run in the same process. Production code must not call this.
|
|
38
|
+
*/
|
|
39
|
+
export function _resetEmpiricalCacheForTest() {
|
|
40
|
+
_empiricalCharsPerTokenByProvider.clear();
|
|
41
|
+
}
|
|
26
42
|
/** Percentage of context consumed before suggesting a continue-here checkpoint */
|
|
27
43
|
const CONTINUE_THRESHOLD_PERCENT = 70;
|
|
28
44
|
// ─── Task count bounds ───────────────────────────────────────────────────────
|
|
@@ -46,7 +62,26 @@ const TASK_COUNT_TIERS = [
|
|
|
46
62
|
export function computeBudgets(contextWindow, provider) {
|
|
47
63
|
const effectiveWindow = contextWindow > 0 ? contextWindow : DEFAULT_CONTEXT_WINDOW;
|
|
48
64
|
const charsPerToken = provider ? getCharsPerToken(provider) : CHARS_PER_TOKEN;
|
|
49
|
-
|
|
65
|
+
// Prefer the tiktoken encoder for total-char estimation when it has been
|
|
66
|
+
// warmed (initTokenCounter resolved). The cl100k_base ratio is stable for
|
|
67
|
+
// ASCII English, so probe once per provider and cache — computeBudgets is
|
|
68
|
+
// called multiple times per prompt build and the probe encode is otherwise
|
|
69
|
+
// wasted work.
|
|
70
|
+
let totalChars;
|
|
71
|
+
if (isAccurateCountingAvailable()) {
|
|
72
|
+
const providerKey = provider ?? "__default__";
|
|
73
|
+
let empirical = _empiricalCharsPerTokenByProvider.get(providerKey);
|
|
74
|
+
if (empirical === undefined) {
|
|
75
|
+
const probe = "the quick brown fox jumps over the lazy dog ".repeat(64);
|
|
76
|
+
const probeTokens = countTokensSync(probe, provider);
|
|
77
|
+
empirical = probeTokens > 0 ? probe.length / probeTokens : charsPerToken;
|
|
78
|
+
_empiricalCharsPerTokenByProvider.set(providerKey, empirical);
|
|
79
|
+
}
|
|
80
|
+
totalChars = effectiveWindow * empirical;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
totalChars = effectiveWindow * charsPerToken;
|
|
84
|
+
}
|
|
50
85
|
return {
|
|
51
86
|
summaryBudgetChars: Math.floor(totalChars * SUMMARY_RATIO),
|
|
52
87
|
inlineContextBudgetChars: Math.floor(totalChars * INLINE_CONTEXT_RATIO),
|
|
@@ -29,6 +29,7 @@ import { _getAdapter, isDbAvailable } from "./gsd-db.js";
|
|
|
29
29
|
import { gsdRoot, normalizeRealPath } from "./paths.js";
|
|
30
30
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
31
31
|
import { effectiveLockFile } from "./session-lock.js";
|
|
32
|
+
import { isInFlightRuntimePhase, listUnitRuntimeRecords } from "./unit-runtime.js";
|
|
32
33
|
const SESSION_FILE_KV_KEY = "session_file";
|
|
33
34
|
function lockPath(basePath) {
|
|
34
35
|
return join(gsdRoot(basePath), effectiveLockFile());
|
|
@@ -72,9 +73,37 @@ function getLatestDispatchForWorker(workerId) {
|
|
|
72
73
|
LIMIT 1`).get({ ":worker_id": workerId });
|
|
73
74
|
return row ?? null;
|
|
74
75
|
}
|
|
75
|
-
function
|
|
76
|
+
function latestInFlightRuntimeRecord(basePath) {
|
|
77
|
+
const records = listUnitRuntimeRecords(basePath).filter((record) => isInFlightRuntimePhase(record.phase));
|
|
78
|
+
if (records.length === 0)
|
|
79
|
+
return null;
|
|
80
|
+
return records.sort((a, b) => {
|
|
81
|
+
const bTime = b.updatedAt || b.startedAt || 0;
|
|
82
|
+
const aTime = a.updatedAt || a.startedAt || 0;
|
|
83
|
+
return bTime - aTime;
|
|
84
|
+
})[0] ?? null;
|
|
85
|
+
}
|
|
86
|
+
function runtimeRecordToLockData(worker, record, sessionFile) {
|
|
87
|
+
const startedAt = Number.isFinite(record.startedAt)
|
|
88
|
+
? new Date(record.startedAt).toISOString()
|
|
89
|
+
: worker.started_at;
|
|
90
|
+
return {
|
|
91
|
+
pid: worker.pid,
|
|
92
|
+
startedAt: worker.started_at,
|
|
93
|
+
unitType: record.unitType,
|
|
94
|
+
unitId: record.unitId,
|
|
95
|
+
unitStartedAt: startedAt,
|
|
96
|
+
sessionFile,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function workerToLockData(basePath, worker) {
|
|
76
100
|
const dispatch = getLatestDispatchForWorker(worker.worker_id);
|
|
77
101
|
const sessionFile = getRuntimeKv("worker", worker.worker_id, SESSION_FILE_KV_KEY) ?? undefined;
|
|
102
|
+
if (!dispatch) {
|
|
103
|
+
const runtimeRecord = latestInFlightRuntimeRecord(basePath);
|
|
104
|
+
if (runtimeRecord)
|
|
105
|
+
return runtimeRecordToLockData(worker, runtimeRecord, sessionFile);
|
|
106
|
+
}
|
|
78
107
|
return {
|
|
79
108
|
pid: worker.pid,
|
|
80
109
|
startedAt: worker.started_at,
|
|
@@ -172,7 +201,7 @@ export function readCrashLock(basePath) {
|
|
|
172
201
|
const projectRoot = normalizeRealPath(basePath);
|
|
173
202
|
const stale = findStaleWorkerForProject(projectRoot);
|
|
174
203
|
if (stale)
|
|
175
|
-
return workerToLockData(stale);
|
|
204
|
+
return workerToLockData(basePath, stale);
|
|
176
205
|
}
|
|
177
206
|
catch {
|
|
178
207
|
// Fall through to the legacy lock-file compatibility path.
|
|
@@ -232,18 +261,32 @@ export function formatCrashInfo(lock) {
|
|
|
232
261
|
export function emitCrashRecoveredUnitEnd(basePath, lock) {
|
|
233
262
|
if (!lock.unitType || !lock.unitId || lock.unitType === "starting")
|
|
234
263
|
return;
|
|
264
|
+
emitOpenUnitEndForUnit(basePath, lock.unitType, lock.unitId, "crash-recovered");
|
|
265
|
+
}
|
|
266
|
+
export function emitOpenUnitEndForUnit(basePath, unitType, unitId, status, errorContext) {
|
|
235
267
|
try {
|
|
236
268
|
const all = queryJournal(basePath);
|
|
237
|
-
const starts = all.filter((e) => e.eventType === "unit-start" &&
|
|
269
|
+
const starts = all.filter((e) => e.eventType === "unit-start" &&
|
|
270
|
+
e.data?.unitType === unitType &&
|
|
271
|
+
e.data?.unitId === unitId);
|
|
238
272
|
if (starts.length === 0)
|
|
239
|
-
return;
|
|
240
|
-
const lastStart =
|
|
273
|
+
return false;
|
|
274
|
+
const lastStart = [...starts].reverse().find((start) => {
|
|
275
|
+
return !all.some((e) => e.eventType === "unit-end" &&
|
|
276
|
+
e.data?.unitType === unitType &&
|
|
277
|
+
e.data?.unitId === unitId &&
|
|
278
|
+
e.causedBy?.flowId === start.flowId &&
|
|
279
|
+
e.causedBy?.seq === start.seq);
|
|
280
|
+
});
|
|
281
|
+
if (!lastStart)
|
|
282
|
+
return false;
|
|
241
283
|
const alreadyClosed = all.some((e) => e.eventType === "unit-end" &&
|
|
242
|
-
e.data?.
|
|
284
|
+
e.data?.unitType === unitType &&
|
|
285
|
+
e.data?.unitId === unitId &&
|
|
243
286
|
e.causedBy?.flowId === lastStart.flowId &&
|
|
244
287
|
e.causedBy?.seq === lastStart.seq);
|
|
245
288
|
if (alreadyClosed)
|
|
246
|
-
return;
|
|
289
|
+
return false;
|
|
247
290
|
const maxSeq = all
|
|
248
291
|
.filter((e) => e.flowId === lastStart.flowId)
|
|
249
292
|
.reduce((max, e) => Math.max(max, e.seq), lastStart.seq);
|
|
@@ -253,16 +296,19 @@ export function emitCrashRecoveredUnitEnd(basePath, lock) {
|
|
|
253
296
|
seq: maxSeq + 1,
|
|
254
297
|
eventType: "unit-end",
|
|
255
298
|
data: {
|
|
256
|
-
unitType
|
|
257
|
-
unitId
|
|
258
|
-
status
|
|
299
|
+
unitType,
|
|
300
|
+
unitId,
|
|
301
|
+
status,
|
|
259
302
|
artifactVerified: false,
|
|
303
|
+
...(errorContext ? { errorContext } : {}),
|
|
260
304
|
},
|
|
261
305
|
causedBy: { flowId: lastStart.flowId, seq: lastStart.seq },
|
|
262
306
|
});
|
|
307
|
+
return true;
|
|
263
308
|
}
|
|
264
309
|
catch {
|
|
265
310
|
// Never throw from crash recovery path.
|
|
311
|
+
return false;
|
|
266
312
|
}
|
|
267
313
|
}
|
|
268
314
|
/**
|
|
@@ -253,6 +253,45 @@ export function markCanceled(dispatchId, reason) {
|
|
|
253
253
|
SET status = 'canceled', ended_at = :ended_at, exit_reason = :reason
|
|
254
254
|
WHERE id = :id AND status IN ('pending','claimed','running')`).run({ ":id": dispatchId, ":ended_at": now, ":reason": reason });
|
|
255
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Best-effort signal/crash cleanup: cancel the latest active dispatch owned by
|
|
258
|
+
* a worker when the process is exiting before the normal loop can settle it.
|
|
259
|
+
*/
|
|
260
|
+
export function markLatestActiveForWorkerCanceled(workerId, reason) {
|
|
261
|
+
if (!isDbAvailable())
|
|
262
|
+
return false;
|
|
263
|
+
const now = new Date().toISOString();
|
|
264
|
+
const db = _getAdapter();
|
|
265
|
+
const result = transaction(() => {
|
|
266
|
+
return db.prepare(`UPDATE unit_dispatches
|
|
267
|
+
SET status = 'canceled', ended_at = :ended_at, exit_reason = :reason
|
|
268
|
+
WHERE id = (
|
|
269
|
+
SELECT id FROM unit_dispatches
|
|
270
|
+
WHERE worker_id = :worker_id
|
|
271
|
+
AND status IN ('pending','claimed','running')
|
|
272
|
+
ORDER BY id DESC
|
|
273
|
+
LIMIT 1
|
|
274
|
+
)`).run({
|
|
275
|
+
":ended_at": now,
|
|
276
|
+
":reason": reason,
|
|
277
|
+
":worker_id": workerId,
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
const changes = typeof result.changes === "number"
|
|
281
|
+
? result.changes
|
|
282
|
+
: 0;
|
|
283
|
+
if (changes <= 0)
|
|
284
|
+
return false;
|
|
285
|
+
insertAuditEvent({
|
|
286
|
+
eventId: randomUUID(),
|
|
287
|
+
traceId: workerId,
|
|
288
|
+
category: "orchestration",
|
|
289
|
+
type: "dispatch-canceled",
|
|
290
|
+
ts: now,
|
|
291
|
+
payload: { workerId, reason },
|
|
292
|
+
});
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
256
295
|
/**
|
|
257
296
|
* Fetch the most recent N dispatches for a unit. Used by recordDispatchClaim
|
|
258
297
|
* callers to compute attempt_n and by detect-stuck.ts (B3) to consult
|
|
@@ -46,7 +46,8 @@ export function createBaseSchemaObjects(db, hooks) {
|
|
|
46
46
|
slice_id TEXT DEFAULT NULL,
|
|
47
47
|
task_id TEXT DEFAULT NULL,
|
|
48
48
|
full_content TEXT NOT NULL DEFAULT '',
|
|
49
|
-
imported_at TEXT NOT NULL DEFAULT ''
|
|
49
|
+
imported_at TEXT NOT NULL DEFAULT '',
|
|
50
|
+
content_hash TEXT DEFAULT NULL
|
|
50
51
|
)
|
|
51
52
|
`);
|
|
52
53
|
db.exec(`
|
|
@@ -64,7 +65,8 @@ export function createBaseSchemaObjects(db, hooks) {
|
|
|
64
65
|
hit_count INTEGER NOT NULL DEFAULT 0,
|
|
65
66
|
scope TEXT NOT NULL DEFAULT 'project',
|
|
66
67
|
tags TEXT NOT NULL DEFAULT '[]',
|
|
67
|
-
structured_fields TEXT DEFAULT NULL
|
|
68
|
+
structured_fields TEXT DEFAULT NULL,
|
|
69
|
+
last_hit_at TEXT DEFAULT NULL
|
|
68
70
|
)
|
|
69
71
|
`);
|
|
70
72
|
db.exec(`
|
|
@@ -379,6 +379,12 @@ export function applyMigrationV26MilestoneCommitAttributions(db) {
|
|
|
379
379
|
`);
|
|
380
380
|
db.exec("CREATE INDEX IF NOT EXISTS idx_milestone_commit_attr_milestone ON milestone_commit_attributions(milestone_id)");
|
|
381
381
|
}
|
|
382
|
+
export function applyMigrationV27ArtifactHash(db) {
|
|
383
|
+
ensureColumn(db, "artifacts", "content_hash", "ALTER TABLE artifacts ADD COLUMN content_hash TEXT DEFAULT NULL");
|
|
384
|
+
}
|
|
385
|
+
export function applyMigrationV28MemoryLastHitAt(db) {
|
|
386
|
+
ensureColumn(db, "memories", "last_hit_at", "ALTER TABLE memories ADD COLUMN last_hit_at TEXT DEFAULT NULL");
|
|
387
|
+
}
|
|
382
388
|
export function applyMigrationV22QualityGateRepair(db, hooks) {
|
|
383
389
|
const qgInfo = db.prepare("PRAGMA table_info(quality_gates)").all();
|
|
384
390
|
const taskIdCol = qgInfo.find((r) => r["name"] === "task_id");
|
|
@@ -13,6 +13,7 @@ import { isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
|
|
|
13
13
|
import { gsdRoot } from "./paths.js";
|
|
14
14
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
15
15
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
16
|
+
import { logWarning } from "./workflow-logger.js";
|
|
16
17
|
import { detectWorktreeName, } from "./worktree.js";
|
|
17
18
|
import { SLICE_BRANCH_RE, QUICK_BRANCH_RE, WORKFLOW_BRANCH_RE } from "./branch-patterns.js";
|
|
18
19
|
import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeBranchExists, nativeHasChanges, nativeAddAllWithExclusions, nativeHasStagedChanges, nativeCommit, nativeRmCached, nativeUpdateRef, nativeAddPaths, nativeResetSoft, nativeCommitSubject, _resetHasChangesCache, } from "./native-git-bridge.js";
|
|
@@ -535,14 +536,45 @@ export class GitServiceImpl {
|
|
|
535
536
|
if (keyFiles.length === 0)
|
|
536
537
|
return false;
|
|
537
538
|
const allExclusions = [...RUNTIME_EXCLUSION_PATHS, ...extraExclusions];
|
|
538
|
-
const
|
|
539
|
+
const normalized = keyFiles
|
|
539
540
|
.map(file => normalizeRepoRelativePath(this.basePath, file))
|
|
540
541
|
.filter((file) => file !== null)
|
|
541
|
-
.filter(file => !isExcludedScopedPath(file, allExclusions))
|
|
542
|
+
.filter(file => !isExcludedScopedPath(file, allExclusions));
|
|
543
|
+
// Drop entries that don't exist on disk. The LLM occasionally lists files
|
|
544
|
+
// it intended to write but didn't (or names them with wrong casing/path).
|
|
545
|
+
// Pre-`b304f738b` `git add -A` swallowed these silently; the scoped
|
|
546
|
+
// pathspec form passes each path explicitly, so a single bad entry made
|
|
547
|
+
// the whole commit fail (see #5500). Filter so valid paths still commit.
|
|
548
|
+
const missing = [];
|
|
549
|
+
const existing = [];
|
|
550
|
+
for (const path of normalized) {
|
|
551
|
+
if (existsSync(join(this.basePath, path))) {
|
|
552
|
+
existing.push(path);
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
missing.push(path);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (missing.length > 0) {
|
|
559
|
+
logWarning("engine", `scoped stage: dropping ${missing.length} non-existent keyFile(s) from task commit: ${missing.join(", ")}`, { file: "git-service.ts" });
|
|
560
|
+
}
|
|
561
|
+
const paths = Array.from(new Set(existing));
|
|
542
562
|
if (paths.length === 0)
|
|
543
563
|
return false;
|
|
544
|
-
|
|
545
|
-
|
|
564
|
+
try {
|
|
565
|
+
nativeAddPaths(this.basePath, paths);
|
|
566
|
+
return true;
|
|
567
|
+
}
|
|
568
|
+
catch (err) {
|
|
569
|
+
// Defense-in-depth: even after existence filtering, libgit2/git can
|
|
570
|
+
// still reject paths (gitignore matches, case-only differences on
|
|
571
|
+
// case-insensitive FS, submodule boundaries). Returning false lets
|
|
572
|
+
// autoCommit fall through to smartStage so the commit still goes out
|
|
573
|
+
// — restoring the resilience the unscoped path used to provide.
|
|
574
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
575
|
+
logWarning("engine", `scoped stage failed (${msg}); falling back to smartStage`, { file: "git-service.ts" });
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
546
578
|
}
|
|
547
579
|
/** Tracks whether runtime file cleanup has run this session. */
|
|
548
580
|
_runtimeFilesCleanedUp = false;
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
// intentionally independent store for cross-worktree claim races and is
|
|
23
23
|
// excluded from this invariant.
|
|
24
24
|
import { createRequire } from "node:module";
|
|
25
|
+
import { createHash } from "node:crypto";
|
|
25
26
|
import { existsSync, copyFileSync, mkdirSync, realpathSync } from "node:fs";
|
|
26
27
|
import { dirname } from "node:path";
|
|
27
28
|
import { GSDError, GSD_STALE_STATE } from "./errors.js";
|
|
@@ -36,7 +37,7 @@ import { rowToActiveDecision, rowToActiveRequirement, rowToDecision, rowToRequir
|
|
|
36
37
|
import { rowToGate } from "./db-gate-rows.js";
|
|
37
38
|
import { rowToArtifact, rowToMilestone } from "./db-milestone-artifact-rows.js";
|
|
38
39
|
import { backupDatabaseBeforeMigration } from "./db-migration-backup.js";
|
|
39
|
-
import { applyMigrationV2Artifacts, applyMigrationV3Memories, applyMigrationV4DecisionMadeBy, applyMigrationV5HierarchyTables, applyMigrationV6SliceSummaries, applyMigrationV7Dependencies, applyMigrationV8PlanningFields, applyMigrationV9Ordering, applyMigrationV10ReplanTrigger, applyMigrationV11TaskPlanning, applyMigrationV12QualityGates, applyMigrationV13HotPathIndexes, applyMigrationV14SliceDependencies, applyMigrationV15AuditTables, applyMigrationV16EscalationSource, applyMigrationV17TaskEscalation, applyMigrationV18MemorySources, applyMigrationV19MemoryFts, applyMigrationV20MemoryRelations, applyMigrationV21StructuredMemories, applyMigrationV22QualityGateRepair, applyMigrationV23MilestoneQueue, applyMigrationV26MilestoneCommitAttributions, } from "./db-migration-steps.js";
|
|
40
|
+
import { applyMigrationV2Artifacts, applyMigrationV3Memories, applyMigrationV4DecisionMadeBy, applyMigrationV5HierarchyTables, applyMigrationV6SliceSummaries, applyMigrationV7Dependencies, applyMigrationV8PlanningFields, applyMigrationV9Ordering, applyMigrationV10ReplanTrigger, applyMigrationV11TaskPlanning, applyMigrationV12QualityGates, applyMigrationV13HotPathIndexes, applyMigrationV14SliceDependencies, applyMigrationV15AuditTables, applyMigrationV16EscalationSource, applyMigrationV17TaskEscalation, applyMigrationV18MemorySources, applyMigrationV19MemoryFts, applyMigrationV20MemoryRelations, applyMigrationV21StructuredMemories, applyMigrationV22QualityGateRepair, applyMigrationV23MilestoneQueue, applyMigrationV26MilestoneCommitAttributions, applyMigrationV27ArtifactHash, applyMigrationV28MemoryLastHitAt, } from "./db-migration-steps.js";
|
|
40
41
|
import { isMemoriesFtsAvailableSchema, tryCreateMemoriesFtsSchema } from "./db-memory-fts-schema.js";
|
|
41
42
|
import { createDbOpenState } from "./db-open-state.js";
|
|
42
43
|
import { createRuntimeKvTableV25 } from "./db-runtime-kv-schema.js";
|
|
@@ -52,7 +53,7 @@ const providerLoader = createSqliteProviderLoader({
|
|
|
52
53
|
nodeVersion: process.versions.node,
|
|
53
54
|
writeStderr: (message) => process.stderr.write(message),
|
|
54
55
|
});
|
|
55
|
-
export const SCHEMA_VERSION =
|
|
56
|
+
export const SCHEMA_VERSION = 28;
|
|
56
57
|
function initSchema(db, fileBacked) {
|
|
57
58
|
if (fileBacked)
|
|
58
59
|
db.exec("PRAGMA journal_mode=WAL");
|
|
@@ -250,6 +251,14 @@ function migrateSchema(db) {
|
|
|
250
251
|
applyMigrationV26MilestoneCommitAttributions(db);
|
|
251
252
|
recordSchemaVersion(db, 26);
|
|
252
253
|
}
|
|
254
|
+
if (currentVersion < 27) {
|
|
255
|
+
applyMigrationV27ArtifactHash(db);
|
|
256
|
+
recordSchemaVersion(db, 27);
|
|
257
|
+
}
|
|
258
|
+
if (currentVersion < 28) {
|
|
259
|
+
applyMigrationV28MemoryLastHitAt(db);
|
|
260
|
+
recordSchemaVersion(db, 28);
|
|
261
|
+
}
|
|
253
262
|
db.exec("COMMIT");
|
|
254
263
|
}
|
|
255
264
|
catch (err) {
|
|
@@ -821,8 +830,9 @@ export function clearArtifacts() {
|
|
|
821
830
|
export function insertArtifact(a) {
|
|
822
831
|
if (!currentDb)
|
|
823
832
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
824
|
-
|
|
825
|
-
|
|
833
|
+
const contentHash = createHash("sha256").update(a.full_content).digest("hex");
|
|
834
|
+
currentDb.prepare(`INSERT OR REPLACE INTO artifacts (path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at, content_hash)
|
|
835
|
+
VALUES (:path, :artifact_type, :milestone_id, :slice_id, :task_id, :full_content, :imported_at, :content_hash)`).run({
|
|
826
836
|
":path": a.path,
|
|
827
837
|
":artifact_type": a.artifact_type,
|
|
828
838
|
":milestone_id": a.milestone_id,
|
|
@@ -830,6 +840,7 @@ export function insertArtifact(a) {
|
|
|
830
840
|
":task_id": a.task_id,
|
|
831
841
|
":full_content": a.full_content,
|
|
832
842
|
":imported_at": new Date().toISOString(),
|
|
843
|
+
":content_hash": contentHash,
|
|
833
844
|
});
|
|
834
845
|
}
|
|
835
846
|
export function insertMilestone(m) {
|
|
@@ -1521,6 +1532,13 @@ export function reconcileWorktreeDb(mainDbPath, worktreeDbPath) {
|
|
|
1521
1532
|
const hasEscalationAwaiting = wtTaskInfo.some((col) => col["name"] === "escalation_awaiting_review");
|
|
1522
1533
|
const hasEscalationArtifact = wtTaskInfo.some((col) => col["name"] === "escalation_artifact_path");
|
|
1523
1534
|
const hasEscalationOverride = wtTaskInfo.some((col) => col["name"] === "escalation_override_applied_at");
|
|
1535
|
+
const wtArtifactInfo = adapter.prepare("PRAGMA wt.table_info('artifacts')").all();
|
|
1536
|
+
const hasArtifactContentHash = wtArtifactInfo.some((col) => col["name"] === "content_hash");
|
|
1537
|
+
const wtMemoryInfo = adapter.prepare("PRAGMA wt.table_info('memories')").all();
|
|
1538
|
+
const hasMemoryScope = wtMemoryInfo.some((col) => col["name"] === "scope");
|
|
1539
|
+
const hasMemoryTags = wtMemoryInfo.some((col) => col["name"] === "tags");
|
|
1540
|
+
const hasMemoryStructuredFields = wtMemoryInfo.some((col) => col["name"] === "structured_fields");
|
|
1541
|
+
const hasMemoryLastHitAt = wtMemoryInfo.some((col) => col["name"] === "last_hit_at");
|
|
1524
1542
|
const decConf = adapter.prepare(`SELECT m.id FROM decisions m INNER JOIN wt.decisions w ON m.id = w.id WHERE m.decision != w.decision OR m.choice != w.choice OR m.rationale != w.rationale OR ${hasMadeBy ? "m.made_by != w.made_by" : "'agent' != 'agent'"} OR m.superseded_by IS NOT w.superseded_by`).all();
|
|
1525
1543
|
for (const row of decConf)
|
|
1526
1544
|
conflicts.push(`decision ${row["id"]}: modified in both`);
|
|
@@ -1553,12 +1571,17 @@ export function reconcileWorktreeDb(mainDbPath, worktreeDbPath) {
|
|
|
1553
1571
|
supporting_slices, validation, notes, full_content, superseded_by
|
|
1554
1572
|
FROM wt.requirements
|
|
1555
1573
|
`).run());
|
|
1574
|
+
// V27: preserve content_hash. If the worktree predates V27 (no column),
|
|
1575
|
+
// fall back to the main DB's existing hash so reconcile doesn't null
|
|
1576
|
+
// out integrity fingerprints on artifacts that were unchanged in wt.
|
|
1556
1577
|
merged.artifacts = countChanges(adapter.prepare(`
|
|
1557
1578
|
INSERT OR REPLACE INTO artifacts (
|
|
1558
|
-
path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at
|
|
1579
|
+
path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at, content_hash
|
|
1559
1580
|
)
|
|
1560
|
-
SELECT path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at
|
|
1561
|
-
|
|
1581
|
+
SELECT w.path, w.artifact_type, w.milestone_id, w.slice_id, w.task_id, w.full_content, w.imported_at,
|
|
1582
|
+
${hasArtifactContentHash ? "w.content_hash" : "m.content_hash"}
|
|
1583
|
+
FROM wt.artifacts w
|
|
1584
|
+
LEFT JOIN artifacts m ON m.path = w.path
|
|
1562
1585
|
`).run());
|
|
1563
1586
|
// Merge milestones — worktree may have updated status/planning fields.
|
|
1564
1587
|
// Never downgrade status: complete > active > pre-planning (#4372).
|
|
@@ -1656,15 +1679,25 @@ export function reconcileWorktreeDb(mainDbPath, worktreeDbPath) {
|
|
|
1656
1679
|
FROM wt.tasks w
|
|
1657
1680
|
LEFT JOIN tasks m ON m.milestone_id = w.milestone_id AND m.slice_id = w.slice_id AND m.id = w.id
|
|
1658
1681
|
`).run());
|
|
1659
|
-
// Merge memories — keep worktree-learned insights
|
|
1682
|
+
// Merge memories — keep worktree-learned insights.
|
|
1683
|
+
// V18 (scope, tags), V21 (structured_fields), V28 (last_hit_at): for each
|
|
1684
|
+
// column the wt may not yet have (older worktree DB), fall back to the
|
|
1685
|
+
// main DB's existing value via LEFT JOIN so reconcile never silently
|
|
1686
|
+
// resets these fields to defaults on rows that already had them.
|
|
1660
1687
|
merged.memories = countChanges(adapter.prepare(`
|
|
1661
1688
|
INSERT OR REPLACE INTO memories (
|
|
1662
1689
|
seq, id, category, content, confidence, source_unit_type, source_unit_id,
|
|
1663
|
-
created_at, updated_at, superseded_by, hit_count
|
|
1690
|
+
created_at, updated_at, superseded_by, hit_count,
|
|
1691
|
+
scope, tags, structured_fields, last_hit_at
|
|
1664
1692
|
)
|
|
1665
|
-
SELECT seq, id, category, content, confidence, source_unit_type, source_unit_id,
|
|
1666
|
-
created_at, updated_at, superseded_by, hit_count
|
|
1667
|
-
|
|
1693
|
+
SELECT w.seq, w.id, w.category, w.content, w.confidence, w.source_unit_type, w.source_unit_id,
|
|
1694
|
+
w.created_at, w.updated_at, w.superseded_by, w.hit_count,
|
|
1695
|
+
${hasMemoryScope ? "w.scope" : "COALESCE(m.scope, 'project')"},
|
|
1696
|
+
${hasMemoryTags ? "w.tags" : "COALESCE(m.tags, '[]')"},
|
|
1697
|
+
${hasMemoryStructuredFields ? "w.structured_fields" : "m.structured_fields"},
|
|
1698
|
+
${hasMemoryLastHitAt ? "w.last_hit_at" : "m.last_hit_at"}
|
|
1699
|
+
FROM wt.memories w
|
|
1700
|
+
LEFT JOIN memories m ON m.id = w.id
|
|
1668
1701
|
`).run());
|
|
1669
1702
|
// Merge verification evidence — append-only, use INSERT OR IGNORE to avoid duplicates
|
|
1670
1703
|
merged.verification_evidence = countChanges(adapter.prepare(`
|
|
@@ -2423,7 +2456,7 @@ export function updateMemoryContentRow(id, content, confidence, updatedAt) {
|
|
|
2423
2456
|
export function incrementMemoryHitCount(id, updatedAt) {
|
|
2424
2457
|
if (!currentDb)
|
|
2425
2458
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2426
|
-
currentDb.prepare("UPDATE memories SET hit_count = hit_count + 1, updated_at = :updated_at WHERE id = :id").run({ ":updated_at": updatedAt, ":id": id });
|
|
2459
|
+
currentDb.prepare("UPDATE memories SET hit_count = hit_count + 1, updated_at = :updated_at, last_hit_at = :last_hit_at WHERE id = :id").run({ ":updated_at": updatedAt, ":last_hit_at": updatedAt, ":id": id });
|
|
2427
2460
|
}
|
|
2428
2461
|
export function supersedeMemoryRow(oldId, newId, updatedAt) {
|
|
2429
2462
|
if (!currentDb)
|