@opengsd/gsd-pi 1.3.0-dev.65546769 → 1.3.0-dev.eed73bea
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/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +11 -2
- package/dist/resources/extensions/google-cli/stream-adapter.js +82 -15
- package/dist/resources/extensions/gsd/auto/orchestrator.js +12 -3
- package/dist/resources/extensions/gsd/auto-dispatch.js +17 -14
- package/dist/resources/extensions/gsd/auto-prompts.js +43 -12
- package/dist/resources/extensions/gsd/auto-recovery.js +13 -6
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +103 -13
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +6 -1
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +8 -3
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +46 -19
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +75 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -1
- package/dist/resources/extensions/gsd/commands-context.js +19 -1
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +16 -10
- package/dist/resources/extensions/gsd/commands-worktree.js +12 -10
- package/dist/resources/extensions/gsd/dashboard-overlay.js +32 -3
- package/dist/resources/extensions/gsd/db/queries.js +60 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +92 -8
- package/dist/resources/extensions/gsd/exec-sandbox.js +45 -9
- package/dist/resources/extensions/gsd/forensics.js +2 -32
- package/dist/resources/extensions/gsd/git-service.js +4 -4
- package/dist/resources/extensions/gsd/guided-flow-queue.js +59 -5
- package/dist/resources/extensions/gsd/health-widget.js +55 -29
- package/dist/resources/extensions/gsd/markdown-renderer.js +6 -2
- package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +44 -21
- package/dist/resources/extensions/gsd/milestone-implementation-evidence.js +26 -20
- package/dist/resources/extensions/gsd/quick.js +45 -2
- package/dist/resources/extensions/gsd/session-forensics.js +11 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +52 -3
- package/dist/resources/extensions/gsd/tools/complete-slice.js +34 -3
- package/dist/resources/extensions/gsd/tools/complete-task.js +78 -16
- package/dist/resources/extensions/gsd/tools/exec-tool.js +7 -2
- package/dist/resources/extensions/gsd/unit-context-composer.js +23 -7
- package/dist/resources/extensions/gsd/unit-registry.js +25 -3
- package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +33 -3
- package/dist/resources/extensions/gsd/validation-block-guard.js +9 -4
- package/dist/resources/extensions/gsd/workspace-git-preflight.js +30 -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 +14 -14
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/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 +14 -14
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/{796.e0bdc932325d7e03.js → 796.3976108148518f7d.js} +3 -3
- package/dist/web/standalone/.next/static/chunks/{webpack-f46ea08200a0227e.js → webpack-7c1d97e39be2da11.js} +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/workflow.d.ts +1 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +2 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +21 -9
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/README.md +1 -1
- package/packages/mcp-server/dist/server.d.ts +1 -1
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +3 -3
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +13 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +34 -20
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +20 -2
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +80 -0
- package/src/resources/extensions/google-cli/stream-adapter.ts +106 -19
- package/src/resources/extensions/gsd/auto/orchestrator.ts +25 -11
- package/src/resources/extensions/gsd/auto-dispatch.ts +18 -17
- package/src/resources/extensions/gsd/auto-prompts.ts +54 -12
- package/src/resources/extensions/gsd/auto-recovery.ts +13 -6
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +125 -12
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +6 -1
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +9 -3
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +52 -18
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +82 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -1
- package/src/resources/extensions/gsd/commands-context.ts +18 -1
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +14 -9
- package/src/resources/extensions/gsd/commands-worktree.ts +12 -10
- package/src/resources/extensions/gsd/dashboard-overlay.ts +32 -3
- package/src/resources/extensions/gsd/db/queries.ts +79 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +103 -9
- package/src/resources/extensions/gsd/exec-sandbox.ts +49 -9
- package/src/resources/extensions/gsd/forensics.ts +2 -33
- package/src/resources/extensions/gsd/git-service.ts +5 -5
- package/src/resources/extensions/gsd/guided-flow-queue.ts +82 -4
- package/src/resources/extensions/gsd/health-widget.ts +69 -32
- package/src/resources/extensions/gsd/markdown-renderer.ts +6 -1
- package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +51 -19
- package/src/resources/extensions/gsd/milestone-implementation-evidence.ts +35 -21
- package/src/resources/extensions/gsd/quick.ts +43 -2
- package/src/resources/extensions/gsd/session-forensics.ts +11 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +76 -8
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +111 -1
- package/src/resources/extensions/gsd/tests/commands-context.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/commands-worktree-clean.test.ts +80 -0
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +48 -8
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +55 -2
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/doctor-forensics-db-open-regression.test.ts +70 -2
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/exec-graceful-kill.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/exec-tool.test.ts +45 -1
- package/src/resources/extensions/gsd/tests/forensics-error-filter.test.ts +88 -0
- package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +268 -3
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +119 -1
- package/src/resources/extensions/gsd/tests/integration/queue-active-milestone-context-budget.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/integration/quick-branch-lifecycle.test.ts +56 -9
- package/src/resources/extensions/gsd/tests/knowledge-cold-start.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +78 -0
- package/src/resources/extensions/gsd/tests/orchestrator-logs.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +54 -1
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +195 -1
- package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +185 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +193 -14
- package/src/resources/extensions/gsd/tests/unmerged-milestone-guard.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +151 -2
- package/src/resources/extensions/gsd/tools/complete-slice.ts +30 -3
- package/src/resources/extensions/gsd/tools/complete-task.ts +86 -16
- package/src/resources/extensions/gsd/tools/exec-tool.ts +7 -3
- package/src/resources/extensions/gsd/unit-context-composer.ts +33 -7
- package/src/resources/extensions/gsd/unit-registry.ts +25 -3
- package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +41 -5
- package/src/resources/extensions/gsd/validation-block-guard.ts +13 -7
- package/src/resources/extensions/gsd/workspace-git-preflight.ts +31 -0
- /package/dist/web/standalone/.next/static/{BTKtGFF1Y-hvVJEGhBRo9 → SzEuqWX37DR9MEpEuQjP1}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{BTKtGFF1Y-hvVJEGhBRo9 → SzEuqWX37DR9MEpEuQjP1}/_ssgManifest.js +0 -0
|
@@ -94,27 +94,48 @@ function getActiveDecisions() {
|
|
|
94
94
|
return [];
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
*/
|
|
103
|
-
function memoryHasSourceMarker(markerKey, value) {
|
|
97
|
+
function emptyMemorySourceMarkers() {
|
|
98
|
+
return { decisionIds: new Set(), knowledgeIds: new Set() };
|
|
99
|
+
}
|
|
100
|
+
function getMemorySourceMarkers() {
|
|
101
|
+
const markers = emptyMemorySourceMarkers();
|
|
104
102
|
if (!isDbAvailable())
|
|
105
|
-
return
|
|
103
|
+
return markers;
|
|
106
104
|
const adapter = _getAdapter();
|
|
107
105
|
if (!adapter)
|
|
108
|
-
return
|
|
106
|
+
return markers;
|
|
109
107
|
try {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
.
|
|
113
|
-
|
|
114
|
-
|
|
108
|
+
const rows = adapter
|
|
109
|
+
.prepare("SELECT structured_fields FROM memories WHERE structured_fields IS NOT NULL")
|
|
110
|
+
.all();
|
|
111
|
+
for (const row of rows) {
|
|
112
|
+
collectMemorySourceMarker(markers, row["structured_fields"]);
|
|
113
|
+
}
|
|
115
114
|
}
|
|
116
115
|
catch {
|
|
117
|
-
return
|
|
116
|
+
return markers;
|
|
117
|
+
}
|
|
118
|
+
return markers;
|
|
119
|
+
}
|
|
120
|
+
function collectMemorySourceMarker(markers, raw) {
|
|
121
|
+
if (typeof raw !== "string" || raw.length === 0)
|
|
122
|
+
return;
|
|
123
|
+
try {
|
|
124
|
+
const parsed = JSON.parse(raw);
|
|
125
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
126
|
+
return;
|
|
127
|
+
const fields = parsed;
|
|
128
|
+
const decisionId = fields["sourceDecisionId"];
|
|
129
|
+
if (typeof decisionId === "string" && decisionId.length > 0) {
|
|
130
|
+
markers.decisionIds.add(decisionId);
|
|
131
|
+
}
|
|
132
|
+
const knowledgeId = fields["sourceKnowledgeId"];
|
|
133
|
+
if (typeof knowledgeId === "string" && knowledgeId.length > 0) {
|
|
134
|
+
markers.knowledgeIds.add(knowledgeId);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return;
|
|
118
139
|
}
|
|
119
140
|
}
|
|
120
141
|
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
@@ -127,10 +148,14 @@ const SAMPLE_LIMIT = 5;
|
|
|
127
148
|
export function scanConsolidationGaps(basePath) {
|
|
128
149
|
// ── Decisions ────────────────────────────────────────────────────────
|
|
129
150
|
const decisions = getActiveDecisions();
|
|
151
|
+
const knowledgeRows = parseKnowledgeRows(knowledgeMdContent(basePath));
|
|
152
|
+
const memorySourceMarkers = decisions.length > 0 || knowledgeRows.length > 0
|
|
153
|
+
? getMemorySourceMarkers()
|
|
154
|
+
: emptyMemorySourceMarkers();
|
|
130
155
|
const decisionSamples = [];
|
|
131
156
|
let decisionMigrated = 0;
|
|
132
157
|
for (const decision of decisions) {
|
|
133
|
-
if (
|
|
158
|
+
if (memorySourceMarkers.decisionIds.has(decision.id)) {
|
|
134
159
|
decisionMigrated += 1;
|
|
135
160
|
continue;
|
|
136
161
|
}
|
|
@@ -142,16 +167,14 @@ export function scanConsolidationGaps(basePath) {
|
|
|
142
167
|
}
|
|
143
168
|
}
|
|
144
169
|
// ── KNOWLEDGE.md ─────────────────────────────────────────────────────
|
|
145
|
-
const knowledgeRows = parseKnowledgeRows(knowledgeMdContent(basePath));
|
|
146
170
|
const knowledgeByTable = { rules: 0, patterns: 0, lessons: 0 };
|
|
147
171
|
const knowledgeSamples = [];
|
|
148
172
|
let knowledgeMigrated = 0;
|
|
149
173
|
for (const row of knowledgeRows) {
|
|
150
174
|
knowledgeByTable[row.table] += 1;
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
if (memoryHasSourceMarker("sourceKnowledgeId", row.id)) {
|
|
175
|
+
// KNOWLEDGE.md backfill writes `sourceKnowledgeId`; rows without that
|
|
176
|
+
// marker are still reported as consolidation gaps.
|
|
177
|
+
if (memorySourceMarkers.knowledgeIds.has(row.id)) {
|
|
155
178
|
knowledgeMigrated += 1;
|
|
156
179
|
continue;
|
|
157
180
|
}
|
|
@@ -10,6 +10,8 @@ import { logWarning } from "./workflow-logger.js";
|
|
|
10
10
|
import { resolveTasksDir } from "./paths.js";
|
|
11
11
|
/** Large enough for unbounded milestone-history git log scans in big repos. */
|
|
12
12
|
const GIT_LOG_MAX_BUFFER = 16 * 1024 * 1024;
|
|
13
|
+
const LOG_FIELD_SEPARATOR = "\x1f";
|
|
14
|
+
const LOG_RECORD_SEPARATOR = "\x1e";
|
|
13
15
|
/**
|
|
14
16
|
* Check whether a milestone produced implementation artifacts (non-`.gsd/`
|
|
15
17
|
* files) in git history. The primary signal is the branch diff against the
|
|
@@ -179,7 +181,7 @@ function getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId) {
|
|
|
179
181
|
// Primary: path-scoped log against .gsd/milestones/<id>. Fast and unbounded
|
|
180
182
|
// by depth when .gsd/ is tracked in git.
|
|
181
183
|
const scoped = scanGsdTaggedCommits(basePath, milestoneId, [
|
|
182
|
-
"log", "--format=%H%x1f%B%
|
|
184
|
+
"log", "--full-diff", "--name-only", "--format=%x1e%H%x1f%B%x1f", "HEAD", "--", `.gsd/milestones/${milestoneId}`,
|
|
183
185
|
]);
|
|
184
186
|
if (!scoped.ok)
|
|
185
187
|
return scoped;
|
|
@@ -195,7 +197,7 @@ function getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId) {
|
|
|
195
197
|
// reintroducing the rolling-depth failure class removed in #4699 where
|
|
196
198
|
// milestone evidence aged out behind unrelated activity.
|
|
197
199
|
const unscoped = scanGsdTaggedCommits(basePath, milestoneId, [
|
|
198
|
-
"log", "--format=%H%x1f%B%
|
|
200
|
+
"log", "--name-only", "--format=%x1e%H%x1f%B%x1f", "HEAD",
|
|
199
201
|
]);
|
|
200
202
|
if (!unscoped.ok)
|
|
201
203
|
return scoped.matched ? scoped : unscoped;
|
|
@@ -280,8 +282,7 @@ function backfillChangedFilesFromUntaggedMilestoneCommits(basePath, milestoneId)
|
|
|
280
282
|
continue;
|
|
281
283
|
if (commitMessageHasGsdTrailer(record.message))
|
|
282
284
|
continue;
|
|
283
|
-
const
|
|
284
|
-
const implementationFiles = commitFiles.map(normalizeRepoPath).filter(isImplementationPath);
|
|
285
|
+
const implementationFiles = record.files.map(normalizeRepoPath).filter(isImplementationPath);
|
|
285
286
|
if (implementationFiles.length === 0)
|
|
286
287
|
continue;
|
|
287
288
|
if (!implementationFiles.some((file) => hintSet.has(file)))
|
|
@@ -306,24 +307,28 @@ function backfillChangedFilesFromUntaggedMilestoneCommits(basePath, milestoneId)
|
|
|
306
307
|
}
|
|
307
308
|
}
|
|
308
309
|
function getCommitRecords(basePath) {
|
|
309
|
-
const logOutput = execFileSync("git", ["log", "--format=%H%x1f%P%x1f%cI%x1f%B%
|
|
310
|
+
const logOutput = execFileSync("git", ["log", "--name-only", "--format=%x1e%H%x1f%P%x1f%cI%x1f%B%x1f", "HEAD"], {
|
|
310
311
|
cwd: basePath,
|
|
311
312
|
stdio: ["ignore", "pipe", "pipe"],
|
|
312
313
|
encoding: "utf-8",
|
|
313
314
|
maxBuffer: GIT_LOG_MAX_BUFFER,
|
|
314
315
|
});
|
|
315
316
|
return logOutput
|
|
316
|
-
.split(
|
|
317
|
-
.map((record) => record.trim())
|
|
317
|
+
.split(LOG_RECORD_SEPARATOR)
|
|
318
318
|
.filter(Boolean)
|
|
319
319
|
.flatMap((record) => {
|
|
320
|
-
const parts = record.split(
|
|
321
|
-
if (parts.length <
|
|
320
|
+
const parts = record.split(LOG_FIELD_SEPARATOR);
|
|
321
|
+
if (parts.length < 5)
|
|
322
322
|
return [];
|
|
323
|
-
const [hash, parents, committedAt
|
|
324
|
-
|
|
323
|
+
const [hash, parents, committedAt] = parts;
|
|
324
|
+
const files = parseNameOnlyFiles(parts.at(-1) ?? "");
|
|
325
|
+
const message = parts.slice(3, -1).join(LOG_FIELD_SEPARATOR);
|
|
326
|
+
return [{ hash: hash.trim(), parents: parents.trim(), committedAt: committedAt.trim(), message, files }];
|
|
325
327
|
});
|
|
326
328
|
}
|
|
329
|
+
function parseNameOnlyFiles(rawFiles) {
|
|
330
|
+
return rawFiles.split(/\r?\n/).map((file) => file.trim()).filter(Boolean);
|
|
331
|
+
}
|
|
327
332
|
function isFullCommitSha(value) {
|
|
328
333
|
return /^[0-9a-f]{40}$/i.test(value);
|
|
329
334
|
}
|
|
@@ -336,23 +341,24 @@ function scanGsdTaggedCommits(basePath, milestoneId, gitArgs) {
|
|
|
336
341
|
maxBuffer: GIT_LOG_MAX_BUFFER,
|
|
337
342
|
});
|
|
338
343
|
const records = logOutput
|
|
339
|
-
.split(
|
|
340
|
-
.map((record) => record.trim())
|
|
344
|
+
.split(LOG_RECORD_SEPARATOR)
|
|
341
345
|
.filter(Boolean)
|
|
342
346
|
.flatMap((record) => {
|
|
343
|
-
const
|
|
344
|
-
if (
|
|
347
|
+
const parts = record.split(LOG_FIELD_SEPARATOR);
|
|
348
|
+
if (parts.length < 3)
|
|
349
|
+
return [];
|
|
350
|
+
const hash = parts[0].trim();
|
|
351
|
+
if (!hash)
|
|
345
352
|
return [];
|
|
346
|
-
const
|
|
347
|
-
const message =
|
|
348
|
-
return [{
|
|
353
|
+
const files = parseNameOnlyFiles(parts.at(-1) ?? "");
|
|
354
|
+
const message = parts.slice(1, -1).join(LOG_FIELD_SEPARATOR);
|
|
355
|
+
return [{ message, files }];
|
|
349
356
|
});
|
|
350
357
|
const files = new Set();
|
|
351
358
|
let matched = false;
|
|
352
|
-
for (const {
|
|
359
|
+
for (const { message, files: commitFiles } of records) {
|
|
353
360
|
if (!commitMessageHasGsdTrailer(message))
|
|
354
361
|
continue;
|
|
355
|
-
const commitFiles = getChangedFilesForCommit(basePath, hash);
|
|
356
362
|
if (!commitMatchesMilestone(basePath, message, milestoneId, commitFiles))
|
|
357
363
|
continue;
|
|
358
364
|
matched = true;
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* Quick tasks live in `.gsd/quick/` and are tracked in STATE.md's
|
|
9
9
|
* "Quick Tasks Completed" table.
|
|
10
10
|
*/
|
|
11
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
-
import { isAbsolute, join, relative } from "node:path";
|
|
11
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
13
13
|
import { QUICK_BRANCH_RE } from "./branch-patterns.js";
|
|
14
14
|
import { loadPrompt } from "./prompt-loader.js";
|
|
15
15
|
import { gsdRoot } from "./paths.js";
|
|
@@ -19,6 +19,7 @@ import { nativeBranchExists, nativeDetectMainBranch, nativeDiffNumstat } from ".
|
|
|
19
19
|
import { nativeHasStagedChanges } from "./native-git-bridge.js";
|
|
20
20
|
import { resolveUokFlags } from "./uok/flags.js";
|
|
21
21
|
let pendingQuickReturn = null;
|
|
22
|
+
const pendingQuickReturnMisses = new Map();
|
|
22
23
|
// ─── Quick Task Helpers ───────────────────────────────────────────────────────
|
|
23
24
|
/**
|
|
24
25
|
* Generate a URL-friendly slug from a description.
|
|
@@ -169,11 +170,36 @@ export function buildQuickCommitInstruction(basePath, root) {
|
|
|
169
170
|
" - Quick tasks run outside the auto-mode lifecycle — there is no system auto-commit, so commit directly here.",
|
|
170
171
|
].join("\n");
|
|
171
172
|
}
|
|
173
|
+
function readHeadBranchName(basePath) {
|
|
174
|
+
try {
|
|
175
|
+
const gitPath = join(basePath, ".git");
|
|
176
|
+
if (!existsSync(gitPath))
|
|
177
|
+
return null;
|
|
178
|
+
let headPath;
|
|
179
|
+
if (lstatSync(gitPath).isDirectory()) {
|
|
180
|
+
headPath = join(gitPath, "HEAD");
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
const gitFile = readFileSync(gitPath, "utf-8").trim();
|
|
184
|
+
if (!gitFile.startsWith("gitdir: "))
|
|
185
|
+
return null;
|
|
186
|
+
headPath = join(resolve(basePath, gitFile.slice("gitdir: ".length)), "HEAD");
|
|
187
|
+
}
|
|
188
|
+
const head = readFileSync(headPath, "utf-8").trim();
|
|
189
|
+
if (!head.startsWith("ref: refs/heads/"))
|
|
190
|
+
return null;
|
|
191
|
+
return head.slice("ref: refs/heads/".length);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
172
197
|
function quickReturnStatePath(basePath) {
|
|
173
198
|
return join(gsdRoot(basePath), "runtime", "quick-return.json");
|
|
174
199
|
}
|
|
175
200
|
function persistPendingReturn(state) {
|
|
176
201
|
pendingQuickReturn = state;
|
|
202
|
+
pendingQuickReturnMisses.delete(state.basePath);
|
|
177
203
|
mkdirSync(join(gsdRoot(state.basePath), "runtime"), { recursive: true });
|
|
178
204
|
writeFileSync(quickReturnStatePath(state.basePath), JSON.stringify(state) + "\n", "utf-8");
|
|
179
205
|
}
|
|
@@ -181,6 +207,13 @@ function readPendingReturn(basePath) {
|
|
|
181
207
|
if (pendingQuickReturn && pendingQuickReturn.basePath === basePath) {
|
|
182
208
|
return pendingQuickReturn;
|
|
183
209
|
}
|
|
210
|
+
if (pendingQuickReturnMisses.has(basePath)) {
|
|
211
|
+
const statePath = quickReturnStatePath(basePath);
|
|
212
|
+
if (!existsSync(statePath) && readHeadBranchName(basePath) === pendingQuickReturnMisses.get(basePath)) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
pendingQuickReturnMisses.delete(basePath);
|
|
216
|
+
}
|
|
184
217
|
try {
|
|
185
218
|
const raw = readFileSync(quickReturnStatePath(basePath), "utf-8");
|
|
186
219
|
const parsed = JSON.parse(raw);
|
|
@@ -191,6 +224,7 @@ function readPendingReturn(basePath) {
|
|
|
191
224
|
&& typeof parsed.slug === "string"
|
|
192
225
|
&& typeof parsed.description === "string") {
|
|
193
226
|
pendingQuickReturn = parsed;
|
|
227
|
+
pendingQuickReturnMisses.delete(basePath);
|
|
194
228
|
return pendingQuickReturn;
|
|
195
229
|
}
|
|
196
230
|
}
|
|
@@ -200,14 +234,23 @@ function readPendingReturn(basePath) {
|
|
|
200
234
|
const inferred = inferQuickReturnFromBranch(basePath);
|
|
201
235
|
if (inferred) {
|
|
202
236
|
pendingQuickReturn = inferred;
|
|
237
|
+
pendingQuickReturnMisses.delete(basePath);
|
|
203
238
|
return inferred;
|
|
204
239
|
}
|
|
240
|
+
const branchAtMiss = readHeadBranchName(basePath);
|
|
241
|
+
if (branchAtMiss) {
|
|
242
|
+
pendingQuickReturnMisses.set(basePath, branchAtMiss);
|
|
243
|
+
}
|
|
205
244
|
return null;
|
|
206
245
|
}
|
|
207
246
|
function clearPendingReturn(basePath) {
|
|
208
247
|
if (pendingQuickReturn?.basePath === basePath) {
|
|
209
248
|
pendingQuickReturn = null;
|
|
210
249
|
}
|
|
250
|
+
const branchAtMiss = readHeadBranchName(basePath);
|
|
251
|
+
if (branchAtMiss) {
|
|
252
|
+
pendingQuickReturnMisses.set(basePath, branchAtMiss);
|
|
253
|
+
}
|
|
211
254
|
rmSync(quickReturnStatePath(basePath), { force: true });
|
|
212
255
|
}
|
|
213
256
|
function hasStagedChanges(basePath) {
|
|
@@ -176,11 +176,21 @@ export function extractTrace(entries) {
|
|
|
176
176
|
}
|
|
177
177
|
// Flush any pending tool calls that never got results (crash mid-tool)
|
|
178
178
|
for (const [, pending] of pendingTools) {
|
|
179
|
+
const missingResultError = `Tool call ${pending.name} started but no toolResult was recorded`;
|
|
179
180
|
toolCalls.push({
|
|
180
181
|
name: pending.name,
|
|
181
182
|
input: redactInput(pending.name, pending.input),
|
|
182
|
-
|
|
183
|
+
result: "missing tool result (stream/tool-call abort before execution)",
|
|
184
|
+
isError: true,
|
|
183
185
|
});
|
|
186
|
+
errors.push(missingResultError);
|
|
187
|
+
// Mark the matching commandsRun entry as failed so it is consistent with
|
|
188
|
+
// the isError: true on the tool call above (bash/bg_shell only).
|
|
189
|
+
if (pending.name === "bash" || pending.name === "bg_shell") {
|
|
190
|
+
const lastCmd = findLast(commandsRun, c => c.command === String(pending.input.command));
|
|
191
|
+
if (lastCmd)
|
|
192
|
+
lastCmd.failed = true;
|
|
193
|
+
}
|
|
184
194
|
}
|
|
185
195
|
return {
|
|
186
196
|
toolCalls,
|
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
// the detect+repair composition moves here. The previous repairStaleRenders
|
|
6
6
|
// had zero callers in production code — wiring it through
|
|
7
7
|
// reconcileBeforeDispatch closes that gap.
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
8
10
|
import { detectStaleRenders, renderPlanCheckboxes, renderRoadmapFromDb, renderSliceSummary, renderTaskSummary, } from "../../markdown-renderer.js";
|
|
9
|
-
import { getMilestone, getMilestoneSlices, getSlice, getSliceTasks, setSliceSummaryMd } from "../../gsd-db.js";
|
|
10
|
-
import {
|
|
11
|
+
import { getMilestone, getMilestoneSlices, getSlice, getSliceTasks, isDbAvailable, setSliceSummaryMd, } from "../../gsd-db.js";
|
|
12
|
+
import { getWorkflowDatabasePath, openWorkflowDatabasePath, refreshWorkflowDatabaseFromDisk, } from "../../db-workspace.js";
|
|
13
|
+
import { gsdRoot, resolveSliceFile } from "../../paths.js";
|
|
11
14
|
import { logWarning } from "../../workflow-logger.js";
|
|
12
15
|
// ─── Core (basePath-only — usable by both drift API and legacy wrapper) ──────
|
|
13
16
|
function detectStaleRenderDriftFromBasePath(basePath) {
|
|
@@ -82,7 +85,42 @@ function resolveRoadmapMilestoneIdFromPath(normPath) {
|
|
|
82
85
|
}
|
|
83
86
|
return fileMatch?.[1] ?? milestoneMatch[1];
|
|
84
87
|
}
|
|
88
|
+
function expectedDbPathForStaleRenderRepair(basePath) {
|
|
89
|
+
return join(gsdRoot(basePath), "gsd.db");
|
|
90
|
+
}
|
|
91
|
+
function ensureDbForStaleRenderRepair(basePath) {
|
|
92
|
+
const dbPath = expectedDbPathForStaleRenderRepair(basePath);
|
|
93
|
+
if (isDbAvailable() && getWorkflowDatabasePath() === dbPath)
|
|
94
|
+
return true;
|
|
95
|
+
if (!existsSync(dbPath))
|
|
96
|
+
return false;
|
|
97
|
+
try {
|
|
98
|
+
return openWorkflowDatabasePath(dbPath);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
logWarning("reconcile", `stale-render repair could not reopen DB: ${err.message}`);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function retryDbForStaleRenderRepair(basePath) {
|
|
106
|
+
const dbPath = expectedDbPathForStaleRenderRepair(basePath);
|
|
107
|
+
if (!existsSync(dbPath))
|
|
108
|
+
return false;
|
|
109
|
+
try {
|
|
110
|
+
if (isDbAvailable() && getWorkflowDatabasePath() === dbPath && refreshWorkflowDatabaseFromDisk()) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return openWorkflowDatabasePath(dbPath);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
logWarning("reconcile", `stale-render repair could not reopen DB: ${err.message}`);
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
85
120
|
async function repairStaleRenderFromBasePath(record, basePath) {
|
|
121
|
+
if (!ensureDbForStaleRenderRepair(basePath)) {
|
|
122
|
+
throw new Error(`stale-render drift: database unavailable for repair (${basePath})`);
|
|
123
|
+
}
|
|
86
124
|
const normPath = record.renderPath.replace(/\\/g, "/");
|
|
87
125
|
const reason = record.reason;
|
|
88
126
|
if (reason.includes("in roadmap")) {
|
|
@@ -101,7 +139,18 @@ async function repairStaleRenderFromBasePath(record, basePath) {
|
|
|
101
139
|
const sliceId = pathMatch[2] && pathMatch[3] && /^\d+$/.test(pathMatch[2])
|
|
102
140
|
? `S${String(parseInt(pathMatch[3], 10)).padStart(2, "0")}`
|
|
103
141
|
: pathMatch[2];
|
|
104
|
-
|
|
142
|
+
let wrote = false;
|
|
143
|
+
try {
|
|
144
|
+
wrote = await renderPlanCheckboxes(basePath, milestoneId, sliceId, record.renderPath);
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
if (!retryDbForStaleRenderRepair(basePath))
|
|
148
|
+
throw err;
|
|
149
|
+
wrote = await renderPlanCheckboxes(basePath, milestoneId, sliceId, record.renderPath);
|
|
150
|
+
}
|
|
151
|
+
if (!wrote && retryDbForStaleRenderRepair(basePath)) {
|
|
152
|
+
wrote = await renderPlanCheckboxes(basePath, milestoneId, sliceId, record.renderPath);
|
|
153
|
+
}
|
|
105
154
|
if (!wrote) {
|
|
106
155
|
throw new Error(`stale-render drift: plan re-render wrote nothing for ${milestoneId}/${pathMatch[2]} ` +
|
|
107
156
|
`(${record.renderPath}); slice has no tasks or its path is unresolvable`);
|
|
@@ -21,7 +21,7 @@ import { classifyUatContent, escalatesArtifactUatToBrowser } from "../uat-policy
|
|
|
21
21
|
import { invalidateStateCache } from "../state.js";
|
|
22
22
|
import { renderRoadmapFromDb, roadmapRenderMarksSliceDone } from "../markdown-renderer.js";
|
|
23
23
|
import { isStaleWrite } from "../auto/turn-epoch.js";
|
|
24
|
-
import {
|
|
24
|
+
import { renderStateProjection, renderTopLevelQueueFromDb, renderTopLevelRoadmapFromDb } from "../workflow-projections.js";
|
|
25
25
|
import { writeManifest } from "../workflow-manifest.js";
|
|
26
26
|
import { appendEvent } from "../workflow-events.js";
|
|
27
27
|
import { logWarning, logError } from "../workflow-logger.js";
|
|
@@ -457,11 +457,42 @@ export async function handleCompleteSlice(params, basePath) {
|
|
|
457
457
|
// ── Post-mutation hook: projections, manifest, event log ───────────────
|
|
458
458
|
// Separate try/catch per step so a projection failure doesn't prevent
|
|
459
459
|
// the event log entry (critical for worktree reconciliation).
|
|
460
|
+
//
|
|
461
|
+
// If the primary summary/UAT/roadmap write block failed (projectionStale),
|
|
462
|
+
// retry the milestone-level roadmap here so ROADMAP.md is not left stale
|
|
463
|
+
// after a committed slice completion. This restores the recovery that the
|
|
464
|
+
// removed flushWorkflowProjections/renderAllProjections provided.
|
|
465
|
+
if (projectionStale) {
|
|
466
|
+
try {
|
|
467
|
+
await renderRoadmapFromDb(artifactBasePath, params.milestoneId);
|
|
468
|
+
}
|
|
469
|
+
catch (projErr) {
|
|
470
|
+
logWarning("tool", `complete-slice milestone roadmap retry warning for ${params.milestoneId}/${params.sliceId}: ${projErr.message}`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
await renderRoadmapFromDb(artifactBasePath, params.milestoneId);
|
|
475
|
+
}
|
|
476
|
+
catch (projErr) {
|
|
477
|
+
logWarning("tool", `complete-slice milestone roadmap projection warning for ${params.milestoneId}/${params.sliceId}: ${projErr.message}`);
|
|
478
|
+
}
|
|
479
|
+
try {
|
|
480
|
+
renderTopLevelRoadmapFromDb(artifactBasePath);
|
|
481
|
+
}
|
|
482
|
+
catch (projErr) {
|
|
483
|
+
logWarning("tool", `complete-slice roadmap projection warning for ${params.milestoneId}/${params.sliceId}: ${projErr.message}`);
|
|
484
|
+
}
|
|
485
|
+
try {
|
|
486
|
+
renderTopLevelQueueFromDb(artifactBasePath);
|
|
487
|
+
}
|
|
488
|
+
catch (projErr) {
|
|
489
|
+
logWarning("tool", `complete-slice queue projection warning for ${params.milestoneId}/${params.sliceId}: ${projErr.message}`);
|
|
490
|
+
}
|
|
460
491
|
try {
|
|
461
|
-
await
|
|
492
|
+
await renderStateProjection(artifactBasePath);
|
|
462
493
|
}
|
|
463
494
|
catch (projErr) {
|
|
464
|
-
logWarning("tool", `complete-slice projection warning for ${params.milestoneId}/${params.sliceId}: ${projErr.message}`);
|
|
495
|
+
logWarning("tool", `complete-slice state projection warning for ${params.milestoneId}/${params.sliceId}: ${projErr.message}`);
|
|
465
496
|
}
|
|
466
497
|
try {
|
|
467
498
|
writeManifest(artifactBasePath);
|
|
@@ -5,22 +5,23 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Validates inputs, writes task row and rendered SUMMARY.md to DB in a
|
|
7
7
|
* transaction, then renders projections to disk and invalidates caches.
|
|
8
|
-
*
|
|
9
|
-
* back
|
|
8
|
+
* If the critical task summary / plan projection write fails, the DB
|
|
9
|
+
* completion is compensated back to pending so DB state does not drift ahead
|
|
10
|
+
* of PLAN.md.
|
|
10
11
|
*/
|
|
11
|
-
import { existsSync } from "node:fs";
|
|
12
|
+
import { existsSync, unlinkSync } from "node:fs";
|
|
12
13
|
import { join } from "node:path";
|
|
13
14
|
import { isClosedStatus } from "../status-guards.js";
|
|
14
|
-
import { transaction, insertMilestone, insertSlice, insertTask, insertVerificationEvidence, getMilestone, getSlice, getTask, updateTaskStatus, deleteVerificationEvidence, saveGateResult, getPendingGatesForTurn, } from "../gsd-db.js";
|
|
15
|
+
import { transaction, insertMilestone, insertSlice, insertTask, insertVerificationEvidence, getMilestone, getSlice, getTask, updateTaskStatus, deleteVerificationEvidence, saveGateResult, getPendingGatesForTurn, isDbAvailable, } from "../gsd-db.js";
|
|
16
|
+
import { getWorkflowDatabasePath, openWorkflowDatabasePath } from "../db-workspace.js";
|
|
15
17
|
import { getGatesForTurn } from "../gate-registry.js";
|
|
16
18
|
import { gsdProjectionRoot, clearPathCache, resolveMilestonePath, resolveSlicePath } from "../paths.js";
|
|
17
19
|
import { resolveCanonicalMilestoneRoot } from "../worktree-manager.js";
|
|
18
20
|
import { checkOwnership, taskUnitKey } from "../unit-ownership.js";
|
|
19
21
|
import { saveFile, clearParseCache } from "../files.js";
|
|
20
22
|
import { invalidateStateCache } from "../state.js";
|
|
21
|
-
import { renderPlanCheckboxes } from "../markdown-renderer.js";
|
|
22
|
-
import { renderSummaryContent } from "../workflow-projections.js";
|
|
23
|
-
import { flushWorkflowProjections } from "../projection-flush.js";
|
|
23
|
+
import { renderPlanCheckboxes, renderRoadmapFromDb } from "../markdown-renderer.js";
|
|
24
|
+
import { renderStateProjection, renderSummaryContent, renderTopLevelQueueFromDb, renderTopLevelRoadmapFromDb, } from "../workflow-projections.js";
|
|
24
25
|
import { writeManifest } from "../workflow-manifest.js";
|
|
25
26
|
import { appendEvent } from "../workflow-events.js";
|
|
26
27
|
import { logWarning, logError } from "../workflow-logger.js";
|
|
@@ -44,6 +45,39 @@ function taskSummaryPath(basePath, milestoneId, sliceId, taskId) {
|
|
|
44
45
|
// Fallback: legacy hardcoded path (milestone/slice dir not on disk yet)
|
|
45
46
|
return join(gsdProjectionRoot(basePath), "milestones", milestoneId, "slices", sliceId, "tasks", `${taskId}-SUMMARY.md`);
|
|
46
47
|
}
|
|
48
|
+
async function renderCompleteTaskProjections(basePath, milestoneId) {
|
|
49
|
+
try {
|
|
50
|
+
await renderRoadmapFromDb(basePath, milestoneId);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
logWarning("projection", `renderRoadmapFromDb failed for ${milestoneId}: ${err.message}`);
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
renderTopLevelRoadmapFromDb(basePath);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
logWarning("projection", `renderTopLevelRoadmapFromDb failed: ${err.message}`);
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
renderTopLevelQueueFromDb(basePath);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
logWarning("projection", `renderTopLevelQueueFromDb failed: ${err.message}`);
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
await renderStateProjection(basePath);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
logWarning("projection", `renderStateProjection failed: ${err.message}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function ensureCompleteTaskDbOpen(dbPath) {
|
|
75
|
+
if (!dbPath || dbPath === ":memory:")
|
|
76
|
+
return isDbAvailable();
|
|
77
|
+
if (isDbAvailable() && getWorkflowDatabasePath() === dbPath)
|
|
78
|
+
return true;
|
|
79
|
+
return openWorkflowDatabasePath(dbPath);
|
|
80
|
+
}
|
|
47
81
|
async function repairMissingTaskSummaryProjection(artifactBasePath, taskRow) {
|
|
48
82
|
const summaryPath = taskSummaryPath(artifactBasePath, taskRow.milestone_id, taskRow.slice_id, taskRow.id);
|
|
49
83
|
const summaryMd = renderSummaryContent(taskRow, taskRow.slice_id, taskRow.milestone_id, []);
|
|
@@ -60,7 +94,7 @@ async function repairMissingTaskSummaryProjection(artifactBasePath, taskRow) {
|
|
|
60
94
|
clearPathCache();
|
|
61
95
|
clearParseCache();
|
|
62
96
|
try {
|
|
63
|
-
await
|
|
97
|
+
await renderCompleteTaskProjections(artifactBasePath, taskRow.milestone_id);
|
|
64
98
|
}
|
|
65
99
|
catch (projErr) {
|
|
66
100
|
logWarning("tool", `complete-task repair projection warning: ${projErr.message}`);
|
|
@@ -181,6 +215,7 @@ export async function handleCompleteTask(params, basePath) {
|
|
|
181
215
|
let guardError = null;
|
|
182
216
|
let summaryMd = "";
|
|
183
217
|
let repairTaskSummaryRow = null;
|
|
218
|
+
const rollbackDbPath = getWorkflowDatabasePath();
|
|
184
219
|
// ── ADR-011 Phase 2: validate escalation payload BEFORE any side effects ─
|
|
185
220
|
// Building the artifact runs the full shape validation (2-4 options, unique
|
|
186
221
|
// ids, recommendation references a real id). If the payload is malformed
|
|
@@ -317,20 +352,48 @@ export async function handleCompleteTask(params, basePath) {
|
|
|
317
352
|
if (guardError) {
|
|
318
353
|
return { error: guardError };
|
|
319
354
|
}
|
|
320
|
-
let projectionStale = false;
|
|
321
355
|
// Resolve and write summary to disk
|
|
322
356
|
const summaryPath = taskSummaryPath(artifactBasePath, params.milestoneId, params.sliceId, params.taskId);
|
|
323
357
|
try {
|
|
324
358
|
await saveFile(summaryPath, summaryMd);
|
|
325
359
|
// Toggle or regenerate the plan projection from DB. Missing projection
|
|
326
360
|
// files are rebuilt by the renderer instead of being skipped.
|
|
327
|
-
|
|
361
|
+
if (!ensureCompleteTaskDbOpen(rollbackDbPath)) {
|
|
362
|
+
throw new Error(`database unavailable before plan projection render for ${params.milestoneId}/${params.sliceId}`);
|
|
363
|
+
}
|
|
364
|
+
const wrotePlan = await renderPlanCheckboxes(artifactBasePath, params.milestoneId, params.sliceId);
|
|
365
|
+
if (!wrotePlan) {
|
|
366
|
+
throw new Error(`plan projection write returned false for ${params.milestoneId}/${params.sliceId}`);
|
|
367
|
+
}
|
|
328
368
|
}
|
|
329
369
|
catch (renderErr) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
370
|
+
logWarning("projection", `complete_task projection write failed for ${params.milestoneId}/${params.sliceId}/${params.taskId}`, { error: renderErr.message });
|
|
371
|
+
let rollbackSucceeded = false;
|
|
372
|
+
try {
|
|
373
|
+
ensureCompleteTaskDbOpen(rollbackDbPath);
|
|
374
|
+
deleteVerificationEvidence(params.milestoneId, params.sliceId, params.taskId);
|
|
375
|
+
updateTaskStatus(params.milestoneId, params.sliceId, params.taskId, "pending");
|
|
376
|
+
invalidateStateCache();
|
|
377
|
+
rollbackSucceeded = true;
|
|
378
|
+
}
|
|
379
|
+
catch (rollbackErr) {
|
|
380
|
+
logWarning("projection", `complete_task rollback failed after projection write failure for ${params.milestoneId}/${params.sliceId}/${params.taskId}: ${rollbackErr.message}`);
|
|
381
|
+
}
|
|
382
|
+
try {
|
|
383
|
+
if (existsSync(summaryPath))
|
|
384
|
+
unlinkSync(summaryPath);
|
|
385
|
+
}
|
|
386
|
+
catch (summaryErr) {
|
|
387
|
+
logWarning("projection", `complete_task could not remove SUMMARY.md after projection write failure for ${params.milestoneId}/${params.sliceId}/${params.taskId}: ${summaryErr.message}`);
|
|
388
|
+
}
|
|
389
|
+
// Clear path/parse caches regardless of rollback outcome so stale
|
|
390
|
+
// entries from the failed write attempt don't leak into subsequent calls.
|
|
391
|
+
clearPathCache();
|
|
392
|
+
clearParseCache();
|
|
393
|
+
const returnMsg = rollbackSucceeded
|
|
394
|
+
? `complete_task projection write failed for ${params.milestoneId}/${params.sliceId}/${params.taskId}; rolled completion back to pending`
|
|
395
|
+
: `complete_task projection write failed for ${params.milestoneId}/${params.sliceId}/${params.taskId}; rollback also failed — task may remain complete with stale plan`;
|
|
396
|
+
return { error: returnMsg };
|
|
334
397
|
}
|
|
335
398
|
// ── Close gates owned by execute-task (Q5/Q6/Q7) for this task ────────
|
|
336
399
|
// Each gate id maps to a specific params field via taskGateFieldForId.
|
|
@@ -435,7 +498,7 @@ export async function handleCompleteTask(params, basePath) {
|
|
|
435
498
|
// Separate try/catch per step so a projection failure doesn't prevent
|
|
436
499
|
// the event log entry (critical for worktree reconciliation).
|
|
437
500
|
try {
|
|
438
|
-
await
|
|
501
|
+
await renderCompleteTaskProjections(artifactBasePath, params.milestoneId);
|
|
439
502
|
}
|
|
440
503
|
catch (projErr) {
|
|
441
504
|
logWarning("tool", `complete-task projection warning: ${projErr.message}`);
|
|
@@ -465,6 +528,5 @@ export async function handleCompleteTask(params, basePath) {
|
|
|
465
528
|
milestoneId: params.milestoneId,
|
|
466
529
|
summaryPath,
|
|
467
530
|
...(escalationMetadata ? { escalation: escalationMetadata } : {}),
|
|
468
|
-
...(projectionStale ? { stale: true } : {}),
|
|
469
531
|
};
|
|
470
532
|
}
|
|
@@ -144,7 +144,7 @@ export async function executeGsdExec(params, deps) {
|
|
|
144
144
|
if (bashReferencesProjectRootOutsideWorktree(script, deps.baseDir)) {
|
|
145
145
|
return paramError("script references the original project root while running inside a milestone worktree; use the active worktree path or relative paths");
|
|
146
146
|
}
|
|
147
|
-
const opts = buildExecOptions(deps.baseDir, deps.preferences?.context_mode, { env: deps.env, now: deps.now, generateId: deps.generateId });
|
|
147
|
+
const opts = buildExecOptions(deps.baseDir, deps.preferences?.context_mode, { env: deps.env, now: deps.now, generateId: deps.generateId, signal: deps.signal });
|
|
148
148
|
const run = deps.run ?? runExecSandbox;
|
|
149
149
|
try {
|
|
150
150
|
const result = await run({
|
|
@@ -235,6 +235,7 @@ function formatResult(result) {
|
|
|
235
235
|
exit_code: result.exit_code,
|
|
236
236
|
signal: result.signal,
|
|
237
237
|
timed_out: result.timed_out,
|
|
238
|
+
aborted: result.aborted === true,
|
|
238
239
|
force_resolved: result.force_resolved,
|
|
239
240
|
duration_ms: result.duration_ms,
|
|
240
241
|
stdout_bytes: result.stdout_bytes,
|
|
@@ -245,12 +246,16 @@ function formatResult(result) {
|
|
|
245
246
|
stderr_path: result.stderr_path,
|
|
246
247
|
meta_path: result.meta_path,
|
|
247
248
|
},
|
|
248
|
-
isError: result.timed_out || result.signal !== null || result.exit_code !== 0,
|
|
249
|
+
isError: result.aborted === true || result.timed_out || result.signal !== null || result.exit_code !== 0,
|
|
249
250
|
};
|
|
250
251
|
}
|
|
251
252
|
function formatExit(result) {
|
|
252
253
|
// force_resolved means a non-closing (D-state) child was force-resolved past its
|
|
253
254
|
// hard deadline rather than observed exiting; distinguish it from a clean timeout.
|
|
255
|
+
if (result.aborted && result.force_resolved)
|
|
256
|
+
return "aborted(force-killed)";
|
|
257
|
+
if (result.aborted)
|
|
258
|
+
return "aborted";
|
|
254
259
|
if (result.force_resolved)
|
|
255
260
|
return "timeout(force-killed)";
|
|
256
261
|
if (result.timed_out)
|