gsd-pi 2.82.0-dev.9d5798940 → 2.82.0-dev.dfbc5f58f
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 +2 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +7 -0
- package/dist/resources/extensions/gsd/auto/infra-errors.js +9 -3
- package/dist/resources/extensions/gsd/auto/loop.js +5 -5
- package/dist/resources/extensions/gsd/auto/orchestrator.js +11 -0
- package/dist/resources/extensions/gsd/auto/phases.js +8 -1
- package/dist/resources/extensions/gsd/auto/workflow-memory-pressure.js +12 -0
- package/dist/resources/extensions/gsd/auto-model-selection.js +2 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +1 -1
- package/dist/resources/extensions/gsd/auto-start.js +78 -9
- package/dist/resources/extensions/gsd/auto-worktree.js +15 -1
- package/dist/resources/extensions/gsd/auto.js +30 -3
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +9 -8
- package/dist/resources/extensions/gsd/bootstrap/subagent-input.js +5 -2
- package/dist/resources/extensions/gsd/crash-recovery.js +31 -5
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +1 -0
- package/dist/resources/extensions/gsd/dispatch-guard.js +2 -2
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +28 -11
- package/dist/resources/extensions/gsd/doctor.js +2 -28
- package/dist/resources/extensions/gsd/git-service.js +39 -1
- package/dist/resources/extensions/gsd/gsd-db.js +1 -0
- package/dist/resources/extensions/gsd/guided-flow.js +6 -0
- package/dist/resources/extensions/gsd/migrate/parsers.js +10 -0
- package/dist/resources/extensions/gsd/native-git-bridge.js +40 -9
- package/dist/resources/extensions/gsd/post-execution-checks.js +73 -2
- package/dist/resources/extensions/gsd/pre-execution-checks.js +28 -1
- package/dist/resources/extensions/gsd/prompt-loader.js +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +3 -3
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/dist/resources/extensions/gsd/status-guards.js +4 -0
- package/dist/resources/extensions/gsd/templates/plan.md +8 -5
- package/dist/resources/extensions/gsd/templates/task-plan.md +4 -2
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -8
- package/dist/resources/extensions/gsd/tools/complete-slice.js +6 -8
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +7 -1
- package/dist/resources/extensions/gsd/tools/plan-slice.js +88 -14
- package/dist/resources/extensions/gsd/validation.js +23 -1
- package/dist/resources/extensions/gsd/verification-gate.js +68 -7
- package/dist/resources/extensions/gsd/workflow-projections.js +6 -8
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +5 -1
- 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 +12 -12
- 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/api/git/route.js +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 +12 -12
- 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 +2 -2
- package/packages/mcp-server/src/workflow-tools.test.ts +1 -1
- package/packages/native/tsconfig.json +2 -1
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +82 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +52 -0
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/simple-options.d.ts +2 -4
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +5 -6
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/simple-options.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/simple-options.test.js +50 -0
- package/packages/pi-ai/dist/providers/simple-options.test.js.map +1 -0
- package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +63 -0
- package/packages/pi-ai/src/providers/openai-codex-responses.ts +91 -1
- package/packages/pi-ai/src/providers/simple-options.test.ts +60 -0
- package/packages/pi-ai/src/providers/simple-options.ts +5 -6
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.js +66 -0
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-thinking-level.test.ts +79 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/GSD-WORKFLOW.md +7 -0
- package/src/resources/extensions/gsd/auto/contracts.ts +14 -6
- package/src/resources/extensions/gsd/auto/infra-errors.ts +9 -3
- package/src/resources/extensions/gsd/auto/loop.ts +8 -5
- package/src/resources/extensions/gsd/auto/orchestrator.ts +11 -0
- package/src/resources/extensions/gsd/auto/phases.ts +7 -1
- package/src/resources/extensions/gsd/auto/workflow-memory-pressure.ts +13 -0
- package/src/resources/extensions/gsd/auto-model-selection.ts +2 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +1 -1
- package/src/resources/extensions/gsd/auto-start.ts +85 -6
- package/src/resources/extensions/gsd/auto-worktree.ts +15 -1
- package/src/resources/extensions/gsd/auto.ts +32 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +9 -8
- package/src/resources/extensions/gsd/bootstrap/subagent-input.ts +3 -1
- package/src/resources/extensions/gsd/crash-recovery.ts +30 -4
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +1 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +2 -2
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +25 -13
- package/src/resources/extensions/gsd/doctor.ts +2 -27
- package/src/resources/extensions/gsd/git-service.ts +45 -1
- package/src/resources/extensions/gsd/gsd-db.ts +3 -0
- package/src/resources/extensions/gsd/guided-flow.ts +6 -0
- package/src/resources/extensions/gsd/migrate/parsers.ts +11 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +46 -9
- package/src/resources/extensions/gsd/post-execution-checks.ts +87 -2
- package/src/resources/extensions/gsd/pre-execution-checks.ts +32 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/src/resources/extensions/gsd/status-guards.ts +5 -0
- package/src/resources/extensions/gsd/templates/plan.md +8 -5
- package/src/resources/extensions/gsd/templates/task-plan.md +4 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +80 -1
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +43 -2
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/guided-flow.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/hook-model-resolution.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/infra-error.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +103 -1
- package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +24 -1
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +63 -2
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +121 -1
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +200 -1
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +86 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/prompt-loader.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +31 -1
- package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +26 -2
- package/src/resources/extensions/gsd/tests/summary-render-parity.test.ts +7 -3
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +110 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/workflow-memory-pressure.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +7 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +8 -10
- package/src/resources/extensions/gsd/tools/complete-slice.ts +6 -8
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +5 -1
- package/src/resources/extensions/gsd/tools/plan-slice.ts +96 -12
- package/src/resources/extensions/gsd/types.ts +1 -1
- package/src/resources/extensions/gsd/validation.ts +23 -1
- package/src/resources/extensions/gsd/verification-gate.ts +78 -6
- package/src/resources/extensions/gsd/workflow-projections.ts +6 -8
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +7 -1
- /package/dist/web/standalone/.next/static/{BdZQhe8yKl6bdKLiXVEzh → q0WYuDVbHeFFYbdd-fei2}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{BdZQhe8yKl6bdKLiXVEzh → q0WYuDVbHeFFYbdd-fei2}/_ssgManifest.js +0 -0
|
@@ -448,6 +448,13 @@ What differed from the plan and why (or "None").
|
|
|
448
448
|
|
|
449
449
|
The one-liner must be substantive: "JWT auth with refresh rotation using jose" not "Authentication implemented."
|
|
450
450
|
|
|
451
|
+
When `key_files` or `key_decisions` are empty, render them as empty YAML lists:
|
|
452
|
+
|
|
453
|
+
```yaml
|
|
454
|
+
key_files: []
|
|
455
|
+
key_decisions: []
|
|
456
|
+
```
|
|
457
|
+
|
|
451
458
|
**Slice summary:** Written when all tasks in a slice complete. Compresses all task summaries. Includes `drill_down_paths` to each task summary. During slice completion, review task summaries for `key_decisions` and ensure any significant ones are captured in `.gsd/DECISIONS.md`.
|
|
452
459
|
|
|
453
460
|
**Milestone summary:** Updated each time a slice completes. Compresses all slice summaries. This is what gets injected into later slice planning instead of loading many individual summaries.
|
|
@@ -47,12 +47,20 @@ export interface DispatchAdapter {
|
|
|
47
47
|
sessionProvider?: string;
|
|
48
48
|
/** Model registry for executor-model lookups inside the budget engine. */
|
|
49
49
|
modelRegistry?: MinimalModelRegistry;
|
|
50
|
-
}): Promise<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
}): Promise<
|
|
51
|
+
| {
|
|
52
|
+
kind: "blocked";
|
|
53
|
+
reason: string;
|
|
54
|
+
action: "pause" | "stop";
|
|
55
|
+
}
|
|
56
|
+
| {
|
|
57
|
+
unitType: string;
|
|
58
|
+
unitId: string;
|
|
59
|
+
reason: string;
|
|
60
|
+
preconditions: string[];
|
|
61
|
+
}
|
|
62
|
+
| null
|
|
63
|
+
>;
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
export interface RecoveryAdapter {
|
|
@@ -7,9 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Error codes indicating infrastructure failures
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Error codes indicating infrastructure-level failures from the OS,
|
|
11
|
+
* filesystem, or network. This set includes permanent resource failures
|
|
12
|
+
* (ENOSPC, ENOMEM, EROFS), transient resource exhaustion (EAGAIN, ENOBUFS),
|
|
13
|
+
* and network/offline errors (ECONNREFUSED, ENOTFOUND, ENETUNREACH).
|
|
14
|
+
*
|
|
15
|
+
* Transient git failures are retried separately through
|
|
16
|
+
* TRANSIENT_GIT_RETRY_CODES in native-git-bridge.ts before escalating to the
|
|
17
|
+
* auto-loop.
|
|
13
18
|
*/
|
|
14
19
|
export const INFRA_ERROR_CODES: ReadonlySet<string> = new Set([
|
|
15
20
|
"ENOSPC", // disk full
|
|
@@ -19,6 +24,7 @@ export const INFRA_ERROR_CODES: ReadonlySet<string> = new Set([
|
|
|
19
24
|
"EMFILE", // too many open files (process)
|
|
20
25
|
"ENFILE", // too many open files (system)
|
|
21
26
|
"EAGAIN", // resource temporarily unavailable (resource exhaustion)
|
|
27
|
+
"ENOBUFS", // no buffer space available (transient pipe exhaustion)
|
|
22
28
|
"ECONNREFUSED", // connection refused (offline / local server down)
|
|
23
29
|
"ENOTFOUND", // DNS lookup failed (offline / no network)
|
|
24
30
|
"ENETUNREACH", // network unreachable (offline / no route)
|
|
@@ -78,7 +78,10 @@ import { createWorkflowTurnReporter } from "./workflow-turn-reporter.js";
|
|
|
78
78
|
import { validateWorkflowSessionLock } from "./workflow-session-lock.js";
|
|
79
79
|
import { dequeueSidecarItem } from "./workflow-sidecar-queue.js";
|
|
80
80
|
import { maintainWorkerHeartbeat } from "./workflow-worker-heartbeat.js";
|
|
81
|
-
import {
|
|
81
|
+
import {
|
|
82
|
+
measureMemoryPressure,
|
|
83
|
+
shouldCheckMemoryPressure,
|
|
84
|
+
} from "./workflow-memory-pressure.js";
|
|
82
85
|
import { buildSidecarIterationData } from "./workflow-sidecar-iteration.js";
|
|
83
86
|
import {
|
|
84
87
|
createExecutionGraphUnitDispatchDeps,
|
|
@@ -203,9 +206,9 @@ function logCustomVerifyRetrySaveFailure(err: unknown): void {
|
|
|
203
206
|
}
|
|
204
207
|
|
|
205
208
|
// ── Memory pressure monitoring (#3331) ──────────────────────────────────
|
|
206
|
-
// Check heap usage every N iterations and trigger
|
|
207
|
-
// the OS OOM killer sends SIGKILL. The threshold is
|
|
208
|
-
// limit (--max-old-space-size or default ~1.5-4GB depending on platform).
|
|
209
|
+
// Check heap usage on session startup, then every N iterations, and trigger
|
|
210
|
+
// graceful shutdown before the OS OOM killer sends SIGKILL. The threshold is
|
|
211
|
+
// 90% of the V8 heap limit (--max-old-space-size or default ~1.5-4GB depending on platform).
|
|
209
212
|
const MEMORY_CHECK_INTERVAL = 5; // check every 5 iterations
|
|
210
213
|
const MAX_CUSTOM_ENGINE_VERIFY_RETRIES = 3;
|
|
211
214
|
|
|
@@ -372,7 +375,7 @@ export async function autoLoop(
|
|
|
372
375
|
|
|
373
376
|
// ── Memory pressure check (#3331) ──
|
|
374
377
|
// Graceful shutdown before OOM killer sends SIGKILL.
|
|
375
|
-
if (iteration
|
|
378
|
+
if (shouldCheckMemoryPressure(iteration, MEMORY_CHECK_INTERVAL)) {
|
|
376
379
|
const mem = measureMemoryPressure();
|
|
377
380
|
debugLog("autoLoop", { phase: "memory-check", ...mem });
|
|
378
381
|
const memoryDecision = decideMemoryPressure({ ...mem, iteration });
|
|
@@ -128,6 +128,17 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
128
128
|
await this.deps.health.postAdvanceRecord(stopped);
|
|
129
129
|
return stopped;
|
|
130
130
|
}
|
|
131
|
+
if (!("unitType" in decision)) {
|
|
132
|
+
const blocked: AutoAdvanceResult = {
|
|
133
|
+
kind: "blocked",
|
|
134
|
+
reason: decision.reason,
|
|
135
|
+
action: decision.action,
|
|
136
|
+
stateSnapshot: reconciliation.stateSnapshot,
|
|
137
|
+
};
|
|
138
|
+
await this.deps.runtime.journalTransition({ name: "advance-blocked", reason: blocked.reason });
|
|
139
|
+
await this.deps.health.postAdvanceRecord(blocked);
|
|
140
|
+
return blocked;
|
|
141
|
+
}
|
|
131
142
|
|
|
132
143
|
const nextKey = `${decision.unitType}:${decision.unitId}`;
|
|
133
144
|
|
|
@@ -1327,9 +1327,15 @@ export async function runDispatch(
|
|
|
1327
1327
|
}
|
|
1328
1328
|
|
|
1329
1329
|
const guardBasePath = _resolveDispatchGuardBasePath(s);
|
|
1330
|
+
let mainBranch = "main";
|
|
1331
|
+
try {
|
|
1332
|
+
mainBranch = deps.getMainBranch(guardBasePath);
|
|
1333
|
+
} catch (err) {
|
|
1334
|
+
debugLog("autoLoop", { phase: "getMainBranch-failed", error: String(err) });
|
|
1335
|
+
}
|
|
1330
1336
|
const priorSliceBlocker = deps.getPriorSliceCompletionBlocker(
|
|
1331
1337
|
guardBasePath,
|
|
1332
|
-
|
|
1338
|
+
mainBranch,
|
|
1333
1339
|
unitType,
|
|
1334
1340
|
unitId,
|
|
1335
1341
|
);
|
|
@@ -19,6 +19,19 @@ export interface MeasureMemoryPressureDeps {
|
|
|
19
19
|
heapLimitBytes: () => number;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Returns true on auto-mode startup, then every configured interval.
|
|
24
|
+
*
|
|
25
|
+
* Iteration 1 is checked explicitly so early session memory pressure cannot
|
|
26
|
+
* bypass the periodic interval guard.
|
|
27
|
+
*/
|
|
28
|
+
export function shouldCheckMemoryPressure(iteration: number, interval: number): boolean {
|
|
29
|
+
if (!Number.isInteger(interval) || interval <= 0) {
|
|
30
|
+
throw new Error("Memory pressure check interval must be a positive integer");
|
|
31
|
+
}
|
|
32
|
+
return iteration === 1 || iteration % interval === 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
function defaultHeapLimitBytes(): number {
|
|
23
36
|
const v8 = require("node:v8") as {
|
|
24
37
|
getHeapStatistics?: () => { heap_size_limit?: number };
|
|
@@ -615,10 +615,11 @@ export async function selectAndApplyModel(
|
|
|
615
615
|
* Handles formats: "provider/model", "bare-id", "org/model-name" (OpenRouter).
|
|
616
616
|
*/
|
|
617
617
|
export function resolveModelId<T extends { id: string; provider: string }>(
|
|
618
|
-
modelId: string,
|
|
618
|
+
modelId: string | undefined,
|
|
619
619
|
availableModels: T[],
|
|
620
620
|
currentProvider: string | undefined,
|
|
621
621
|
): T | undefined {
|
|
622
|
+
if (!modelId) return undefined;
|
|
622
623
|
const slashIdx = modelId.indexOf("/");
|
|
623
624
|
|
|
624
625
|
if (slashIdx !== -1) {
|
|
@@ -164,7 +164,7 @@ async function buildTaskCommitContextForUnit(
|
|
|
164
164
|
sliceTitle: stripKnownIdPrefix(slice?.title, sid),
|
|
165
165
|
oneLiner: summary?.oneLiner || task?.one_liner || undefined,
|
|
166
166
|
keyFiles:
|
|
167
|
-
summary?.frontmatter.key_files?.filter(f => !f.includes("{{")) ??
|
|
167
|
+
summary?.frontmatter.key_files?.filter(f => !f.includes("{{") && f.trim() !== "(none)") ??
|
|
168
168
|
task?.key_files ??
|
|
169
169
|
undefined,
|
|
170
170
|
issueNumber: ghIssueNumber,
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
nativeDetectMainBranch,
|
|
45
45
|
nativeCheckoutBranch,
|
|
46
46
|
nativeBranchList,
|
|
47
|
+
nativeBranchExists,
|
|
47
48
|
nativeBranchListMerged,
|
|
48
49
|
nativeBranchDelete,
|
|
49
50
|
nativeWorktreeRemove,
|
|
@@ -64,7 +65,7 @@ import { initRoutingHistory } from "./routing-history.js";
|
|
|
64
65
|
import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
|
|
65
66
|
import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
|
|
66
67
|
import { snapshotSkills } from "./skill-discovery.js";
|
|
67
|
-
import { isDbAvailable, getMilestone, openDatabase, getDbStatus } from "./gsd-db.js";
|
|
68
|
+
import { isDbAvailable, getMilestone, getAllMilestones, openDatabase, getDbStatus } from "./gsd-db.js";
|
|
68
69
|
import { isClosedStatus } from "./status-guards.js";
|
|
69
70
|
import { classifyMilestoneSummaryContent } from "./milestone-summary-classifier.js";
|
|
70
71
|
import { auditOrphanedPreflightStashes } from "./orphan-stash-audit.js";
|
|
@@ -101,6 +102,7 @@ import { getSessionModelOverride } from "./session-model-override.js";
|
|
|
101
102
|
export interface BootstrapDeps {
|
|
102
103
|
shouldUseWorktreeIsolation: (basePath?: string) => boolean;
|
|
103
104
|
registerSigtermHandler: (basePath: string) => void;
|
|
105
|
+
registerAutoWorkerForSession: (basePath: string) => void;
|
|
104
106
|
lockBase: () => string;
|
|
105
107
|
buildLifecycle: () => WorktreeLifecycle;
|
|
106
108
|
}
|
|
@@ -202,9 +204,15 @@ export function decideSurvivorAction(
|
|
|
202
204
|
export function auditOrphanedMilestoneBranches(
|
|
203
205
|
basePath: string,
|
|
204
206
|
isolationMode: "worktree" | "branch" | "none",
|
|
207
|
+
gitDeps: {
|
|
208
|
+
branchList?: typeof nativeBranchList;
|
|
209
|
+
branchExists?: typeof nativeBranchExists;
|
|
210
|
+
} = {},
|
|
205
211
|
): { recovered: string[]; warnings: string[] } {
|
|
206
212
|
const recovered: string[] = [];
|
|
207
213
|
const warnings: string[] = [];
|
|
214
|
+
const branchList = gitDeps.branchList ?? nativeBranchList;
|
|
215
|
+
const branchExists = gitDeps.branchExists ?? nativeBranchExists;
|
|
208
216
|
|
|
209
217
|
// Skip in none mode — no milestone branches are created
|
|
210
218
|
if (isolationMode === "none") return { recovered, warnings };
|
|
@@ -213,15 +221,16 @@ export function auditOrphanedMilestoneBranches(
|
|
|
213
221
|
if (!isDbAvailable()) return { recovered, warnings };
|
|
214
222
|
|
|
215
223
|
let milestoneBranches: string[];
|
|
224
|
+
let milestoneBranchListAvailable = true;
|
|
216
225
|
try {
|
|
217
|
-
milestoneBranches =
|
|
226
|
+
milestoneBranches = branchList(basePath, "milestone/*");
|
|
218
227
|
} catch {
|
|
219
|
-
|
|
220
|
-
|
|
228
|
+
milestoneBranchListAvailable = false;
|
|
229
|
+
// git branch list failed — fall through with an empty branch set so the
|
|
230
|
+
// branch-less orphan pass can still run after per-milestone verification.
|
|
231
|
+
milestoneBranches = [];
|
|
221
232
|
}
|
|
222
233
|
|
|
223
|
-
if (milestoneBranches.length === 0) return { recovered, warnings };
|
|
224
|
-
|
|
225
234
|
// Detect main branch for merge-check
|
|
226
235
|
let mainBranch: string;
|
|
227
236
|
try {
|
|
@@ -354,6 +363,74 @@ export function auditOrphanedMilestoneBranches(
|
|
|
354
363
|
}
|
|
355
364
|
}
|
|
356
365
|
|
|
366
|
+
// Second pass (#5879): catch worktree directories stranded by a previous
|
|
367
|
+
// audit that deleted the milestone/* branch but failed to remove the
|
|
368
|
+
// directory (or the dir was orphaned by a separate path entirely, e.g.
|
|
369
|
+
// postflight-stash-restore-failed during closeout). The branch-keyed loop
|
|
370
|
+
// above is invisible to these cases — `nativeBranchList` returns nothing
|
|
371
|
+
// for the milestone, so the dir-cleanup block at line ~310 is never
|
|
372
|
+
// reached.
|
|
373
|
+
//
|
|
374
|
+
// Keyed on milestones whose DB status is `complete`. We do not iterate
|
|
375
|
+
// over arbitrary directories under .gsd/worktrees/ to avoid touching
|
|
376
|
+
// dirs that belong to an in-progress milestone whose branch was deleted
|
|
377
|
+
// separately — those are handled by the in-progress orphan path above
|
|
378
|
+
// when the branch is present, and by `/gsd doctor` when it is not.
|
|
379
|
+
const seenMilestoneIds = new Set(
|
|
380
|
+
milestoneBranches.map((branch) => branch.replace(/^milestone\//, "")),
|
|
381
|
+
);
|
|
382
|
+
let completedMilestones: readonly { id: string; status: string }[] = [];
|
|
383
|
+
try {
|
|
384
|
+
completedMilestones = getAllMilestones();
|
|
385
|
+
} catch {
|
|
386
|
+
// DB read failure — skip the second pass; the first pass is still useful.
|
|
387
|
+
completedMilestones = [];
|
|
388
|
+
}
|
|
389
|
+
for (const m of completedMilestones) {
|
|
390
|
+
if (m.status !== "complete") continue;
|
|
391
|
+
if (seenMilestoneIds.has(m.id)) continue; // already processed in the branch loop
|
|
392
|
+
if (!milestoneBranchListAvailable) {
|
|
393
|
+
try {
|
|
394
|
+
if (branchExists(basePath, `milestone/${m.id}`)) continue;
|
|
395
|
+
} catch (err) {
|
|
396
|
+
warnings.push(
|
|
397
|
+
`Could not verify whether milestone/${m.id} still exists; skipping branch-less worktree cleanup for safety: ${err instanceof Error ? err.message : String(err)}`,
|
|
398
|
+
);
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const wtDir = getWorktreeDir(basePath, m.id);
|
|
403
|
+
if (!existsSync(wtDir)) continue;
|
|
404
|
+
if (!isInsideWorktreesDir(basePath, wtDir)) {
|
|
405
|
+
warnings.push(
|
|
406
|
+
`Orphaned worktree directory for ${m.id} is outside .gsd/worktrees/ — skipping removal for safety.`,
|
|
407
|
+
);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
// Try `git worktree remove` first in case the dir is still registered
|
|
411
|
+
// (defensive — usually it is not when we reach this branch-less pass).
|
|
412
|
+
try {
|
|
413
|
+
nativeWorktreeRemove(basePath, wtDir, true);
|
|
414
|
+
} catch (e) {
|
|
415
|
+
logWarning(
|
|
416
|
+
"engine",
|
|
417
|
+
`worktree remove failed (expected for branch-less orphans): ${e instanceof Error ? e.message : String(e)}`,
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
if (existsSync(wtDir)) {
|
|
421
|
+
try {
|
|
422
|
+
rmSync(wtDir, { recursive: true, force: true });
|
|
423
|
+
recovered.push(`Removed orphaned worktree directory for ${m.id} (branch already deleted).`);
|
|
424
|
+
} catch (err) {
|
|
425
|
+
warnings.push(
|
|
426
|
+
`Failed to remove orphaned worktree directory for ${m.id}: ${err instanceof Error ? err.message : String(err)}`,
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
} else {
|
|
430
|
+
recovered.push(`Removed orphaned worktree directory for ${m.id} (branch already deleted).`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
357
434
|
return { recovered, warnings };
|
|
358
435
|
}
|
|
359
436
|
|
|
@@ -533,6 +610,7 @@ export async function bootstrapAutoSession(
|
|
|
533
610
|
const {
|
|
534
611
|
shouldUseWorktreeIsolation,
|
|
535
612
|
registerSigtermHandler,
|
|
613
|
+
registerAutoWorkerForSession,
|
|
536
614
|
lockBase,
|
|
537
615
|
buildLifecycle,
|
|
538
616
|
} = deps;
|
|
@@ -722,6 +800,7 @@ export async function bootstrapAutoSession(
|
|
|
722
800
|
// consult DB status and avoid clearing runtime units for milestones that
|
|
723
801
|
// only have a failure-path SUMMARY on disk (#4663).
|
|
724
802
|
await openProjectDbIfPresent(base);
|
|
803
|
+
registerAutoWorkerForSession(base);
|
|
725
804
|
|
|
726
805
|
// Clean stale runtime unit files for completed milestones (#887).
|
|
727
806
|
// DB-authoritative: when DB is available, require DB status to be closed
|
|
@@ -252,7 +252,17 @@ function gitPathspecForWorktreePath(basePath: string, targetPath: string): strin
|
|
|
252
252
|
let base = basePath;
|
|
253
253
|
let target = targetPath;
|
|
254
254
|
try {
|
|
255
|
-
base =
|
|
255
|
+
base = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
256
|
+
cwd: basePath,
|
|
257
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
258
|
+
encoding: "utf-8",
|
|
259
|
+
}).trim() || basePath;
|
|
260
|
+
} catch {
|
|
261
|
+
/* keep original */
|
|
262
|
+
void base;
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
base = realpathSync.native(base);
|
|
256
266
|
} catch {
|
|
257
267
|
/* keep original */
|
|
258
268
|
void base;
|
|
@@ -269,6 +279,10 @@ function gitPathspecForWorktreePath(basePath: string, targetPath: string): strin
|
|
|
269
279
|
return rel.replaceAll("\\", "/");
|
|
270
280
|
}
|
|
271
281
|
|
|
282
|
+
export function _gitPathspecForWorktreePath(basePath: string, targetPath: string): string | null {
|
|
283
|
+
return gitPathspecForWorktreePath(basePath, targetPath);
|
|
284
|
+
}
|
|
285
|
+
|
|
272
286
|
function gitRemoteExists(basePath: string, remote: string): boolean {
|
|
273
287
|
try {
|
|
274
288
|
execFileSync("git", ["remote", "get-url", remote], {
|
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
import {
|
|
59
59
|
writeLock,
|
|
60
60
|
clearLock,
|
|
61
|
+
clearStaleWorkerLock,
|
|
61
62
|
readCrashLock,
|
|
62
63
|
isLockProcessAlive,
|
|
63
64
|
formatCrashInfo,
|
|
@@ -240,7 +241,7 @@ import { runAutoLoopWithUok } from "./uok/kernel.js";
|
|
|
240
241
|
import { resolveUokFlags } from "./uok/flags.js";
|
|
241
242
|
import { validateDirectory } from "./validate-directory.js";
|
|
242
243
|
import { createAutoOrchestrator } from "./auto/orchestrator.js";
|
|
243
|
-
import type { AutoOrchestrationModule, AutoOrchestratorDeps, DispatchAdapter } from "./auto/contracts.js";
|
|
244
|
+
import type { AutoAdvanceResult, AutoOrchestrationModule, AutoOrchestratorDeps, DispatchAdapter } from "./auto/contracts.js";
|
|
244
245
|
import { reconcileBeforeDispatch } from "./state-reconciliation.js";
|
|
245
246
|
import { compileUnitToolContract } from "./tool-contract.js";
|
|
246
247
|
import { createWorktreeSafetyModule } from "./worktree-safety.js";
|
|
@@ -1044,6 +1045,7 @@ export async function cleanupAfterLoopExit(ctx: ExtensionContext): Promise<void>
|
|
|
1044
1045
|
// visible so the user still has a resumable auto-mode signal on screen.
|
|
1045
1046
|
if (!s.paused) {
|
|
1046
1047
|
ctx.ui.setStatus("gsd-auto", undefined);
|
|
1048
|
+
ctx.ui.setWidget("gsd-progress", undefined);
|
|
1047
1049
|
if (s.completionStopInProgress) {
|
|
1048
1050
|
s.completionStopInProgress = false;
|
|
1049
1051
|
}
|
|
@@ -1839,6 +1841,13 @@ export function createWiredDispatchAdapter(
|
|
|
1839
1841
|
modelRegistry,
|
|
1840
1842
|
});
|
|
1841
1843
|
|
|
1844
|
+
if (action.action === "stop") {
|
|
1845
|
+
return {
|
|
1846
|
+
kind: "blocked",
|
|
1847
|
+
reason: action.reason,
|
|
1848
|
+
action: action.level === "warning" ? "pause" : "stop",
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1842
1851
|
if (action.action !== "dispatch") return null;
|
|
1843
1852
|
return {
|
|
1844
1853
|
unitType: action.unitType,
|
|
@@ -2065,6 +2074,18 @@ export function createWiredAutoOrchestrationModule(
|
|
|
2065
2074
|
return createAutoOrchestrator(deps);
|
|
2066
2075
|
}
|
|
2067
2076
|
|
|
2077
|
+
function notifyResumeBlocked(ctx: ExtensionContext, result: Extract<AutoAdvanceResult, { kind: "blocked" }>): void {
|
|
2078
|
+
const resumeCmd = s.stepMode ? "/gsd next" : "/gsd auto";
|
|
2079
|
+
ctx.ui.notify(`Auto-mode blocked: ${result.reason}. Fix and run ${resumeCmd} to resume.`, "warning");
|
|
2080
|
+
setLifecycleOutcome(ctx, {
|
|
2081
|
+
status: "blocked",
|
|
2082
|
+
title: "Auto-mode blocked",
|
|
2083
|
+
detail: result.reason,
|
|
2084
|
+
nextAction: `Fix the blocker, then run ${resumeCmd} to resume.`,
|
|
2085
|
+
commands: ["/gsd status for overview", `${resumeCmd} to resume`, "/gsd doctor to diagnose"],
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2068
2089
|
function ensureOrchestrationModule(ctx: ExtensionContext, pi: ExtensionAPI, basePath: string): void {
|
|
2069
2090
|
s.orchestration = createWiredAutoOrchestrationModule(ctx, pi, basePath, lockBase());
|
|
2070
2091
|
}
|
|
@@ -2402,7 +2423,7 @@ export async function startAuto(
|
|
|
2402
2423
|
// This closes the journal gap reported in #3348 where the worker wrote side
|
|
2403
2424
|
// effects (SUMMARY.md, DB updates) but died before emitting unit-end.
|
|
2404
2425
|
emitCrashRecoveredUnitEnd(base, freshStartAssessment.lock);
|
|
2405
|
-
|
|
2426
|
+
clearStaleWorkerLock(base);
|
|
2406
2427
|
}
|
|
2407
2428
|
|
|
2408
2429
|
if (!s.paused) {
|
|
@@ -2473,6 +2494,8 @@ export async function startAuto(
|
|
|
2473
2494
|
s.unitLifetimeDispatches.clear();
|
|
2474
2495
|
if (!getLedger()) initMetrics(base);
|
|
2475
2496
|
if (s.currentMilestoneId) setActiveMilestoneId(base, s.currentMilestoneId);
|
|
2497
|
+
await openProjectDbIfPresent(base);
|
|
2498
|
+
registerAutoWorkerForSession(s, base);
|
|
2476
2499
|
|
|
2477
2500
|
// Re-register health level notification callback lost across process restart
|
|
2478
2501
|
setLevelChangeCallback((_from, to, summary) => {
|
|
@@ -2580,7 +2603,12 @@ export async function startAuto(
|
|
|
2580
2603
|
pi.events.emit(CMUX_CHANNELS.LOG, { preferences: loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, message: s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", level: "progress" });
|
|
2581
2604
|
|
|
2582
2605
|
try {
|
|
2583
|
-
await s.orchestration?.resume();
|
|
2606
|
+
const resumeResult = await s.orchestration?.resume();
|
|
2607
|
+
if (resumeResult?.kind === "blocked") {
|
|
2608
|
+
notifyResumeBlocked(ctx, resumeResult);
|
|
2609
|
+
await cleanupAfterLoopExit(ctx);
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2584
2612
|
} catch (err) {
|
|
2585
2613
|
debugLog("resume-orchestration-resume", { error: err instanceof Error ? err.message : String(err) });
|
|
2586
2614
|
}
|
|
@@ -2601,6 +2629,7 @@ export async function startAuto(
|
|
|
2601
2629
|
const bootstrapDeps: BootstrapDeps = {
|
|
2602
2630
|
shouldUseWorktreeIsolation,
|
|
2603
2631
|
registerSigtermHandler,
|
|
2632
|
+
registerAutoWorkerForSession: (projectRoot) => registerAutoWorkerForSession(s, projectRoot),
|
|
2604
2633
|
lockBase,
|
|
2605
2634
|
buildLifecycle,
|
|
2606
2635
|
};
|
|
@@ -487,17 +487,18 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
487
487
|
promptGuidelines: [
|
|
488
488
|
"Use gsd_plan_milestone for milestone planning instead of writing ROADMAP.md directly.",
|
|
489
489
|
"Keep parameters flat and provide the full milestone planning payload, including slices.",
|
|
490
|
+
"Milestone and slice titles must not contain forward slash (/), en dash, or em dash characters.",
|
|
490
491
|
"The tool validates input, writes milestone and slice planning data transactionally, renders ROADMAP.md from DB, and clears both state and parse caches after success.",
|
|
491
492
|
"Use the canonical name gsd_plan_milestone; gsd_milestone_plan is only an alias.",
|
|
492
493
|
],
|
|
493
494
|
parameters: Type.Object({
|
|
494
495
|
// ── Core identification + content (required) ──────────────────────
|
|
495
496
|
milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
|
|
496
|
-
title: Type.String({ description: "Milestone title" }),
|
|
497
|
+
title: Type.String({ description: "Milestone title; must not contain forward slash (/), en dash, or em dash characters" }),
|
|
497
498
|
vision: Type.String({ description: "Milestone vision" }),
|
|
498
499
|
slices: Type.Array(Type.Object({
|
|
499
500
|
sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
|
|
500
|
-
title: Type.String({ description: "Slice title" }),
|
|
501
|
+
title: Type.String({ description: "Slice title; must not contain forward slash (/), en dash, or em dash characters" }),
|
|
501
502
|
risk: Type.String({ description: "Slice risk" }),
|
|
502
503
|
depends: Type.Array(Type.String(), { description: "Slice dependency IDs" }),
|
|
503
504
|
demo: Type.String({ description: "Roadmap demo text / After this" }),
|
|
@@ -570,10 +571,10 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
570
571
|
title: Type.String({ description: "Task title" }),
|
|
571
572
|
description: Type.String({ description: "Task description / steps block" }),
|
|
572
573
|
estimate: Type.String({ description: "Task estimate string" }),
|
|
573
|
-
files: Type.Array(Type.String(), { description: "
|
|
574
|
+
files: Type.Array(Type.String(), { description: "Array<string> of files likely touched; pass [\"path\"] or [], never a single string" }),
|
|
574
575
|
verify: Type.String({ description: "Verification command or block" }),
|
|
575
|
-
inputs: Type.Array(Type.String(), { description: "
|
|
576
|
-
expectedOutput: Type.Array(Type.String(), { description: "
|
|
576
|
+
inputs: Type.Array(Type.String(), { description: "Array<string> of input files or references; pass [\"path\"] or [], never a single string" }),
|
|
577
|
+
expectedOutput: Type.Array(Type.String(), { description: "Array<string> of expected output files or artifacts; pass [\"path\"] or [], never a single string" }),
|
|
577
578
|
observabilityImpact: Type.Optional(Type.String({ description: "Task observability impact" })),
|
|
578
579
|
}), { description: "Planned tasks for the slice" }),
|
|
579
580
|
// ── Enrichment metadata (optional — defaults to empty) ────────────
|
|
@@ -650,10 +651,10 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
650
651
|
title: Type.String({ description: "Task title" }),
|
|
651
652
|
description: Type.String({ description: "Task description / steps block" }),
|
|
652
653
|
estimate: Type.String({ description: "Task estimate string" }),
|
|
653
|
-
files: Type.Array(Type.String(), { description: "
|
|
654
|
+
files: Type.Array(Type.String(), { description: "Array<string> of files likely touched; pass [\"path\"] or [], never a single string" }),
|
|
654
655
|
verify: Type.String({ description: "Verification command or block" }),
|
|
655
|
-
inputs: Type.Array(Type.String(), { description: "
|
|
656
|
-
expectedOutput: Type.Array(Type.String(), { description: "
|
|
656
|
+
inputs: Type.Array(Type.String(), { description: "Array<string> of input files or references; pass [\"path\"] or [], never a single string" }),
|
|
657
|
+
expectedOutput: Type.Array(Type.String(), { description: "Array<string> of expected output files or artifacts; pass [\"path\"] or [], never a single string" }),
|
|
657
658
|
observabilityImpact: Type.Optional(Type.String({ description: "Task observability impact" })),
|
|
658
659
|
// Single-writer v3 audit trail (Stream 2): caller-provided actor identity + causation.
|
|
659
660
|
actorName: Type.Optional(Type.String({ description: "Caller-provided actor identity for the audit trail (e.g. 'executor-01', 'gsd-orchestrator')" })),
|
|
@@ -4,7 +4,9 @@ export function extractSubagentAgentClasses(input: unknown): string[] {
|
|
|
4
4
|
const agentClasses: string[] = [];
|
|
5
5
|
const visited = new WeakSet<object>();
|
|
6
6
|
const addAgentClass = (value: unknown): void => {
|
|
7
|
-
if (typeof value
|
|
7
|
+
if (typeof value !== "string") return;
|
|
8
|
+
const normalized = value.trim().replace(/\.md$/i, "");
|
|
9
|
+
if (normalized.length > 0) agentClasses.push(normalized);
|
|
8
10
|
};
|
|
9
11
|
|
|
10
12
|
const visitItems = (value: unknown): void => {
|
|
@@ -30,9 +30,10 @@ import { join } from "node:path";
|
|
|
30
30
|
import {
|
|
31
31
|
findStaleWorkerForProject,
|
|
32
32
|
getAllAutoWorkers,
|
|
33
|
+
markWorkerCrashed,
|
|
33
34
|
type AutoWorkerRow,
|
|
34
35
|
} from "./db/auto-workers.js";
|
|
35
|
-
import {
|
|
36
|
+
import { markLatestActiveForWorkerCanceled, type DispatchStatus } from "./db/unit-dispatches.js";
|
|
36
37
|
import { getRuntimeKv, setRuntimeKv, deleteRuntimeKv } from "./db/runtime-kv.js";
|
|
37
38
|
import { _getAdapter, isDbAvailable } from "./gsd-db.js";
|
|
38
39
|
import { gsdRoot, normalizeRealPath } from "./paths.js";
|
|
@@ -56,6 +57,15 @@ function lockPath(basePath: string): string {
|
|
|
56
57
|
return join(gsdRoot(basePath), effectiveLockFile());
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
function clearLegacyLockFile(basePath: string): void {
|
|
61
|
+
try {
|
|
62
|
+
const p = lockPath(basePath);
|
|
63
|
+
if (existsSync(p)) unlinkSync(p);
|
|
64
|
+
} catch {
|
|
65
|
+
// Best-effort.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
59
69
|
function readLegacyLock(basePath: string): LockData | null {
|
|
60
70
|
try {
|
|
61
71
|
const p = lockPath(basePath);
|
|
@@ -204,18 +214,34 @@ export function writeLock(
|
|
|
204
214
|
* stale session-file pointer.
|
|
205
215
|
*/
|
|
206
216
|
export function clearLock(basePath: string): void {
|
|
217
|
+
clearLegacyLockFile(basePath);
|
|
218
|
+
|
|
219
|
+
if (!isDbAvailable()) return;
|
|
207
220
|
try {
|
|
208
|
-
const
|
|
209
|
-
|
|
221
|
+
const projectRoot = normalizeRealPath(basePath);
|
|
222
|
+
const worker = findActiveWorkerForCurrentProcess(projectRoot);
|
|
223
|
+
if (!worker) return;
|
|
224
|
+
deleteRuntimeKv("worker", worker.worker_id, SESSION_FILE_KV_KEY);
|
|
210
225
|
} catch {
|
|
211
226
|
// Best-effort.
|
|
212
227
|
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Clear a stale DB-backed worker lock after readCrashLock/findStaleWorkerForProject
|
|
232
|
+
* has identified a dead worker. Unlike clearLock(), this targets the stale
|
|
233
|
+
* worker row instead of the current process's active worker.
|
|
234
|
+
*/
|
|
235
|
+
export function clearStaleWorkerLock(basePath: string): void {
|
|
236
|
+
clearLegacyLockFile(basePath);
|
|
213
237
|
|
|
214
238
|
if (!isDbAvailable()) return;
|
|
215
239
|
try {
|
|
216
240
|
const projectRoot = normalizeRealPath(basePath);
|
|
217
|
-
const worker =
|
|
241
|
+
const worker = findStaleWorkerForProject(projectRoot);
|
|
218
242
|
if (!worker) return;
|
|
243
|
+
markLatestActiveForWorkerCanceled(worker.worker_id, "crash-recovered");
|
|
244
|
+
markWorkerCrashed(worker.worker_id);
|
|
219
245
|
deleteRuntimeKv("worker", worker.worker_id, SESSION_FILE_KV_KEY);
|
|
220
246
|
} catch {
|
|
221
247
|
// Best-effort.
|
|
@@ -528,6 +528,7 @@ export function getRecentUnitKeysForProjectRoot(
|
|
|
528
528
|
FROM unit_dispatches ud
|
|
529
529
|
INNER JOIN workers w ON w.worker_id = ud.worker_id
|
|
530
530
|
WHERE w.project_root_realpath = :project_root_realpath
|
|
531
|
+
AND w.status != 'crashed'
|
|
531
532
|
ORDER BY ud.started_at DESC, ud.id DESC
|
|
532
533
|
LIMIT :limit`,
|
|
533
534
|
).all({
|
|
@@ -5,7 +5,7 @@ import { findMilestoneIds } from "./guided-flow.js";
|
|
|
5
5
|
import { parseUnitId } from "./unit-id.js";
|
|
6
6
|
import { isDbAvailable, getMilestoneSlices, getMilestone } from "./gsd-db.js";
|
|
7
7
|
import { parseRoadmap } from "./parsers-legacy.js";
|
|
8
|
-
import { isClosedStatus } from "./status-guards.js";
|
|
8
|
+
import { isClosedStatus, isSkippedForDispatch } from "./status-guards.js";
|
|
9
9
|
import { classifyMilestoneSummaryContent } from "./milestone-summary-classifier.js";
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
11
11
|
|
|
@@ -58,7 +58,7 @@ export function getPriorSliceCompletionBlocker(
|
|
|
58
58
|
// DB-backed projects must not treat SUMMARY.md as authoritative.
|
|
59
59
|
if (isDbAvailable()) {
|
|
60
60
|
const milestoneRow = getMilestone(mid);
|
|
61
|
-
if (milestoneRow &&
|
|
61
|
+
if (milestoneRow && isSkippedForDispatch(milestoneRow.status)) continue;
|
|
62
62
|
} else {
|
|
63
63
|
const summaryPath = resolveMilestoneFile(base, mid, "SUMMARY");
|
|
64
64
|
let summaryContent: string | null = null;
|