gsd-pi 2.80.0-dev.e146beb20 → 2.80.0-dev.e6c48c3af
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/phases.js +59 -21
- package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +17 -2
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +1 -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 +84 -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 +75 -4
- package/dist/resources/extensions/gsd/clean-root-preflight.js +24 -6
- package/dist/resources/extensions/gsd/context-budget.js +37 -2
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
- package/dist/resources/extensions/gsd/db-base-schema.js +4 -2
- package/dist/resources/extensions/gsd/db-migration-steps.js +6 -0
- package/dist/resources/extensions/gsd/git-service.js +36 -4
- package/dist/resources/extensions/gsd/gsd-db.js +46 -13
- package/dist/resources/extensions/gsd/guided-flow.js +33 -4
- package/dist/resources/extensions/gsd/memory-store.js +69 -12
- package/dist/resources/extensions/gsd/migrate/command.js +40 -1
- package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
- package/dist/resources/extensions/gsd/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 +16 -13
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/dist/resources/extensions/gsd/quick.js +34 -2
- package/dist/resources/extensions/gsd/tools/context-mode-tool-result.js +15 -0
- package/dist/resources/extensions/gsd/tools/exec-search-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +3 -15
- package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
- package/dist/resources/extensions/gsd/tools/resume-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
- package/dist/resources/extensions/gsd/unit-context-composer.js +12 -3
- package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
- package/dist/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 +16 -16
- 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 +16 -16
- 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-tool-refresh.test.js +15 -0
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +12 -3
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +3 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +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 +12 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +13 -5
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
- package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +18 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +14 -3
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +3 -1
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
- package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +2 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +5 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +12 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +4 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +22 -7
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +18 -8
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/tui.ts +20 -8
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -2
- package/src/resources/extensions/gsd/auto/phases.ts +85 -35
- package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +22 -2
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +1 -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 +96 -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 +88 -4
- package/src/resources/extensions/gsd/clean-root-preflight.ts +32 -7
- package/src/resources/extensions/gsd/context-budget.ts +44 -2
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
- package/src/resources/extensions/gsd/db-base-schema.ts +4 -2
- package/src/resources/extensions/gsd/db-migration-steps.ts +8 -0
- package/src/resources/extensions/gsd/git-service.ts +46 -8
- package/src/resources/extensions/gsd/gsd-db.ts +50 -13
- package/src/resources/extensions/gsd/guided-flow.ts +49 -4
- package/src/resources/extensions/gsd/memory-store.ts +77 -12
- package/src/resources/extensions/gsd/migrate/command.ts +47 -1
- package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
- package/src/resources/extensions/gsd/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 +16 -13
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/src/resources/extensions/gsd/quick.ts +37 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +215 -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/auto-wrapup-inflight-guard.test.ts +166 -4
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +15 -6
- 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/custom-engine-loop-integration.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
- package/src/resources/extensions/gsd/tests/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 +239 -1
- 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/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/schema-v27-v28-sequence.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
- package/src/resources/extensions/gsd/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/resume-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
- package/src/resources/extensions/gsd/unit-context-composer.ts +19 -4
- package/src/resources/extensions/gsd/unit-runtime.ts +11 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +36 -15
- /package/dist/web/standalone/.next/static/{y73quA-XdLo9n41nxphjW → 4dQ9NTZJ8pEvFwBgDUX93}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{y73quA-XdLo9n41nxphjW → 4dQ9NTZJ8pEvFwBgDUX93}/_ssgManifest.js +0 -0
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* #2909: Adds a fast-path git status check before milestone completion merges.
|
|
5
5
|
* When the working tree is dirty the user is warned and changes are auto-stashed
|
|
6
|
-
* so the merge can proceed cleanly.
|
|
7
|
-
* restores the stashed changes.
|
|
6
|
+
* so the merge can proceed cleanly. After the merge completes, postflightPopStash
|
|
7
|
+
* restores the stashed changes and reports whether manual recovery is needed.
|
|
8
8
|
*
|
|
9
9
|
* Design constraints (from Trek-e approval):
|
|
10
10
|
* - Warn the user before stashing (no silent surprises)
|
|
11
11
|
* - git stash push / git stash pop only — no custom stash management layer
|
|
12
|
-
* - Stash/pop errors are logged but MUST NOT block the merge
|
|
12
|
+
* - Stash/pop errors are logged but MUST NOT block the merge itself
|
|
13
13
|
* - Fast-path status check — clean trees pay no extra cost
|
|
14
14
|
*/
|
|
15
15
|
|
|
@@ -27,6 +27,13 @@ export interface PreflightResult {
|
|
|
27
27
|
summary: string;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export interface PostflightResult {
|
|
31
|
+
restored: boolean;
|
|
32
|
+
needsManualRecovery: boolean;
|
|
33
|
+
message: string;
|
|
34
|
+
stashRef?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
30
37
|
function findPreflightStashRef(basePath: string, milestoneId: string, stashMarker?: string): string | null {
|
|
31
38
|
const markerPrefix = `gsd-preflight-stash:${milestoneId}:`;
|
|
32
39
|
let fallbackRef: string | null = null;
|
|
@@ -112,14 +119,15 @@ export function preflightCleanRoot(
|
|
|
112
119
|
*
|
|
113
120
|
* Only called when preflightCleanRoot returned stashPushed=true.
|
|
114
121
|
* Any pop error (e.g. conflict) is logged and notified but does NOT throw —
|
|
115
|
-
* the merge already completed successfully.
|
|
122
|
+
* the merge already completed successfully. Callers must treat
|
|
123
|
+
* needsManualRecovery=true as a dirty workspace stop, not a clean completion.
|
|
116
124
|
*/
|
|
117
125
|
export function postflightPopStash(
|
|
118
126
|
basePath: string,
|
|
119
127
|
milestoneId: string,
|
|
120
128
|
stashMarker: string | undefined,
|
|
121
129
|
notify: (message: string, level: "info" | "warning" | "error") => void,
|
|
122
|
-
):
|
|
130
|
+
): PostflightResult {
|
|
123
131
|
let stashRef: string | null = null;
|
|
124
132
|
try {
|
|
125
133
|
stashRef = findPreflightStashRef(basePath, milestoneId, stashMarker);
|
|
@@ -127,7 +135,11 @@ export function postflightPopStash(
|
|
|
127
135
|
const msg = `No matching GSD preflight stash found for milestone ${milestoneId}; leaving stash list untouched.`;
|
|
128
136
|
logWarning("preflight", msg);
|
|
129
137
|
notify(msg, "warning");
|
|
130
|
-
return
|
|
138
|
+
return {
|
|
139
|
+
restored: false,
|
|
140
|
+
needsManualRecovery: true,
|
|
141
|
+
message: msg,
|
|
142
|
+
};
|
|
131
143
|
}
|
|
132
144
|
execFileSync("git", ["stash", "pop", stashRef], {
|
|
133
145
|
cwd: basePath,
|
|
@@ -135,7 +147,14 @@ export function postflightPopStash(
|
|
|
135
147
|
encoding: "utf-8",
|
|
136
148
|
env: GIT_NO_PROMPT_ENV,
|
|
137
149
|
});
|
|
138
|
-
|
|
150
|
+
const msg = `Restored stashed changes after milestone ${milestoneId} merge.`;
|
|
151
|
+
notify(msg, "info");
|
|
152
|
+
return {
|
|
153
|
+
restored: true,
|
|
154
|
+
needsManualRecovery: false,
|
|
155
|
+
message: msg,
|
|
156
|
+
stashRef,
|
|
157
|
+
};
|
|
139
158
|
} catch (err) {
|
|
140
159
|
// Pop conflicts mean the merged code collides with the stashed changes.
|
|
141
160
|
// Log a warning — the user needs to resolve manually, but the merge succeeded.
|
|
@@ -145,5 +164,11 @@ export function postflightPopStash(
|
|
|
145
164
|
const msg = `git stash pop ${stashRef ?? ""}`.trim() + ` failed after merge of milestone ${milestoneId}: ${err instanceof Error ? err.message : String(err)}. ${restoreHint}`;
|
|
146
165
|
logWarning("preflight", msg);
|
|
147
166
|
notify(msg, "warning");
|
|
167
|
+
return {
|
|
168
|
+
restored: false,
|
|
169
|
+
needsManualRecovery: true,
|
|
170
|
+
message: msg,
|
|
171
|
+
...(stashRef ? { stashRef } : {}),
|
|
172
|
+
};
|
|
148
173
|
}
|
|
149
174
|
}
|
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
* @see D001 (module location), D002 (200K fallback), D003 (section-boundary truncation)
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
type TokenProvider,
|
|
13
|
+
getCharsPerToken,
|
|
14
|
+
isAccurateCountingAvailable,
|
|
15
|
+
countTokensSync,
|
|
16
|
+
} from "./token-counter.js";
|
|
12
17
|
|
|
13
18
|
// ─── Budget ratio constants ──────────────────────────────────────────────────
|
|
14
19
|
// Percentages of total context window allocated to each budget category.
|
|
@@ -32,6 +37,24 @@ const DEFAULT_CONTEXT_WINDOW = 200_000;
|
|
|
32
37
|
/** Conservative effective context for Claude Code subscription routing (#4676) */
|
|
33
38
|
const CLAUDE_CODE_EFFECTIVE_CONTEXT_WINDOW = 200_000;
|
|
34
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Cached empirical chars-per-token from a tiktoken probe, keyed by provider.
|
|
42
|
+
* countTokensSync's fallback path is provider-aware, so we cache per-provider
|
|
43
|
+
* to preserve that distinction once the encoder warms. The cl100k_base encoder
|
|
44
|
+
* itself gives a stable ratio for ASCII English so a single probe per provider
|
|
45
|
+
* key is sufficient. Empty map means "not yet probed" or "encoder unavailable".
|
|
46
|
+
*/
|
|
47
|
+
const _empiricalCharsPerTokenByProvider = new Map<string, number>();
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Test hook — clears the empirical chars-per-token cache so test cases that
|
|
51
|
+
* assert against the static char-ratio fallback aren't polluted by a prior
|
|
52
|
+
* tiktoken-warmed run in the same process. Production code must not call this.
|
|
53
|
+
*/
|
|
54
|
+
export function _resetEmpiricalCacheForTest(): void {
|
|
55
|
+
_empiricalCharsPerTokenByProvider.clear();
|
|
56
|
+
}
|
|
57
|
+
|
|
35
58
|
/** Percentage of context consumed before suggesting a continue-here checkpoint */
|
|
36
59
|
const CONTINUE_THRESHOLD_PERCENT = 70;
|
|
37
60
|
|
|
@@ -101,7 +124,26 @@ export interface MinimalPreferences {
|
|
|
101
124
|
export function computeBudgets(contextWindow: number, provider?: TokenProvider): BudgetAllocation {
|
|
102
125
|
const effectiveWindow = contextWindow > 0 ? contextWindow : DEFAULT_CONTEXT_WINDOW;
|
|
103
126
|
const charsPerToken = provider ? getCharsPerToken(provider) : CHARS_PER_TOKEN;
|
|
104
|
-
|
|
127
|
+
|
|
128
|
+
// Prefer the tiktoken encoder for total-char estimation when it has been
|
|
129
|
+
// warmed (initTokenCounter resolved). The cl100k_base ratio is stable for
|
|
130
|
+
// ASCII English, so probe once per provider and cache — computeBudgets is
|
|
131
|
+
// called multiple times per prompt build and the probe encode is otherwise
|
|
132
|
+
// wasted work.
|
|
133
|
+
let totalChars: number;
|
|
134
|
+
if (isAccurateCountingAvailable()) {
|
|
135
|
+
const providerKey = provider ?? "__default__";
|
|
136
|
+
let empirical = _empiricalCharsPerTokenByProvider.get(providerKey);
|
|
137
|
+
if (empirical === undefined) {
|
|
138
|
+
const probe = "the quick brown fox jumps over the lazy dog ".repeat(64);
|
|
139
|
+
const probeTokens = countTokensSync(probe, provider);
|
|
140
|
+
empirical = probeTokens > 0 ? probe.length / probeTokens : charsPerToken;
|
|
141
|
+
_empiricalCharsPerTokenByProvider.set(providerKey, empirical);
|
|
142
|
+
}
|
|
143
|
+
totalChars = effectiveWindow * empirical;
|
|
144
|
+
} else {
|
|
145
|
+
totalChars = effectiveWindow * charsPerToken;
|
|
146
|
+
}
|
|
105
147
|
|
|
106
148
|
return {
|
|
107
149
|
summaryBudgetChars: Math.floor(totalChars * SUMMARY_RATIO),
|
|
@@ -359,6 +359,47 @@ export function markCanceled(dispatchId: number, reason: string): void {
|
|
|
359
359
|
).run({ ":id": dispatchId, ":ended_at": now, ":reason": reason });
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Best-effort signal/crash cleanup: cancel the latest active dispatch owned by
|
|
364
|
+
* a worker when the process is exiting before the normal loop can settle it.
|
|
365
|
+
*/
|
|
366
|
+
export function markLatestActiveForWorkerCanceled(workerId: string, reason: string): boolean {
|
|
367
|
+
if (!isDbAvailable()) return false;
|
|
368
|
+
const now = new Date().toISOString();
|
|
369
|
+
const db = _getAdapter()!;
|
|
370
|
+
const result = transaction(() => {
|
|
371
|
+
return db.prepare(
|
|
372
|
+
`UPDATE unit_dispatches
|
|
373
|
+
SET status = 'canceled', ended_at = :ended_at, exit_reason = :reason
|
|
374
|
+
WHERE id = (
|
|
375
|
+
SELECT id FROM unit_dispatches
|
|
376
|
+
WHERE worker_id = :worker_id
|
|
377
|
+
AND status IN ('pending','claimed','running')
|
|
378
|
+
ORDER BY id DESC
|
|
379
|
+
LIMIT 1
|
|
380
|
+
)`,
|
|
381
|
+
).run({
|
|
382
|
+
":ended_at": now,
|
|
383
|
+
":reason": reason,
|
|
384
|
+
":worker_id": workerId,
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
const changes =
|
|
388
|
+
typeof (result as { changes?: unknown }).changes === "number"
|
|
389
|
+
? (result as { changes: number }).changes
|
|
390
|
+
: 0;
|
|
391
|
+
if (changes <= 0) return false;
|
|
392
|
+
insertAuditEvent({
|
|
393
|
+
eventId: randomUUID(),
|
|
394
|
+
traceId: workerId,
|
|
395
|
+
category: "orchestration",
|
|
396
|
+
type: "dispatch-canceled",
|
|
397
|
+
ts: now,
|
|
398
|
+
payload: { workerId, reason },
|
|
399
|
+
});
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
|
|
362
403
|
/**
|
|
363
404
|
* Fetch the most recent N dispatches for a unit. Used by recordDispatchClaim
|
|
364
405
|
* callers to compute attempt_n and by detect-stuck.ts (B3) to consult
|
|
@@ -57,7 +57,8 @@ export function createBaseSchemaObjects(db: DbAdapter, hooks: BaseSchemaHooks):
|
|
|
57
57
|
slice_id TEXT DEFAULT NULL,
|
|
58
58
|
task_id TEXT DEFAULT NULL,
|
|
59
59
|
full_content TEXT NOT NULL DEFAULT '',
|
|
60
|
-
imported_at TEXT NOT NULL DEFAULT ''
|
|
60
|
+
imported_at TEXT NOT NULL DEFAULT '',
|
|
61
|
+
content_hash TEXT DEFAULT NULL
|
|
61
62
|
)
|
|
62
63
|
`);
|
|
63
64
|
|
|
@@ -76,7 +77,8 @@ export function createBaseSchemaObjects(db: DbAdapter, hooks: BaseSchemaHooks):
|
|
|
76
77
|
hit_count INTEGER NOT NULL DEFAULT 0,
|
|
77
78
|
scope TEXT NOT NULL DEFAULT 'project',
|
|
78
79
|
tags TEXT NOT NULL DEFAULT '[]',
|
|
79
|
-
structured_fields TEXT DEFAULT NULL
|
|
80
|
+
structured_fields TEXT DEFAULT NULL,
|
|
81
|
+
last_hit_at TEXT DEFAULT NULL
|
|
80
82
|
)
|
|
81
83
|
`);
|
|
82
84
|
|
|
@@ -416,6 +416,14 @@ export function applyMigrationV26MilestoneCommitAttributions(db: DbAdapter): voi
|
|
|
416
416
|
db.exec("CREATE INDEX IF NOT EXISTS idx_milestone_commit_attr_milestone ON milestone_commit_attributions(milestone_id)");
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
+
export function applyMigrationV27ArtifactHash(db: DbAdapter): void {
|
|
420
|
+
ensureColumn(db, "artifacts", "content_hash", "ALTER TABLE artifacts ADD COLUMN content_hash TEXT DEFAULT NULL");
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export function applyMigrationV28MemoryLastHitAt(db: DbAdapter): void {
|
|
424
|
+
ensureColumn(db, "memories", "last_hit_at", "ALTER TABLE memories ADD COLUMN last_hit_at TEXT DEFAULT NULL");
|
|
425
|
+
}
|
|
426
|
+
|
|
419
427
|
export interface MigrationV22Hooks {
|
|
420
428
|
copyQualityGateRowsToRepairedTable(db: DbAdapter): void;
|
|
421
429
|
}
|
|
@@ -14,6 +14,7 @@ import { isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
|
|
|
14
14
|
import { gsdRoot } from "./paths.js";
|
|
15
15
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
16
16
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
17
|
+
import { logWarning } from "./workflow-logger.js";
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
import {
|
|
@@ -722,16 +723,53 @@ export class GitServiceImpl {
|
|
|
722
723
|
if (keyFiles.length === 0) return false;
|
|
723
724
|
|
|
724
725
|
const allExclusions = [...RUNTIME_EXCLUSION_PATHS, ...extraExclusions];
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
726
|
+
const normalized = keyFiles
|
|
727
|
+
.map(file => normalizeRepoRelativePath(this.basePath, file))
|
|
728
|
+
.filter((file): file is string => file !== null)
|
|
729
|
+
.filter(file => !isExcludedScopedPath(file, allExclusions));
|
|
730
|
+
|
|
731
|
+
// Drop entries that don't exist on disk. The LLM occasionally lists files
|
|
732
|
+
// it intended to write but didn't (or names them with wrong casing/path).
|
|
733
|
+
// Pre-`b304f738b` `git add -A` swallowed these silently; the scoped
|
|
734
|
+
// pathspec form passes each path explicitly, so a single bad entry made
|
|
735
|
+
// the whole commit fail (see #5500). Filter so valid paths still commit.
|
|
736
|
+
const missing: string[] = [];
|
|
737
|
+
const existing: string[] = [];
|
|
738
|
+
for (const path of normalized) {
|
|
739
|
+
if (existsSync(join(this.basePath, path))) {
|
|
740
|
+
existing.push(path);
|
|
741
|
+
} else {
|
|
742
|
+
missing.push(path);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
if (missing.length > 0) {
|
|
746
|
+
logWarning(
|
|
747
|
+
"engine",
|
|
748
|
+
`scoped stage: dropping ${missing.length} non-existent keyFile(s) from task commit: ${missing.join(", ")}`,
|
|
749
|
+
{ file: "git-service.ts" },
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const paths = Array.from(new Set(existing));
|
|
731
754
|
if (paths.length === 0) return false;
|
|
732
755
|
|
|
733
|
-
|
|
734
|
-
|
|
756
|
+
try {
|
|
757
|
+
nativeAddPaths(this.basePath, paths);
|
|
758
|
+
return true;
|
|
759
|
+
} catch (err) {
|
|
760
|
+
// Defense-in-depth: even after existence filtering, libgit2/git can
|
|
761
|
+
// still reject paths (gitignore matches, case-only differences on
|
|
762
|
+
// case-insensitive FS, submodule boundaries). Returning false lets
|
|
763
|
+
// autoCommit fall through to smartStage so the commit still goes out
|
|
764
|
+
// — restoring the resilience the unscoped path used to provide.
|
|
765
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
766
|
+
logWarning(
|
|
767
|
+
"engine",
|
|
768
|
+
`scoped stage failed (${msg}); falling back to smartStage`,
|
|
769
|
+
{ file: "git-service.ts" },
|
|
770
|
+
);
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
735
773
|
}
|
|
736
774
|
|
|
737
775
|
/** Tracks whether runtime file cleanup has run this session. */
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
// excluded from this invariant.
|
|
24
24
|
|
|
25
25
|
import { createRequire } from "node:module";
|
|
26
|
+
import { createHash } from "node:crypto";
|
|
26
27
|
import { existsSync, copyFileSync, mkdirSync, realpathSync } from "node:fs";
|
|
27
28
|
import { dirname } from "node:path";
|
|
28
29
|
import type { Decision, Requirement, GateRow, GateId, GateScope, GateStatus, GateVerdict } from "./types.js";
|
|
@@ -78,6 +79,8 @@ import {
|
|
|
78
79
|
applyMigrationV22QualityGateRepair,
|
|
79
80
|
applyMigrationV23MilestoneQueue,
|
|
80
81
|
applyMigrationV26MilestoneCommitAttributions,
|
|
82
|
+
applyMigrationV27ArtifactHash,
|
|
83
|
+
applyMigrationV28MemoryLastHitAt,
|
|
81
84
|
} from "./db-migration-steps.js";
|
|
82
85
|
import { isMemoriesFtsAvailableSchema, tryCreateMemoriesFtsSchema } from "./db-memory-fts-schema.js";
|
|
83
86
|
import { createDbOpenState, type DbOpenPhase } from "./db-open-state.js";
|
|
@@ -106,7 +109,7 @@ const providerLoader = createSqliteProviderLoader({
|
|
|
106
109
|
writeStderr: (message: string) => process.stderr.write(message),
|
|
107
110
|
});
|
|
108
111
|
|
|
109
|
-
export const SCHEMA_VERSION =
|
|
112
|
+
export const SCHEMA_VERSION = 28;
|
|
110
113
|
|
|
111
114
|
function initSchema(db: DbAdapter, fileBacked: boolean): void {
|
|
112
115
|
if (fileBacked) db.exec("PRAGMA journal_mode=WAL");
|
|
@@ -335,6 +338,16 @@ function migrateSchema(db: DbAdapter): void {
|
|
|
335
338
|
recordSchemaVersion(db, 26);
|
|
336
339
|
}
|
|
337
340
|
|
|
341
|
+
if (currentVersion < 27) {
|
|
342
|
+
applyMigrationV27ArtifactHash(db);
|
|
343
|
+
recordSchemaVersion(db, 27);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (currentVersion < 28) {
|
|
347
|
+
applyMigrationV28MemoryLastHitAt(db);
|
|
348
|
+
recordSchemaVersion(db, 28);
|
|
349
|
+
}
|
|
350
|
+
|
|
338
351
|
db.exec("COMMIT");
|
|
339
352
|
} catch (err) {
|
|
340
353
|
db.exec("ROLLBACK");
|
|
@@ -913,9 +926,10 @@ export function insertArtifact(a: {
|
|
|
913
926
|
full_content: string;
|
|
914
927
|
}): void {
|
|
915
928
|
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
929
|
+
const contentHash = createHash("sha256").update(a.full_content).digest("hex");
|
|
916
930
|
currentDb.prepare(
|
|
917
|
-
`INSERT OR REPLACE INTO artifacts (path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at)
|
|
918
|
-
VALUES (:path, :artifact_type, :milestone_id, :slice_id, :task_id, :full_content, :imported_at)`,
|
|
931
|
+
`INSERT OR REPLACE INTO artifacts (path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at, content_hash)
|
|
932
|
+
VALUES (:path, :artifact_type, :milestone_id, :slice_id, :task_id, :full_content, :imported_at, :content_hash)`,
|
|
919
933
|
).run({
|
|
920
934
|
":path": a.path,
|
|
921
935
|
":artifact_type": a.artifact_type,
|
|
@@ -924,6 +938,7 @@ export function insertArtifact(a: {
|
|
|
924
938
|
":task_id": a.task_id,
|
|
925
939
|
":full_content": a.full_content,
|
|
926
940
|
":imported_at": new Date().toISOString(),
|
|
941
|
+
":content_hash": contentHash,
|
|
927
942
|
});
|
|
928
943
|
}
|
|
929
944
|
|
|
@@ -1795,6 +1810,13 @@ export function reconcileWorktreeDb(
|
|
|
1795
1810
|
const hasEscalationAwaiting = wtTaskInfo.some((col) => col["name"] === "escalation_awaiting_review");
|
|
1796
1811
|
const hasEscalationArtifact = wtTaskInfo.some((col) => col["name"] === "escalation_artifact_path");
|
|
1797
1812
|
const hasEscalationOverride = wtTaskInfo.some((col) => col["name"] === "escalation_override_applied_at");
|
|
1813
|
+
const wtArtifactInfo = adapter.prepare("PRAGMA wt.table_info('artifacts')").all();
|
|
1814
|
+
const hasArtifactContentHash = wtArtifactInfo.some((col) => col["name"] === "content_hash");
|
|
1815
|
+
const wtMemoryInfo = adapter.prepare("PRAGMA wt.table_info('memories')").all();
|
|
1816
|
+
const hasMemoryScope = wtMemoryInfo.some((col) => col["name"] === "scope");
|
|
1817
|
+
const hasMemoryTags = wtMemoryInfo.some((col) => col["name"] === "tags");
|
|
1818
|
+
const hasMemoryStructuredFields = wtMemoryInfo.some((col) => col["name"] === "structured_fields");
|
|
1819
|
+
const hasMemoryLastHitAt = wtMemoryInfo.some((col) => col["name"] === "last_hit_at");
|
|
1798
1820
|
|
|
1799
1821
|
const decConf = adapter.prepare(
|
|
1800
1822
|
`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 ${
|
|
@@ -1842,12 +1864,17 @@ export function reconcileWorktreeDb(
|
|
|
1842
1864
|
FROM wt.requirements
|
|
1843
1865
|
`).run());
|
|
1844
1866
|
|
|
1867
|
+
// V27: preserve content_hash. If the worktree predates V27 (no column),
|
|
1868
|
+
// fall back to the main DB's existing hash so reconcile doesn't null
|
|
1869
|
+
// out integrity fingerprints on artifacts that were unchanged in wt.
|
|
1845
1870
|
merged.artifacts = countChanges(adapter.prepare(`
|
|
1846
1871
|
INSERT OR REPLACE INTO artifacts (
|
|
1847
|
-
path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at
|
|
1872
|
+
path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at, content_hash
|
|
1848
1873
|
)
|
|
1849
|
-
SELECT path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at
|
|
1850
|
-
|
|
1874
|
+
SELECT w.path, w.artifact_type, w.milestone_id, w.slice_id, w.task_id, w.full_content, w.imported_at,
|
|
1875
|
+
${hasArtifactContentHash ? "w.content_hash" : "m.content_hash"}
|
|
1876
|
+
FROM wt.artifacts w
|
|
1877
|
+
LEFT JOIN artifacts m ON m.path = w.path
|
|
1851
1878
|
`).run());
|
|
1852
1879
|
|
|
1853
1880
|
// Merge milestones — worktree may have updated status/planning fields.
|
|
@@ -1949,15 +1976,25 @@ export function reconcileWorktreeDb(
|
|
|
1949
1976
|
LEFT JOIN tasks m ON m.milestone_id = w.milestone_id AND m.slice_id = w.slice_id AND m.id = w.id
|
|
1950
1977
|
`).run());
|
|
1951
1978
|
|
|
1952
|
-
// Merge memories — keep worktree-learned insights
|
|
1979
|
+
// Merge memories — keep worktree-learned insights.
|
|
1980
|
+
// V18 (scope, tags), V21 (structured_fields), V28 (last_hit_at): for each
|
|
1981
|
+
// column the wt may not yet have (older worktree DB), fall back to the
|
|
1982
|
+
// main DB's existing value via LEFT JOIN so reconcile never silently
|
|
1983
|
+
// resets these fields to defaults on rows that already had them.
|
|
1953
1984
|
merged.memories = countChanges(adapter.prepare(`
|
|
1954
1985
|
INSERT OR REPLACE INTO memories (
|
|
1955
1986
|
seq, id, category, content, confidence, source_unit_type, source_unit_id,
|
|
1956
|
-
created_at, updated_at, superseded_by, hit_count
|
|
1987
|
+
created_at, updated_at, superseded_by, hit_count,
|
|
1988
|
+
scope, tags, structured_fields, last_hit_at
|
|
1957
1989
|
)
|
|
1958
|
-
SELECT seq, id, category, content, confidence, source_unit_type, source_unit_id,
|
|
1959
|
-
created_at, updated_at, superseded_by, hit_count
|
|
1960
|
-
|
|
1990
|
+
SELECT w.seq, w.id, w.category, w.content, w.confidence, w.source_unit_type, w.source_unit_id,
|
|
1991
|
+
w.created_at, w.updated_at, w.superseded_by, w.hit_count,
|
|
1992
|
+
${hasMemoryScope ? "w.scope" : "COALESCE(m.scope, 'project')"},
|
|
1993
|
+
${hasMemoryTags ? "w.tags" : "COALESCE(m.tags, '[]')"},
|
|
1994
|
+
${hasMemoryStructuredFields ? "w.structured_fields" : "m.structured_fields"},
|
|
1995
|
+
${hasMemoryLastHitAt ? "w.last_hit_at" : "m.last_hit_at"}
|
|
1996
|
+
FROM wt.memories w
|
|
1997
|
+
LEFT JOIN memories m ON m.id = w.id
|
|
1961
1998
|
`).run());
|
|
1962
1999
|
|
|
1963
2000
|
// Merge verification evidence — append-only, use INSERT OR IGNORE to avoid duplicates
|
|
@@ -3062,8 +3099,8 @@ export function updateMemoryContentRow(
|
|
|
3062
3099
|
export function incrementMemoryHitCount(id: string, updatedAt: string): void {
|
|
3063
3100
|
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3064
3101
|
currentDb.prepare(
|
|
3065
|
-
"UPDATE memories SET hit_count = hit_count + 1, updated_at = :updated_at WHERE id = :id",
|
|
3066
|
-
).run({ ":updated_at": updatedAt, ":id": id });
|
|
3102
|
+
"UPDATE memories SET hit_count = hit_count + 1, updated_at = :updated_at, last_hit_at = :last_hit_at WHERE id = :id",
|
|
3103
|
+
).run({ ":updated_at": updatedAt, ":last_hit_at": updatedAt, ":id": id });
|
|
3067
3104
|
}
|
|
3068
3105
|
|
|
3069
3106
|
export function supersedeMemoryRow(oldId: string, newId: string, updatedAt: string): void {
|
|
@@ -87,6 +87,34 @@ import { logWarning } from "./workflow-logger.js";
|
|
|
87
87
|
import { deleteRuntimeKv } from "./db/runtime-kv.js";
|
|
88
88
|
import { PAUSED_SESSION_KV_KEY } from "./interrupted-session.js";
|
|
89
89
|
|
|
90
|
+
type AutoStartOptions = Parameters<typeof startAutoDetached>[4];
|
|
91
|
+
type AutoStartLauncher = typeof startAutoDetached;
|
|
92
|
+
|
|
93
|
+
function scheduleAutoStartAfterIdle(
|
|
94
|
+
ctx: ExtensionCommandContext,
|
|
95
|
+
pi: ExtensionAPI,
|
|
96
|
+
basePath: string,
|
|
97
|
+
verboseMode: boolean,
|
|
98
|
+
options?: AutoStartOptions,
|
|
99
|
+
launch: AutoStartLauncher = startAutoDetached,
|
|
100
|
+
): void {
|
|
101
|
+
const waitForIdle =
|
|
102
|
+
typeof (ctx as { waitForIdle?: unknown }).waitForIdle === "function"
|
|
103
|
+
? ctx.waitForIdle.bind(ctx)
|
|
104
|
+
: async () => {};
|
|
105
|
+
void waitForIdle()
|
|
106
|
+
.then(() => {
|
|
107
|
+
setTimeout(() => launch(ctx, pi, basePath, verboseMode, options), 0);
|
|
108
|
+
})
|
|
109
|
+
.catch((err) => {
|
|
110
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
111
|
+
ctx.ui.notify(`Auto-start failed while waiting for the prior turn to settle: ${message}`, "error");
|
|
112
|
+
logWarning("guided", `auto-start idle wait failed: ${message}`);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const _scheduleAutoStartAfterIdleForTest = scheduleAutoStartAfterIdle;
|
|
117
|
+
|
|
90
118
|
// ─── Scope-based validator wrappers ──────────────────────────────────────────
|
|
91
119
|
// These thin wrappers accept a MilestoneScope so callers that already hold a
|
|
92
120
|
// pinned scope never have to re-derive (basePath, milestoneId) separately.
|
|
@@ -446,7 +474,7 @@ async function dispatchNextDeepProjectSetupStage(entry: PendingDeepProjectSetupE
|
|
|
446
474
|
|
|
447
475
|
if (!hasPendingDeepStage(prefs, entry.basePath)) {
|
|
448
476
|
pendingDeepProjectSetupMap.delete(entry.basePath);
|
|
449
|
-
|
|
477
|
+
scheduleAutoStartAfterIdle(entry.ctx, entry.pi, entry.basePath, false, { step: entry.step });
|
|
450
478
|
return true;
|
|
451
479
|
}
|
|
452
480
|
|
|
@@ -479,7 +507,7 @@ async function dispatchNextDeepProjectSetupStage(entry: PendingDeepProjectSetupE
|
|
|
479
507
|
entry.ctx.ui.notify(result.reason, result.level);
|
|
480
508
|
} else if (hasPendingDeepStage(prefs, entry.basePath)) {
|
|
481
509
|
pendingDeepProjectSetupMap.delete(entry.basePath);
|
|
482
|
-
|
|
510
|
+
scheduleAutoStartAfterIdle(entry.ctx, entry.pi, entry.basePath, false, { step: entry.step });
|
|
483
511
|
return true;
|
|
484
512
|
}
|
|
485
513
|
return false;
|
|
@@ -487,7 +515,7 @@ async function dispatchNextDeepProjectSetupStage(entry: PendingDeepProjectSetupE
|
|
|
487
515
|
|
|
488
516
|
if (!USER_DRIVEN_DEEP_SETUP_UNITS.has(result.unitType)) {
|
|
489
517
|
pendingDeepProjectSetupMap.delete(entry.basePath);
|
|
490
|
-
|
|
518
|
+
scheduleAutoStartAfterIdle(entry.ctx, entry.pi, entry.basePath, false, { step: entry.step });
|
|
491
519
|
return true;
|
|
492
520
|
}
|
|
493
521
|
|
|
@@ -693,7 +721,7 @@ export function checkAutoStartAfterDiscuss(): boolean {
|
|
|
693
721
|
|
|
694
722
|
pendingAutoStartMap.delete(basePath);
|
|
695
723
|
ctx.ui.notify(`Milestone ${milestoneId} ready.`, "success");
|
|
696
|
-
|
|
724
|
+
scheduleAutoStartAfterIdle(ctx, pi, basePath, false, { step });
|
|
697
725
|
return true;
|
|
698
726
|
}
|
|
699
727
|
|
|
@@ -2058,6 +2086,23 @@ export async function showSmartEntry(
|
|
|
2058
2086
|
}
|
|
2059
2087
|
}
|
|
2060
2088
|
|
|
2089
|
+
if (interrupted.classification !== "recoverable") {
|
|
2090
|
+
try {
|
|
2091
|
+
const { autoImportMarkdownHierarchyIfDbMismatch } = await import("./migration-auto-check.js");
|
|
2092
|
+
const result = await autoImportMarkdownHierarchyIfDbMismatch(basePath);
|
|
2093
|
+
if (result.action === "imported") {
|
|
2094
|
+
ctx.ui.notify(
|
|
2095
|
+
`Recovered migrated planning state into gsd.db (${result.reason}): ${result.afterDb.milestones} milestone(s), ${result.afterDb.slices} slice(s), ${result.afterDb.tasks} task(s).`,
|
|
2096
|
+
"info",
|
|
2097
|
+
);
|
|
2098
|
+
}
|
|
2099
|
+
} catch (err) {
|
|
2100
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2101
|
+
ctx.ui.notify(`GSD could not auto-import existing planning state into gsd.db: ${message}`, "warning");
|
|
2102
|
+
logWarning("guided", `planning state auto-import failed: ${message}`, { file: "guided-flow.ts" });
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2061
2106
|
// Always derive from the project root — the assessment may have derived
|
|
2062
2107
|
// state from a worktree path that was cleaned up in the stale branch above.
|
|
2063
2108
|
const state = await deriveState(basePath);
|