gsd-pi 2.32.0 → 2.33.0-dev.69bff0f
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 +22 -20
- package/dist/resource-loader.js +13 -3
- package/dist/resources/extensions/gsd/auto-dashboard.ts +3 -1
- package/dist/resources/extensions/gsd/auto-dispatch.ts +40 -12
- package/dist/resources/extensions/gsd/auto-idempotency.ts +3 -2
- package/dist/resources/extensions/gsd/auto-observability.ts +2 -4
- package/dist/resources/extensions/gsd/auto-post-unit.ts +5 -5
- package/dist/resources/extensions/gsd/auto-prompts.ts +46 -44
- package/dist/resources/extensions/gsd/auto-recovery.ts +8 -22
- package/dist/resources/extensions/gsd/auto-start.ts +8 -6
- package/dist/resources/extensions/gsd/auto-stuck-detection.ts +3 -2
- package/dist/resources/extensions/gsd/auto-supervisor.ts +10 -5
- package/dist/resources/extensions/gsd/auto-timeout-recovery.ts +2 -1
- package/dist/resources/extensions/gsd/auto-timers.ts +3 -2
- package/dist/resources/extensions/gsd/auto-verification.ts +6 -6
- package/dist/resources/extensions/gsd/auto-worktree.ts +140 -5
- package/dist/resources/extensions/gsd/auto.ts +108 -182
- package/dist/resources/extensions/gsd/commands-inspect.ts +2 -1
- package/dist/resources/extensions/gsd/commands-workflow-templates.ts +2 -1
- package/dist/resources/extensions/gsd/commands.ts +14 -2
- package/dist/resources/extensions/gsd/complexity-classifier.ts +5 -7
- package/dist/resources/extensions/gsd/crash-recovery.ts +15 -2
- package/dist/resources/extensions/gsd/dispatch-guard.ts +2 -1
- package/dist/resources/extensions/gsd/error-utils.ts +6 -0
- package/dist/resources/extensions/gsd/export.ts +2 -1
- package/dist/resources/extensions/gsd/git-service.ts +3 -2
- package/dist/resources/extensions/gsd/guided-flow.ts +3 -2
- package/dist/resources/extensions/gsd/index.ts +12 -5
- package/dist/resources/extensions/gsd/key-manager.ts +2 -1
- package/dist/resources/extensions/gsd/marketplace-discovery.ts +4 -3
- package/dist/resources/extensions/gsd/metrics.ts +3 -3
- package/dist/resources/extensions/gsd/migrate-external.ts +21 -4
- package/dist/resources/extensions/gsd/milestone-ids.ts +2 -1
- package/dist/resources/extensions/gsd/native-git-bridge.ts +2 -1
- package/dist/resources/extensions/gsd/parallel-merge.ts +2 -1
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +2 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +8 -9
- package/dist/resources/extensions/gsd/quick.ts +58 -3
- package/dist/resources/extensions/gsd/repo-identity.ts +22 -1
- package/dist/resources/extensions/gsd/session-lock.ts +86 -11
- package/dist/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +14 -11
- package/dist/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +691 -0
- package/dist/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +317 -0
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/loop-regression.test.ts +877 -0
- package/dist/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +358 -0
- package/dist/resources/extensions/gsd/tests/session-lock-regression.test.ts +216 -0
- package/dist/resources/extensions/gsd/tests/session-lock.test.ts +119 -0
- package/dist/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +206 -0
- package/dist/resources/extensions/gsd/undo.ts +5 -7
- package/dist/resources/extensions/gsd/unit-id.ts +14 -0
- package/dist/resources/extensions/gsd/unit-runtime.ts +2 -1
- package/dist/resources/extensions/gsd/worktree-command.ts +8 -7
- package/package.json +3 -2
- package/packages/pi-coding-agent/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto-dashboard.ts +3 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +40 -12
- package/src/resources/extensions/gsd/auto-idempotency.ts +3 -2
- package/src/resources/extensions/gsd/auto-observability.ts +2 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +5 -5
- package/src/resources/extensions/gsd/auto-prompts.ts +46 -44
- package/src/resources/extensions/gsd/auto-recovery.ts +8 -22
- package/src/resources/extensions/gsd/auto-start.ts +8 -6
- package/src/resources/extensions/gsd/auto-stuck-detection.ts +3 -2
- package/src/resources/extensions/gsd/auto-supervisor.ts +10 -5
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -1
- package/src/resources/extensions/gsd/auto-timers.ts +3 -2
- package/src/resources/extensions/gsd/auto-verification.ts +6 -6
- package/src/resources/extensions/gsd/auto-worktree.ts +140 -5
- package/src/resources/extensions/gsd/auto.ts +108 -182
- package/src/resources/extensions/gsd/commands-inspect.ts +2 -1
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +2 -1
- package/src/resources/extensions/gsd/commands.ts +14 -2
- package/src/resources/extensions/gsd/complexity-classifier.ts +5 -7
- package/src/resources/extensions/gsd/crash-recovery.ts +15 -2
- package/src/resources/extensions/gsd/dispatch-guard.ts +2 -1
- package/src/resources/extensions/gsd/error-utils.ts +6 -0
- package/src/resources/extensions/gsd/export.ts +2 -1
- package/src/resources/extensions/gsd/git-service.ts +3 -2
- package/src/resources/extensions/gsd/guided-flow.ts +3 -2
- package/src/resources/extensions/gsd/index.ts +12 -5
- package/src/resources/extensions/gsd/key-manager.ts +2 -1
- package/src/resources/extensions/gsd/marketplace-discovery.ts +4 -3
- package/src/resources/extensions/gsd/metrics.ts +3 -3
- package/src/resources/extensions/gsd/migrate-external.ts +21 -4
- package/src/resources/extensions/gsd/milestone-ids.ts +2 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +2 -1
- package/src/resources/extensions/gsd/parallel-merge.ts +2 -1
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +2 -1
- package/src/resources/extensions/gsd/post-unit-hooks.ts +8 -9
- package/src/resources/extensions/gsd/quick.ts +58 -3
- package/src/resources/extensions/gsd/repo-identity.ts +22 -1
- package/src/resources/extensions/gsd/session-lock.ts +86 -11
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +14 -11
- package/src/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +691 -0
- package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +317 -0
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/loop-regression.test.ts +877 -0
- package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +358 -0
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +216 -0
- package/src/resources/extensions/gsd/tests/session-lock.test.ts +119 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +206 -0
- package/src/resources/extensions/gsd/undo.ts +5 -7
- package/src/resources/extensions/gsd/unit-id.ts +14 -0
- package/src/resources/extensions/gsd/unit-runtime.ts +2 -1
- package/src/resources/extensions/gsd/worktree-command.ts +8 -7
- package/dist/resources/extensions/mcporter/extension-manifest.json +0 -12
- package/src/resources/extensions/mcporter/extension-manifest.json +0 -12
package/README.md
CHANGED
|
@@ -24,21 +24,19 @@ One command. Walk away. Come back to a built project with clean git history.
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
-
## What's New in v2.
|
|
28
|
-
|
|
29
|
-
- **
|
|
30
|
-
-
|
|
31
|
-
- **
|
|
32
|
-
-
|
|
33
|
-
- **
|
|
34
|
-
- **
|
|
35
|
-
- **
|
|
36
|
-
- **
|
|
37
|
-
-
|
|
38
|
-
- **
|
|
39
|
-
- **
|
|
40
|
-
- **Windows non-ASCII path support** — `cpSync` fallback for usernames with special characters
|
|
41
|
-
- **`needs-discussion` routing** — milestones with draft context now route to the interactive discussion flow instead of stopping
|
|
27
|
+
## What's New in v2.33
|
|
28
|
+
|
|
29
|
+
- **Dispatch loop hardening** — defensive guards, reentrancy protection, and 125 new regression tests covering the full `deriveState → resolveDispatch` chain without an LLM
|
|
30
|
+
- **Live regression test harness** — post-build pipeline validation that catches dispatch, parser, and lock lifecycle regressions before promotion
|
|
31
|
+
- **Unified error handling** — `getErrorMessage()` helper replaces 65 inline duplicates across the codebase
|
|
32
|
+
- **Centralized unit ID parsing** — `parseUnitId()` eliminates fragile regex patterns scattered across dispatch, recovery, and metrics code
|
|
33
|
+
- **Milestone merge consolidation** — `tryMergeMilestone()` replaces 4 duplicate merge paths in the auto-mode loop
|
|
34
|
+
- **Lock alignment fix** — retry lock path now matches primary lock settings, preventing `ECOMPROMISED` errors on resume
|
|
35
|
+
- **NixOS/nix-darwin support** — symlinks in `.gsd/` are skipped during `makeTreeWritable` to prevent `EPERM` failures
|
|
36
|
+
- **Windows EPERM fallback** — `.gsd/` migration uses copy+delete when NTFS blocks direct rename
|
|
37
|
+
- **Worktree identity fix** — stable project hash resolved from main repo root, not worktree path
|
|
38
|
+
- **Quick-task branch cleanup** — `/gsd quick` branches auto-merge back to the original branch after completion
|
|
39
|
+
- **Crash recovery guidance** — actionable next-step messages based on what was interrupted and what state survived
|
|
42
40
|
|
|
43
41
|
See the full [Changelog](./CHANGELOG.md) for details.
|
|
44
42
|
|
|
@@ -65,6 +63,7 @@ Full documentation is available in the [`docs/`](./docs/) directory:
|
|
|
65
63
|
- **[Visualizer](./docs/visualizer.md)** — workflow visualizer with stats and discussion status
|
|
66
64
|
- **[Remote Questions](./docs/remote-questions.md)** — route decisions to Slack or Discord when human input is needed
|
|
67
65
|
- **[Dynamic Model Routing](./docs/dynamic-model-routing.md)** — complexity-based model selection and budget pressure
|
|
66
|
+
- **[Pipeline Simplification (ADR-003)](./docs/ADR-003-pipeline-simplification.md)** — merged research into planning, mechanical completion
|
|
68
67
|
- **[Migration from v1](./docs/migration.md)** — `.planning` → `.gsd` migration
|
|
69
68
|
|
|
70
69
|
---
|
|
@@ -141,12 +140,12 @@ The iron rule: **a task must fit in one context window.** If it can't, it's two
|
|
|
141
140
|
Each slice flows through phases automatically:
|
|
142
141
|
|
|
143
142
|
```
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
Plan (with integrated research) → Execute (per task) → Complete → Reassess Roadmap → Next Slice
|
|
144
|
+
↓ (all slices done)
|
|
145
|
+
Validate Milestone → Complete Milestone
|
|
147
146
|
```
|
|
148
147
|
|
|
149
|
-
**
|
|
148
|
+
**Plan** scouts the codebase, researches relevant docs, and decomposes the slice into tasks with must-haves (mechanically verifiable outcomes). **Execute** runs each task in a fresh context window with only the relevant files pre-loaded — then runs configured verification commands (lint, test, etc.) with auto-fix retries. **Complete** writes the summary, UAT script, marks the roadmap, and commits with meaningful messages derived from task summaries. **Reassess** checks if the roadmap still makes sense given what was learned. **Validate Milestone** runs a reconciliation gate after all slices complete — comparing roadmap success criteria against actual results before sealing the milestone.
|
|
150
149
|
|
|
151
150
|
### `/gsd auto` — The Main Event
|
|
152
151
|
|
|
@@ -326,6 +325,7 @@ On first run, GSD launches a branded setup wizard that walks you through LLM pro
|
|
|
326
325
|
| `gsd headless [cmd]` | Run `/gsd` commands without TUI (CI, cron, scripts) |
|
|
327
326
|
| `gsd headless query` | Instant JSON snapshot — state, next dispatch, costs (no LLM) |
|
|
328
327
|
| `gsd --continue` (`-c`) | Resume the most recent session for the current directory |
|
|
328
|
+
| `gsd --worktree` (`-w`) | Launch an isolated worktree session for the active milestone |
|
|
329
329
|
| `gsd sessions` | Interactive session picker — browse and resume any saved session |
|
|
330
330
|
|
|
331
331
|
---
|
|
@@ -483,7 +483,7 @@ See the full [Token Optimization Guide](./docs/token-optimization.md) for detail
|
|
|
483
483
|
|
|
484
484
|
### Bundled Tools
|
|
485
485
|
|
|
486
|
-
GSD ships with
|
|
486
|
+
GSD ships with 18 extensions, all loaded automatically:
|
|
487
487
|
|
|
488
488
|
| Extension | What it provides |
|
|
489
489
|
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -503,6 +503,8 @@ GSD ships with 16 extensions, all loaded automatically:
|
|
|
503
503
|
| **Secure Env Collect** | Masked secret collection without manual .env editing |
|
|
504
504
|
| **Remote Questions** | Route decisions to Slack/Discord when human input is needed in headless/CI mode |
|
|
505
505
|
| **Universal Config** | Discover and import MCP servers and rules from other AI coding tools |
|
|
506
|
+
| **AWS Auth** | Automatic Bedrock credential refresh for AWS-hosted models |
|
|
507
|
+
| **TTSR** | Tool-use type-safe runtime validation |
|
|
506
508
|
|
|
507
509
|
### Bundled Agents
|
|
508
510
|
|
package/dist/resource-loader.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DefaultResourceLoader } from '@gsd/pi-coding-agent';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
|
-
import { chmodSync, copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { chmodSync, copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
5
5
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { compareSemver } from './update-check.js';
|
|
@@ -118,7 +118,12 @@ export function getNewerManagedResourceVersion(agentDir, currentVersion) {
|
|
|
118
118
|
function makeTreeWritable(dirPath) {
|
|
119
119
|
if (!existsSync(dirPath))
|
|
120
120
|
return;
|
|
121
|
-
|
|
121
|
+
// Use lstatSync to avoid following symlinks into immutable filesystems
|
|
122
|
+
// (e.g., Nix store on NixOS/nix-darwin). Symlinks don't carry their own
|
|
123
|
+
// permissions and their targets may be read-only by design (#1298).
|
|
124
|
+
const stats = lstatSync(dirPath);
|
|
125
|
+
if (stats.isSymbolicLink())
|
|
126
|
+
return;
|
|
122
127
|
const isDir = stats.isDirectory();
|
|
123
128
|
const currentMode = stats.mode & 0o777;
|
|
124
129
|
// Ensure owner-write; for directories also ensure owner-exec so they remain traversable.
|
|
@@ -127,7 +132,12 @@ function makeTreeWritable(dirPath) {
|
|
|
127
132
|
newMode |= 0o100;
|
|
128
133
|
}
|
|
129
134
|
if (newMode !== currentMode) {
|
|
130
|
-
|
|
135
|
+
try {
|
|
136
|
+
chmodSync(dirPath, newMode);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Non-fatal — may fail on read-only filesystems or insufficient permissions
|
|
140
|
+
}
|
|
131
141
|
}
|
|
132
142
|
if (isDir) {
|
|
133
143
|
for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
|
|
@@ -20,6 +20,7 @@ import { parseRoadmap, parsePlan } from "./files.js";
|
|
|
20
20
|
import { readFileSync, existsSync } from "node:fs";
|
|
21
21
|
import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
|
22
22
|
import { makeUI, GLYPH, INDENT } from "../shared/mod.js";
|
|
23
|
+
import { parseUnitId } from "./unit-id.js";
|
|
23
24
|
|
|
24
25
|
// ─── Dashboard Data ───────────────────────────────────────────────────────────
|
|
25
26
|
|
|
@@ -372,8 +373,9 @@ export function updateProgressWidget(
|
|
|
372
373
|
lines.push("");
|
|
373
374
|
|
|
374
375
|
const isHook = unitType.startsWith("hook/");
|
|
376
|
+
const hookParsed = isHook ? parseUnitId(unitId) : undefined;
|
|
375
377
|
const target = isHook
|
|
376
|
-
? (
|
|
378
|
+
? (hookParsed!.task ?? hookParsed!.slice ?? unitId)
|
|
377
379
|
: (task ? `${task.id}: ${task.title}` : unitId);
|
|
378
380
|
const actionLeft = `${pad}${theme.fg("accent", "▸")} ${theme.fg("accent", verb)} ${theme.fg("text", target)}`;
|
|
379
381
|
const tierTag = tierBadge ? theme.fg("dim", `[${tierBadge}] `) : "";
|
|
@@ -65,6 +65,28 @@ export function resetRewriteCircuitBreaker(): void {
|
|
|
65
65
|
rewriteAttemptCount = 0;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Guard for accessing activeSlice/activeTask in dispatch rules.
|
|
70
|
+
* Returns a stop action if the expected ref is null (corrupt state).
|
|
71
|
+
*/
|
|
72
|
+
function requireSlice(state: GSDState): { sid: string; sTitle: string } | DispatchAction {
|
|
73
|
+
if (!state.activeSlice) {
|
|
74
|
+
return { action: "stop", reason: `Phase "${state.phase}" but no active slice — run /gsd doctor.`, level: "error" };
|
|
75
|
+
}
|
|
76
|
+
return { sid: state.activeSlice.id, sTitle: state.activeSlice.title };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function requireTask(state: GSDState): { sid: string; sTitle: string; tid: string; tTitle: string } | DispatchAction {
|
|
80
|
+
if (!state.activeSlice || !state.activeTask) {
|
|
81
|
+
return { action: "stop", reason: `Phase "${state.phase}" but no active slice/task — run /gsd doctor.`, level: "error" };
|
|
82
|
+
}
|
|
83
|
+
return { sid: state.activeSlice.id, sTitle: state.activeSlice.title, tid: state.activeTask.id, tTitle: state.activeTask.title };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isStopAction(v: unknown): v is DispatchAction {
|
|
87
|
+
return typeof v === "object" && v !== null && "action" in v;
|
|
88
|
+
}
|
|
89
|
+
|
|
68
90
|
// ─── Rules ────────────────────────────────────────────────────────────────
|
|
69
91
|
|
|
70
92
|
const DISPATCH_RULES: DispatchRule[] = [
|
|
@@ -93,8 +115,9 @@ const DISPATCH_RULES: DispatchRule[] = [
|
|
|
93
115
|
name: "summarizing → complete-slice",
|
|
94
116
|
match: async ({ state, mid, midTitle, basePath }) => {
|
|
95
117
|
if (state.phase !== "summarizing") return null;
|
|
96
|
-
const
|
|
97
|
-
|
|
118
|
+
const sliceRef = requireSlice(state);
|
|
119
|
+
if (isStopAction(sliceRef)) return sliceRef as DispatchAction;
|
|
120
|
+
const { sid, sTitle } = sliceRef;
|
|
98
121
|
return {
|
|
99
122
|
action: "dispatch",
|
|
100
123
|
unitType: "complete-slice",
|
|
@@ -222,8 +245,9 @@ const DISPATCH_RULES: DispatchRule[] = [
|
|
|
222
245
|
if (state.phase !== "planning") return null;
|
|
223
246
|
// Phase skip: skip research when preference or profile says so
|
|
224
247
|
if (prefs?.phases?.skip_research || prefs?.phases?.skip_slice_research) return null;
|
|
225
|
-
const
|
|
226
|
-
|
|
248
|
+
const sliceRef = requireSlice(state);
|
|
249
|
+
if (isStopAction(sliceRef)) return sliceRef as DispatchAction;
|
|
250
|
+
const { sid, sTitle } = sliceRef;
|
|
227
251
|
const researchFile = resolveSliceFile(basePath, mid, sid, "RESEARCH");
|
|
228
252
|
if (researchFile) return null; // has research, fall through
|
|
229
253
|
// Skip slice research for S01 when milestone research already exists —
|
|
@@ -242,8 +266,9 @@ const DISPATCH_RULES: DispatchRule[] = [
|
|
|
242
266
|
name: "planning → plan-slice",
|
|
243
267
|
match: async ({ state, mid, midTitle, basePath }) => {
|
|
244
268
|
if (state.phase !== "planning") return null;
|
|
245
|
-
const
|
|
246
|
-
|
|
269
|
+
const sliceRef = requireSlice(state);
|
|
270
|
+
if (isStopAction(sliceRef)) return sliceRef as DispatchAction;
|
|
271
|
+
const { sid, sTitle } = sliceRef;
|
|
247
272
|
return {
|
|
248
273
|
action: "dispatch",
|
|
249
274
|
unitType: "plan-slice",
|
|
@@ -256,8 +281,9 @@ const DISPATCH_RULES: DispatchRule[] = [
|
|
|
256
281
|
name: "replanning-slice → replan-slice",
|
|
257
282
|
match: async ({ state, mid, midTitle, basePath }) => {
|
|
258
283
|
if (state.phase !== "replanning-slice") return null;
|
|
259
|
-
const
|
|
260
|
-
|
|
284
|
+
const sliceRef = requireSlice(state);
|
|
285
|
+
if (isStopAction(sliceRef)) return sliceRef as DispatchAction;
|
|
286
|
+
const { sid, sTitle } = sliceRef;
|
|
261
287
|
return {
|
|
262
288
|
action: "dispatch",
|
|
263
289
|
unitType: "replan-slice",
|
|
@@ -270,8 +296,9 @@ const DISPATCH_RULES: DispatchRule[] = [
|
|
|
270
296
|
name: "executing → execute-task (recover missing task plan → plan-slice)",
|
|
271
297
|
match: async ({ state, mid, midTitle, basePath }) => {
|
|
272
298
|
if (state.phase !== "executing" || !state.activeTask) return null;
|
|
273
|
-
const
|
|
274
|
-
|
|
299
|
+
const sliceRef = requireSlice(state);
|
|
300
|
+
if (isStopAction(sliceRef)) return sliceRef as DispatchAction;
|
|
301
|
+
const { sid, sTitle } = sliceRef;
|
|
275
302
|
const tid = state.activeTask.id;
|
|
276
303
|
|
|
277
304
|
// Guard: if the slice plan exists but the individual task plan files are
|
|
@@ -296,8 +323,9 @@ const DISPATCH_RULES: DispatchRule[] = [
|
|
|
296
323
|
name: "executing → execute-task",
|
|
297
324
|
match: async ({ state, mid, basePath }) => {
|
|
298
325
|
if (state.phase !== "executing" || !state.activeTask) return null;
|
|
299
|
-
const
|
|
300
|
-
|
|
326
|
+
const sliceRef = requireSlice(state);
|
|
327
|
+
if (isStopAction(sliceRef)) return sliceRef as DispatchAction;
|
|
328
|
+
const { sid, sTitle } = sliceRef;
|
|
301
329
|
const tid = state.activeTask.id;
|
|
302
330
|
const tTitle = state.activeTask.title;
|
|
303
331
|
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import { resolveMilestoneFile } from "./paths.js";
|
|
19
19
|
import { MAX_CONSECUTIVE_SKIPS, MAX_LIFETIME_DISPATCHES } from "./auto/session.js";
|
|
20
20
|
import type { AutoSession } from "./auto/session.js";
|
|
21
|
+
import { parseUnitId } from "./unit-id.js";
|
|
21
22
|
|
|
22
23
|
export interface IdempotencyContext {
|
|
23
24
|
s: AutoSession;
|
|
@@ -54,7 +55,7 @@ export function checkIdempotency(ictx: IdempotencyContext): IdempotencyResult {
|
|
|
54
55
|
s.unitConsecutiveSkips.set(idempotencyKey, skipCount);
|
|
55
56
|
if (skipCount > MAX_CONSECUTIVE_SKIPS) {
|
|
56
57
|
// Cross-check: verify the unit's milestone is still active (#790)
|
|
57
|
-
const skippedMid = unitId.
|
|
58
|
+
const skippedMid = parseUnitId(unitId).milestone;
|
|
58
59
|
const skippedMilestoneComplete = skippedMid
|
|
59
60
|
? !!resolveMilestoneFile(basePath, skippedMid, "SUMMARY")
|
|
60
61
|
: false;
|
|
@@ -110,7 +111,7 @@ export function checkIdempotency(ictx: IdempotencyContext): IdempotencyResult {
|
|
|
110
111
|
const skipCount2 = (s.unitConsecutiveSkips.get(idempotencyKey) ?? 0) + 1;
|
|
111
112
|
s.unitConsecutiveSkips.set(idempotencyKey, skipCount2);
|
|
112
113
|
if (skipCount2 > MAX_CONSECUTIVE_SKIPS) {
|
|
113
|
-
const skippedMid2 = unitId.
|
|
114
|
+
const skippedMid2 = parseUnitId(unitId).milestone;
|
|
114
115
|
const skippedMilestoneComplete2 = skippedMid2
|
|
115
116
|
? !!resolveMilestoneFile(basePath, skippedMid2, "SUMMARY")
|
|
116
117
|
: false;
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
formatValidationIssues,
|
|
13
13
|
} from "./observability-validator.js";
|
|
14
14
|
import type { ValidationIssue } from "./observability-validator.js";
|
|
15
|
+
import { parseUnitId } from "./unit-id.js";
|
|
15
16
|
|
|
16
17
|
export async function collectObservabilityWarnings(
|
|
17
18
|
ctx: ExtensionContext,
|
|
@@ -22,10 +23,7 @@ export async function collectObservabilityWarnings(
|
|
|
22
23
|
// Hook units have custom artifacts — skip standard observability checks
|
|
23
24
|
if (unitType.startsWith("hook/")) return [];
|
|
24
25
|
|
|
25
|
-
const
|
|
26
|
-
const mid = parts[0];
|
|
27
|
-
const sid = parts[1];
|
|
28
|
-
const tid = parts[2];
|
|
26
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
29
27
|
|
|
30
28
|
if (!mid || !sid) return [];
|
|
31
29
|
|
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
} from "./auto-dashboard.js";
|
|
62
62
|
import { join } from "node:path";
|
|
63
63
|
import { STATE_REBUILD_MIN_INTERVAL_MS } from "./auto-constants.js";
|
|
64
|
+
import { parseUnitId } from "./unit-id.js";
|
|
64
65
|
|
|
65
66
|
/**
|
|
66
67
|
* Initialize a unit dispatch: stamp the current time, set `s.currentUnit`,
|
|
@@ -134,8 +135,7 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
134
135
|
let taskContext: TaskCommitContext | undefined;
|
|
135
136
|
|
|
136
137
|
if (s.currentUnit.type === "execute-task") {
|
|
137
|
-
const
|
|
138
|
-
const [mid, sid, tid] = parts;
|
|
138
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(s.currentUnit.id);
|
|
139
139
|
if (mid && sid && tid) {
|
|
140
140
|
const summaryPath = resolveTaskFile(s.basePath, mid, sid, tid, "SUMMARY");
|
|
141
141
|
if (summaryPath) {
|
|
@@ -167,8 +167,8 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
167
167
|
|
|
168
168
|
// Doctor: fix mechanical bookkeeping
|
|
169
169
|
try {
|
|
170
|
-
const
|
|
171
|
-
const doctorScope =
|
|
170
|
+
const { milestone, slice } = parseUnitId(s.currentUnit.id);
|
|
171
|
+
const doctorScope = slice ? `${milestone}/${slice}` : milestone;
|
|
172
172
|
const sliceTerminalUnits = new Set(["complete-slice", "run-uat"]);
|
|
173
173
|
const effectiveFixLevel = sliceTerminalUnits.has(s.currentUnit.type) ? "all" as const : "task" as const;
|
|
174
174
|
const report = await runGSDDoctor(s.basePath, { fix: true, scope: doctorScope, fixLevel: effectiveFixLevel });
|
|
@@ -348,7 +348,7 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
348
348
|
// instead of dispatching LLM sessions for complete-slice / validate-milestone.
|
|
349
349
|
if (s.currentUnit?.type === "execute-task" && !s.stepMode) {
|
|
350
350
|
try {
|
|
351
|
-
const
|
|
351
|
+
const { milestone: mid, slice: sid } = parseUnitId(s.currentUnit.id);
|
|
352
352
|
if (mid && sid) {
|
|
353
353
|
const state = await deriveState(s.basePath);
|
|
354
354
|
if (state.phase === "summarizing" && state.activeSlice?.id === sid) {
|
|
@@ -189,30 +189,52 @@ export async function inlineGsdRootFile(
|
|
|
189
189
|
// ─── DB-Aware Inline Helpers ──────────────────────────────────────────────
|
|
190
190
|
|
|
191
191
|
/**
|
|
192
|
-
*
|
|
193
|
-
*
|
|
192
|
+
* Shared DB-fallback pattern: attempt a DB query via the context-store, format
|
|
193
|
+
* the result, and fall back to the filesystem file when the DB is unavailable
|
|
194
|
+
* or the query yields no results.
|
|
195
|
+
*
|
|
196
|
+
* @param base Project root for filesystem fallback
|
|
197
|
+
* @param label Section heading (e.g. "Decisions")
|
|
198
|
+
* @param filename Filesystem fallback file (e.g. "decisions.md")
|
|
199
|
+
* @param queryDb Async callback receiving the dynamically-imported
|
|
200
|
+
* context-store module. Returns formatted markdown or null.
|
|
194
201
|
*/
|
|
195
|
-
|
|
196
|
-
base: string,
|
|
202
|
+
async function inlineFromDbOrFile(
|
|
203
|
+
base: string,
|
|
204
|
+
label: string,
|
|
205
|
+
filename: string,
|
|
206
|
+
queryDb: (cs: typeof import("./context-store.js")) => string | null,
|
|
197
207
|
): Promise<string | null> {
|
|
198
|
-
const inlineLevel = level ?? resolveInlineLevel();
|
|
199
208
|
try {
|
|
200
209
|
const { isDbAvailable } = await import("./gsd-db.js");
|
|
201
210
|
if (isDbAvailable()) {
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
const formatted = inlineLevel !== "full"
|
|
207
|
-
? formatDecisionsCompact(decisions)
|
|
208
|
-
: formatDecisionsForPrompt(decisions);
|
|
209
|
-
return `### Decisions\nSource: \`.gsd/DECISIONS.md\`\n\n${formatted}`;
|
|
211
|
+
const contextStore = await import("./context-store.js");
|
|
212
|
+
const content = queryDb(contextStore);
|
|
213
|
+
if (content) {
|
|
214
|
+
return `### ${label}\nSource: \`.gsd/${filename.toUpperCase().replace(/\.MD$/i, "")}.md\`\n\n${content}`;
|
|
210
215
|
}
|
|
211
216
|
}
|
|
212
217
|
} catch {
|
|
213
218
|
// DB not available — fall through to filesystem
|
|
214
219
|
}
|
|
215
|
-
return inlineGsdRootFile(base,
|
|
220
|
+
return inlineGsdRootFile(base, filename, label);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Inline decisions with optional milestone scoping from the DB.
|
|
225
|
+
* Falls back to filesystem via inlineGsdRootFile when DB unavailable or empty.
|
|
226
|
+
*/
|
|
227
|
+
export async function inlineDecisionsFromDb(
|
|
228
|
+
base: string, milestoneId?: string, scope?: string, level?: InlineLevel,
|
|
229
|
+
): Promise<string | null> {
|
|
230
|
+
const inlineLevel = level ?? resolveInlineLevel();
|
|
231
|
+
return inlineFromDbOrFile(base, "Decisions", "decisions.md", (cs) => {
|
|
232
|
+
const decisions = cs.queryDecisions({ milestoneId, scope });
|
|
233
|
+
if (decisions.length === 0) return null;
|
|
234
|
+
return inlineLevel !== "full"
|
|
235
|
+
? formatDecisionsCompact(decisions)
|
|
236
|
+
: cs.formatDecisionsForPrompt(decisions);
|
|
237
|
+
});
|
|
216
238
|
}
|
|
217
239
|
|
|
218
240
|
/**
|
|
@@ -223,23 +245,13 @@ export async function inlineRequirementsFromDb(
|
|
|
223
245
|
base: string, sliceId?: string, level?: InlineLevel,
|
|
224
246
|
): Promise<string | null> {
|
|
225
247
|
const inlineLevel = level ?? resolveInlineLevel();
|
|
226
|
-
|
|
227
|
-
const {
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const formatted = inlineLevel !== "full"
|
|
234
|
-
? formatRequirementsCompact(requirements)
|
|
235
|
-
: formatRequirementsForPrompt(requirements);
|
|
236
|
-
return `### Requirements\nSource: \`.gsd/REQUIREMENTS.md\`\n\n${formatted}`;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
} catch {
|
|
240
|
-
// DB not available — fall through to filesystem
|
|
241
|
-
}
|
|
242
|
-
return inlineGsdRootFile(base, "requirements.md", "Requirements");
|
|
248
|
+
return inlineFromDbOrFile(base, "Requirements", "requirements.md", (cs) => {
|
|
249
|
+
const requirements = cs.queryRequirements({ sliceId });
|
|
250
|
+
if (requirements.length === 0) return null;
|
|
251
|
+
return inlineLevel !== "full"
|
|
252
|
+
? formatRequirementsCompact(requirements)
|
|
253
|
+
: cs.formatRequirementsForPrompt(requirements);
|
|
254
|
+
});
|
|
243
255
|
}
|
|
244
256
|
|
|
245
257
|
/**
|
|
@@ -249,19 +261,9 @@ export async function inlineRequirementsFromDb(
|
|
|
249
261
|
export async function inlineProjectFromDb(
|
|
250
262
|
base: string,
|
|
251
263
|
): Promise<string | null> {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const { queryProject } = await import("./context-store.js");
|
|
256
|
-
const content = queryProject();
|
|
257
|
-
if (content) {
|
|
258
|
-
return `### Project\nSource: \`.gsd/PROJECT.md\`\n\n${content}`;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
} catch {
|
|
262
|
-
// DB not available — fall through to filesystem
|
|
263
|
-
}
|
|
264
|
-
return inlineGsdRootFile(base, "project.md", "Project");
|
|
264
|
+
return inlineFromDbOrFile(base, "Project", "project.md", (cs) => {
|
|
265
|
+
return cs.queryProject();
|
|
266
|
+
});
|
|
265
267
|
}
|
|
266
268
|
|
|
267
269
|
// ─── Skill Discovery ──────────────────────────────────────────────────────
|
|
@@ -42,6 +42,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "
|
|
|
42
42
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
43
43
|
import { loadJsonFileOrNull } from "./json-persistence.js";
|
|
44
44
|
import { dirname, join } from "node:path";
|
|
45
|
+
import { parseUnitId } from "./unit-id.js";
|
|
45
46
|
|
|
46
47
|
// ─── Artifact Resolution & Verification ───────────────────────────────────────
|
|
47
48
|
|
|
@@ -49,9 +50,7 @@ import { dirname, join } from "node:path";
|
|
|
49
50
|
* Resolve the expected artifact for a unit to an absolute path.
|
|
50
51
|
*/
|
|
51
52
|
export function resolveExpectedArtifactPath(unitType: string, unitId: string, base: string): string | null {
|
|
52
|
-
const
|
|
53
|
-
const mid = parts[0]!;
|
|
54
|
-
const sid = parts[1];
|
|
53
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
55
54
|
switch (unitType) {
|
|
56
55
|
case "research-milestone": {
|
|
57
56
|
const dir = resolveMilestonePath(base, mid);
|
|
@@ -78,7 +77,6 @@ export function resolveExpectedArtifactPath(unitType: string, unitId: string, ba
|
|
|
78
77
|
return dir ? join(dir, buildSliceFileName(sid!, "UAT-RESULT")) : null;
|
|
79
78
|
}
|
|
80
79
|
case "execute-task": {
|
|
81
|
-
const tid = parts[2];
|
|
82
80
|
const dir = resolveSlicePath(base, mid, sid!);
|
|
83
81
|
return dir && tid ? join(dir, "tasks", buildTaskFileName(tid, "SUMMARY")) : null;
|
|
84
82
|
}
|
|
@@ -167,10 +165,7 @@ export function verifyExpectedArtifact(unitType: string, unitId: string, base: s
|
|
|
167
165
|
|
|
168
166
|
// execute-task must also have its checkbox marked [x] in the slice plan
|
|
169
167
|
if (unitType === "execute-task") {
|
|
170
|
-
const
|
|
171
|
-
const mid = parts[0];
|
|
172
|
-
const sid = parts[1];
|
|
173
|
-
const tid = parts[2];
|
|
168
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
174
169
|
if (mid && sid && tid) {
|
|
175
170
|
const planAbs = resolveSliceFile(base, mid, sid, "PLAN");
|
|
176
171
|
if (planAbs && existsSync(planAbs)) {
|
|
@@ -187,9 +182,7 @@ export function verifyExpectedArtifact(unitType: string, unitId: string, base: s
|
|
|
187
182
|
// but omitted T{tid}-PLAN.md files would be marked complete, causing execute-task
|
|
188
183
|
// to dispatch with a missing task plan (see issue #739).
|
|
189
184
|
if (unitType === "plan-slice") {
|
|
190
|
-
const
|
|
191
|
-
const mid = parts[0];
|
|
192
|
-
const sid = parts[1];
|
|
185
|
+
const { milestone: mid, slice: sid } = parseUnitId(unitId);
|
|
193
186
|
if (mid && sid) {
|
|
194
187
|
try {
|
|
195
188
|
const planContent = readFileSync(absPath, "utf-8");
|
|
@@ -213,9 +206,8 @@ export function verifyExpectedArtifact(unitType: string, unitId: string, base: s
|
|
|
213
206
|
// state machine keeps returning the same complete-slice unit (roadmap still shows
|
|
214
207
|
// the slice incomplete), so dispatchNextUnit recurses forever.
|
|
215
208
|
if (unitType === "complete-slice") {
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
const sid = parts[1];
|
|
209
|
+
const { milestone: mid, slice: sid } = parseUnitId(unitId);
|
|
210
|
+
|
|
219
211
|
if (mid && sid) {
|
|
220
212
|
const dir = resolveSlicePath(base, mid, sid);
|
|
221
213
|
if (dir) {
|
|
@@ -268,9 +260,7 @@ export function writeBlockerPlaceholder(unitType: string, unitId: string, base:
|
|
|
268
260
|
}
|
|
269
261
|
|
|
270
262
|
export function diagnoseExpectedArtifact(unitType: string, unitId: string, base: string): string | null {
|
|
271
|
-
const
|
|
272
|
-
const mid = parts[0];
|
|
273
|
-
const sid = parts[1];
|
|
263
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
274
264
|
switch (unitType) {
|
|
275
265
|
case "research-milestone":
|
|
276
266
|
return `${relMilestoneFile(base, mid!, "RESEARCH")} (milestone research)`;
|
|
@@ -281,7 +271,6 @@ export function diagnoseExpectedArtifact(unitType: string, unitId: string, base:
|
|
|
281
271
|
case "plan-slice":
|
|
282
272
|
return `${relSliceFile(base, mid!, sid!, "PLAN")} (slice plan)`;
|
|
283
273
|
case "execute-task": {
|
|
284
|
-
const tid = parts[2];
|
|
285
274
|
return `Task ${tid} marked [x] in ${relSliceFile(base, mid!, sid!, "PLAN")} + summary written`;
|
|
286
275
|
}
|
|
287
276
|
case "complete-slice":
|
|
@@ -539,10 +528,7 @@ export async function selfHealRuntimeRecords(
|
|
|
539
528
|
* These are shown when automatic reconciliation is not possible.
|
|
540
529
|
*/
|
|
541
530
|
export function buildLoopRemediationSteps(unitType: string, unitId: string, base: string): string | null {
|
|
542
|
-
const
|
|
543
|
-
const mid = parts[0];
|
|
544
|
-
const sid = parts[1];
|
|
545
|
-
const tid = parts[2];
|
|
531
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
546
532
|
switch (unitType) {
|
|
547
533
|
case "execute-task": {
|
|
548
534
|
if (!mid || !sid || !tid) break;
|
|
@@ -63,6 +63,8 @@ import { debugLog, enableDebug, isDebugEnabled, getDebugLogPath } from "./debug-
|
|
|
63
63
|
import type { AutoSession } from "./auto/session.js";
|
|
64
64
|
import { existsSync, mkdirSync, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
65
65
|
import { join } from "node:path";
|
|
66
|
+
import { getErrorMessage } from "./error-utils.js";
|
|
67
|
+
import { parseUnitId } from "./unit-id.js";
|
|
66
68
|
|
|
67
69
|
export interface BootstrapDeps {
|
|
68
70
|
shouldUseWorktreeIsolation: () => boolean;
|
|
@@ -138,7 +140,7 @@ export async function bootstrapAutoSession(
|
|
|
138
140
|
if (crashLock && crashLock.pid !== process.pid) {
|
|
139
141
|
// We already hold the session lock, so no concurrent session is running.
|
|
140
142
|
// The crash lock is from a dead process — recover context from it.
|
|
141
|
-
const recoveredMid = crashLock.unitId.
|
|
143
|
+
const recoveredMid = parseUnitId(crashLock.unitId).milestone;
|
|
142
144
|
const milestoneAlreadyComplete = recoveredMid
|
|
143
145
|
? !!resolveMilestoneFile(base, recoveredMid, "SUMMARY")
|
|
144
146
|
: false;
|
|
@@ -201,11 +203,11 @@ export async function bootstrapAutoSession(
|
|
|
201
203
|
if (!midMatch) continue;
|
|
202
204
|
const mid = midMatch[1];
|
|
203
205
|
if (resolveMilestoneFile(base, mid, "SUMMARY")) {
|
|
204
|
-
try { unlinkSync(join(runtimeUnitsDir, file)); } catch (e) { debugLog("stale-unit-cleanup-failed", { file, error:
|
|
206
|
+
try { unlinkSync(join(runtimeUnitsDir, file)); } catch (e) { debugLog("stale-unit-cleanup-failed", { file, error: getErrorMessage(e) }); }
|
|
205
207
|
}
|
|
206
208
|
}
|
|
207
209
|
}
|
|
208
|
-
} catch (e) { debugLog("stale-unit-dir-cleanup-failed", { error:
|
|
210
|
+
} catch (e) { debugLog("stale-unit-dir-cleanup-failed", { error: getErrorMessage(e) }); }
|
|
209
211
|
|
|
210
212
|
let state = await deriveState(base);
|
|
211
213
|
|
|
@@ -343,7 +345,7 @@ export async function bootstrapAutoSession(
|
|
|
343
345
|
registerSigtermHandler(s.originalBasePath);
|
|
344
346
|
} catch (err) {
|
|
345
347
|
ctx.ui.notify(
|
|
346
|
-
`Auto-worktree setup failed: ${
|
|
348
|
+
`Auto-worktree setup failed: ${getErrorMessage(err)}. Continuing in project root.`,
|
|
347
349
|
"warning",
|
|
348
350
|
);
|
|
349
351
|
}
|
|
@@ -435,7 +437,7 @@ export async function bootstrapAutoSession(
|
|
|
435
437
|
}
|
|
436
438
|
} catch (err) {
|
|
437
439
|
ctx.ui.notify(
|
|
438
|
-
`Secrets check error: ${
|
|
440
|
+
`Secrets check error: ${getErrorMessage(err)}. Continuing without secrets.`,
|
|
439
441
|
"warning",
|
|
440
442
|
);
|
|
441
443
|
}
|
|
@@ -453,7 +455,7 @@ export async function bootstrapAutoSession(
|
|
|
453
455
|
ctx.ui.notify("Removed stale .git/index.lock from prior crash.", "info");
|
|
454
456
|
}
|
|
455
457
|
}
|
|
456
|
-
} catch (e) { debugLog("git-lock-cleanup-failed", { error:
|
|
458
|
+
} catch (e) { debugLog("git-lock-cleanup-failed", { error: getErrorMessage(e) }); }
|
|
457
459
|
|
|
458
460
|
// Pre-flight: validate milestone queue
|
|
459
461
|
try {
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
import type { AutoSession } from "./auto/session.js";
|
|
40
40
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
41
41
|
import { join } from "node:path";
|
|
42
|
+
import { parseUnitId } from "./unit-id.js";
|
|
42
43
|
|
|
43
44
|
export interface StuckContext {
|
|
44
45
|
s: AutoSession;
|
|
@@ -99,7 +100,7 @@ export async function checkStuckAndRecover(sctx: StuckContext): Promise<StuckRes
|
|
|
99
100
|
|
|
100
101
|
// Final reconciliation pass for execute-task
|
|
101
102
|
if (unitType === "execute-task") {
|
|
102
|
-
const
|
|
103
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
103
104
|
if (mid && sid && tid) {
|
|
104
105
|
const status = await inspectExecuteTaskDurability(basePath, unitId);
|
|
105
106
|
if (status) {
|
|
@@ -168,7 +169,7 @@ export async function checkStuckAndRecover(sctx: StuckContext): Promise<StuckRes
|
|
|
168
169
|
// Adaptive self-repair: each retry attempts a different remediation step.
|
|
169
170
|
if (unitType === "execute-task") {
|
|
170
171
|
const status = await inspectExecuteTaskDurability(basePath, unitId);
|
|
171
|
-
const
|
|
172
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
172
173
|
if (status && mid && sid && tid) {
|
|
173
174
|
if (status.summaryExists && !status.taskChecked) {
|
|
174
175
|
const repaired = skipExecuteTask(basePath, mid, sid, tid, status, "self-repair", 0);
|