@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
|
@@ -30,7 +30,7 @@ import { classifyUatContent, escalatesArtifactUatToBrowser } from "../uat-policy
|
|
|
30
30
|
import { invalidateStateCache } from "../state.js";
|
|
31
31
|
import { renderRoadmapFromDb, roadmapRenderMarksSliceDone } from "../markdown-renderer.js";
|
|
32
32
|
import { isStaleWrite } from "../auto/turn-epoch.js";
|
|
33
|
-
import {
|
|
33
|
+
import { renderStateProjection, renderTopLevelQueueFromDb, renderTopLevelRoadmapFromDb } from "../workflow-projections.js";
|
|
34
34
|
import { writeManifest } from "../workflow-manifest.js";
|
|
35
35
|
import { appendEvent } from "../workflow-events.js";
|
|
36
36
|
import { logWarning, logError } from "../workflow-logger.js";
|
|
@@ -539,10 +539,37 @@ export async function handleCompleteSlice(
|
|
|
539
539
|
// ── Post-mutation hook: projections, manifest, event log ───────────────
|
|
540
540
|
// Separate try/catch per step so a projection failure doesn't prevent
|
|
541
541
|
// the event log entry (critical for worktree reconciliation).
|
|
542
|
+
//
|
|
543
|
+
// If the primary summary/UAT/roadmap write block failed (projectionStale),
|
|
544
|
+
// retry the milestone-level roadmap here so ROADMAP.md is not left stale
|
|
545
|
+
// after a committed slice completion. This restores the recovery that the
|
|
546
|
+
// removed flushWorkflowProjections/renderAllProjections provided.
|
|
547
|
+
if (projectionStale) {
|
|
548
|
+
try {
|
|
549
|
+
await renderRoadmapFromDb(artifactBasePath, params.milestoneId);
|
|
550
|
+
} catch (projErr) {
|
|
551
|
+
logWarning("tool", `complete-slice milestone roadmap retry warning for ${params.milestoneId}/${params.sliceId}: ${(projErr as Error).message}`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
try {
|
|
555
|
+
await renderRoadmapFromDb(artifactBasePath, params.milestoneId);
|
|
556
|
+
} catch (projErr) {
|
|
557
|
+
logWarning("tool", `complete-slice milestone roadmap projection warning for ${params.milestoneId}/${params.sliceId}: ${(projErr as Error).message}`);
|
|
558
|
+
}
|
|
559
|
+
try {
|
|
560
|
+
renderTopLevelRoadmapFromDb(artifactBasePath);
|
|
561
|
+
} catch (projErr) {
|
|
562
|
+
logWarning("tool", `complete-slice roadmap projection warning for ${params.milestoneId}/${params.sliceId}: ${(projErr as Error).message}`);
|
|
563
|
+
}
|
|
564
|
+
try {
|
|
565
|
+
renderTopLevelQueueFromDb(artifactBasePath);
|
|
566
|
+
} catch (projErr) {
|
|
567
|
+
logWarning("tool", `complete-slice queue projection warning for ${params.milestoneId}/${params.sliceId}: ${(projErr as Error).message}`);
|
|
568
|
+
}
|
|
542
569
|
try {
|
|
543
|
-
await
|
|
570
|
+
await renderStateProjection(artifactBasePath);
|
|
544
571
|
} catch (projErr) {
|
|
545
|
-
logWarning("tool", `complete-slice projection warning for ${params.milestoneId}/${params.sliceId}: ${(projErr as Error).message}`);
|
|
572
|
+
logWarning("tool", `complete-slice state projection warning for ${params.milestoneId}/${params.sliceId}: ${(projErr as Error).message}`);
|
|
546
573
|
}
|
|
547
574
|
try {
|
|
548
575
|
writeManifest(artifactBasePath);
|
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Validates inputs, writes task row and rendered SUMMARY.md to DB in a
|
|
8
8
|
* transaction, then renders projections to disk and invalidates caches.
|
|
9
|
-
*
|
|
10
|
-
* back
|
|
9
|
+
* If the critical task summary / plan projection write fails, the DB
|
|
10
|
+
* completion is compensated back to pending so DB state does not drift ahead
|
|
11
|
+
* of PLAN.md.
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
|
-
import { existsSync } from "node:fs";
|
|
14
|
+
import { existsSync, unlinkSync } from "node:fs";
|
|
14
15
|
import { join } from "node:path";
|
|
15
16
|
|
|
16
17
|
import type { CompleteTaskParams, EscalationArtifact } from "../types.js";
|
|
@@ -28,16 +29,22 @@ import {
|
|
|
28
29
|
deleteVerificationEvidence,
|
|
29
30
|
saveGateResult,
|
|
30
31
|
getPendingGatesForTurn,
|
|
32
|
+
isDbAvailable,
|
|
31
33
|
} from "../gsd-db.js";
|
|
34
|
+
import { getWorkflowDatabasePath, openWorkflowDatabasePath } from "../db-workspace.js";
|
|
32
35
|
import { getGatesForTurn } from "../gate-registry.js";
|
|
33
36
|
import { gsdProjectionRoot, clearPathCache, resolveMilestonePath, resolveSlicePath } from "../paths.js";
|
|
34
37
|
import { resolveCanonicalMilestoneRoot } from "../worktree-manager.js";
|
|
35
38
|
import { checkOwnership, taskUnitKey } from "../unit-ownership.js";
|
|
36
39
|
import { saveFile, clearParseCache } from "../files.js";
|
|
37
40
|
import { invalidateStateCache } from "../state.js";
|
|
38
|
-
import { renderPlanCheckboxes } from "../markdown-renderer.js";
|
|
39
|
-
import {
|
|
40
|
-
|
|
41
|
+
import { renderPlanCheckboxes, renderRoadmapFromDb } from "../markdown-renderer.js";
|
|
42
|
+
import {
|
|
43
|
+
renderStateProjection,
|
|
44
|
+
renderSummaryContent,
|
|
45
|
+
renderTopLevelQueueFromDb,
|
|
46
|
+
renderTopLevelRoadmapFromDb,
|
|
47
|
+
} from "../workflow-projections.js";
|
|
41
48
|
import { writeManifest } from "../workflow-manifest.js";
|
|
42
49
|
import { appendEvent } from "../workflow-events.js";
|
|
43
50
|
import { logWarning, logError } from "../workflow-logger.js";
|
|
@@ -101,6 +108,35 @@ function taskSummaryPath(
|
|
|
101
108
|
);
|
|
102
109
|
}
|
|
103
110
|
|
|
111
|
+
async function renderCompleteTaskProjections(basePath: string, milestoneId: string): Promise<void> {
|
|
112
|
+
try {
|
|
113
|
+
await renderRoadmapFromDb(basePath, milestoneId);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
logWarning("projection", `renderRoadmapFromDb failed for ${milestoneId}: ${(err as Error).message}`);
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
renderTopLevelRoadmapFromDb(basePath);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
logWarning("projection", `renderTopLevelRoadmapFromDb failed: ${(err as Error).message}`);
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
renderTopLevelQueueFromDb(basePath);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
logWarning("projection", `renderTopLevelQueueFromDb failed: ${(err as Error).message}`);
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
await renderStateProjection(basePath);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
logWarning("projection", `renderStateProjection failed: ${(err as Error).message}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function ensureCompleteTaskDbOpen(dbPath: string | null): boolean {
|
|
135
|
+
if (!dbPath || dbPath === ":memory:") return isDbAvailable();
|
|
136
|
+
if (isDbAvailable() && getWorkflowDatabasePath() === dbPath) return true;
|
|
137
|
+
return openWorkflowDatabasePath(dbPath);
|
|
138
|
+
}
|
|
139
|
+
|
|
104
140
|
async function repairMissingTaskSummaryProjection(
|
|
105
141
|
artifactBasePath: string,
|
|
106
142
|
taskRow: TaskRow,
|
|
@@ -131,7 +167,7 @@ async function repairMissingTaskSummaryProjection(
|
|
|
131
167
|
clearParseCache();
|
|
132
168
|
|
|
133
169
|
try {
|
|
134
|
-
await
|
|
170
|
+
await renderCompleteTaskProjections(artifactBasePath, taskRow.milestone_id);
|
|
135
171
|
} catch (projErr) {
|
|
136
172
|
logWarning("tool", `complete-task repair projection warning: ${(projErr as Error).message}`);
|
|
137
173
|
}
|
|
@@ -267,6 +303,7 @@ export async function handleCompleteTask(
|
|
|
267
303
|
let guardError: string | null = null;
|
|
268
304
|
let summaryMd = "";
|
|
269
305
|
let repairTaskSummaryRow: TaskRow | null = null;
|
|
306
|
+
const rollbackDbPath = getWorkflowDatabasePath();
|
|
270
307
|
|
|
271
308
|
// ── ADR-011 Phase 2: validate escalation payload BEFORE any side effects ─
|
|
272
309
|
// Building the artifact runs the full shape validation (2-4 options, unique
|
|
@@ -423,8 +460,6 @@ export async function handleCompleteTask(
|
|
|
423
460
|
return { error: guardError };
|
|
424
461
|
}
|
|
425
462
|
|
|
426
|
-
let projectionStale = false;
|
|
427
|
-
|
|
428
463
|
// Resolve and write summary to disk
|
|
429
464
|
const summaryPath = taskSummaryPath(
|
|
430
465
|
artifactBasePath,
|
|
@@ -438,12 +473,48 @@ export async function handleCompleteTask(
|
|
|
438
473
|
|
|
439
474
|
// Toggle or regenerate the plan projection from DB. Missing projection
|
|
440
475
|
// files are rebuilt by the renderer instead of being skipped.
|
|
441
|
-
|
|
476
|
+
if (!ensureCompleteTaskDbOpen(rollbackDbPath)) {
|
|
477
|
+
throw new Error(`database unavailable before plan projection render for ${params.milestoneId}/${params.sliceId}`);
|
|
478
|
+
}
|
|
479
|
+
const wrotePlan = await renderPlanCheckboxes(artifactBasePath, params.milestoneId, params.sliceId);
|
|
480
|
+
if (!wrotePlan) {
|
|
481
|
+
throw new Error(`plan projection write returned false for ${params.milestoneId}/${params.sliceId}`);
|
|
482
|
+
}
|
|
442
483
|
} catch (renderErr) {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
484
|
+
logWarning(
|
|
485
|
+
"projection",
|
|
486
|
+
`complete_task projection write failed for ${params.milestoneId}/${params.sliceId}/${params.taskId}`,
|
|
487
|
+
{ error: (renderErr as Error).message },
|
|
488
|
+
);
|
|
489
|
+
let rollbackSucceeded = false;
|
|
490
|
+
try {
|
|
491
|
+
ensureCompleteTaskDbOpen(rollbackDbPath);
|
|
492
|
+
deleteVerificationEvidence(params.milestoneId, params.sliceId, params.taskId);
|
|
493
|
+
updateTaskStatus(params.milestoneId, params.sliceId, params.taskId, "pending");
|
|
494
|
+
invalidateStateCache();
|
|
495
|
+
rollbackSucceeded = true;
|
|
496
|
+
} catch (rollbackErr) {
|
|
497
|
+
logWarning(
|
|
498
|
+
"projection",
|
|
499
|
+
`complete_task rollback failed after projection write failure for ${params.milestoneId}/${params.sliceId}/${params.taskId}: ${(rollbackErr as Error).message}`,
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
try {
|
|
503
|
+
if (existsSync(summaryPath)) unlinkSync(summaryPath);
|
|
504
|
+
} catch (summaryErr) {
|
|
505
|
+
logWarning(
|
|
506
|
+
"projection",
|
|
507
|
+
`complete_task could not remove SUMMARY.md after projection write failure for ${params.milestoneId}/${params.sliceId}/${params.taskId}: ${(summaryErr as Error).message}`,
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
// Clear path/parse caches regardless of rollback outcome so stale
|
|
511
|
+
// entries from the failed write attempt don't leak into subsequent calls.
|
|
512
|
+
clearPathCache();
|
|
513
|
+
clearParseCache();
|
|
514
|
+
const returnMsg = rollbackSucceeded
|
|
515
|
+
? `complete_task projection write failed for ${params.milestoneId}/${params.sliceId}/${params.taskId}; rolled completion back to pending`
|
|
516
|
+
: `complete_task projection write failed for ${params.milestoneId}/${params.sliceId}/${params.taskId}; rollback also failed — task may remain complete with stale plan`;
|
|
517
|
+
return { error: returnMsg };
|
|
447
518
|
}
|
|
448
519
|
|
|
449
520
|
// ── Close gates owned by execute-task (Q5/Q6/Q7) for this task ────────
|
|
@@ -569,7 +640,7 @@ export async function handleCompleteTask(
|
|
|
569
640
|
// Separate try/catch per step so a projection failure doesn't prevent
|
|
570
641
|
// the event log entry (critical for worktree reconciliation).
|
|
571
642
|
try {
|
|
572
|
-
await
|
|
643
|
+
await renderCompleteTaskProjections(artifactBasePath, params.milestoneId);
|
|
573
644
|
} catch (projErr) {
|
|
574
645
|
logWarning("tool", `complete-task projection warning: ${(projErr as Error).message}`);
|
|
575
646
|
}
|
|
@@ -597,6 +668,5 @@ export async function handleCompleteTask(
|
|
|
597
668
|
milestoneId: params.milestoneId,
|
|
598
669
|
summaryPath,
|
|
599
670
|
...(escalationMetadata ? { escalation: escalationMetadata } : {}),
|
|
600
|
-
...(projectionStale ? { stale: true } : {}),
|
|
601
671
|
};
|
|
602
672
|
}
|
|
@@ -31,6 +31,7 @@ export interface ExecToolDeps {
|
|
|
31
31
|
env?: NodeJS.ProcessEnv;
|
|
32
32
|
now?: () => Date;
|
|
33
33
|
generateId?: () => string;
|
|
34
|
+
signal?: AbortSignal;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export type UatExecIntent =
|
|
@@ -74,7 +75,7 @@ const UAT_EXEC_INTENT_ALIASES: Record<string, UatExecIntent> = {
|
|
|
74
75
|
export function buildExecOptions(
|
|
75
76
|
baseDir: string,
|
|
76
77
|
cfg: ContextModeConfig | undefined,
|
|
77
|
-
extras?: Pick<ExecSandboxOptions, "env" | "now" | "generateId">,
|
|
78
|
+
extras?: Pick<ExecSandboxOptions, "env" | "now" | "generateId" | "signal">,
|
|
78
79
|
): ExecSandboxOptions {
|
|
79
80
|
const allowlist = Array.isArray(cfg?.exec_env_allowlist) ? cfg!.exec_env_allowlist! : EXEC_DEFAULTS.envAllowlist;
|
|
80
81
|
const stdoutCap = clampNumber(
|
|
@@ -209,7 +210,7 @@ export async function executeGsdExec(
|
|
|
209
210
|
const opts = buildExecOptions(
|
|
210
211
|
deps.baseDir,
|
|
211
212
|
deps.preferences?.context_mode,
|
|
212
|
-
{ env: deps.env, now: deps.now, generateId: deps.generateId },
|
|
213
|
+
{ env: deps.env, now: deps.now, generateId: deps.generateId, signal: deps.signal },
|
|
213
214
|
);
|
|
214
215
|
const run = deps.run ?? runExecSandbox;
|
|
215
216
|
|
|
@@ -308,6 +309,7 @@ function formatResult(result: ExecSandboxResult): ToolExecutionResult {
|
|
|
308
309
|
exit_code: result.exit_code,
|
|
309
310
|
signal: result.signal,
|
|
310
311
|
timed_out: result.timed_out,
|
|
312
|
+
aborted: result.aborted === true,
|
|
311
313
|
force_resolved: result.force_resolved,
|
|
312
314
|
duration_ms: result.duration_ms,
|
|
313
315
|
stdout_bytes: result.stdout_bytes,
|
|
@@ -318,13 +320,15 @@ function formatResult(result: ExecSandboxResult): ToolExecutionResult {
|
|
|
318
320
|
stderr_path: result.stderr_path,
|
|
319
321
|
meta_path: result.meta_path,
|
|
320
322
|
},
|
|
321
|
-
isError: result.timed_out || result.signal !== null || result.exit_code !== 0,
|
|
323
|
+
isError: result.aborted === true || result.timed_out || result.signal !== null || result.exit_code !== 0,
|
|
322
324
|
};
|
|
323
325
|
}
|
|
324
326
|
|
|
325
327
|
function formatExit(result: ExecSandboxResult): string {
|
|
326
328
|
// force_resolved means a non-closing (D-state) child was force-resolved past its
|
|
327
329
|
// hard deadline rather than observed exiting; distinguish it from a clean timeout.
|
|
330
|
+
if (result.aborted && result.force_resolved) return "aborted(force-killed)";
|
|
331
|
+
if (result.aborted) return "aborted";
|
|
328
332
|
if (result.force_resolved) return "timeout(force-killed)";
|
|
329
333
|
if (result.timed_out) return "timeout";
|
|
330
334
|
if (result.signal) return `signal:${result.signal}`;
|
|
@@ -135,14 +135,38 @@ const CONTEXT_MODE_GUIDANCE_BY_LANE: Record<Exclude<ContextModePolicy, "none">,
|
|
|
135
135
|
"Use `gsd_resume` for prior context, `gsd_exec_search` for saved evidence, and `gsd_exec` for noisy doc validation commands.",
|
|
136
136
|
};
|
|
137
137
|
|
|
138
|
-
// Per-unit overrides win over the lane default.
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
// Per-unit overrides win over the lane default. Some units intentionally run
|
|
139
|
+
// with narrower tool contracts than their shared Context Mode lane, so their
|
|
140
|
+
// guidance must name only tools the unit can actually call.
|
|
141
|
+
export const CONTEXT_MODE_GUIDANCE_BY_UNIT: Readonly<Record<string, string>> = {
|
|
142
|
+
"discuss-milestone":
|
|
143
|
+
"Use `ask_user_questions` to continue the milestone interview, then persist outcomes with `gsd_summary_save`, `gsd_decision_save`, `gsd_requirement_save`, `gsd_requirement_update`, `gsd_plan_milestone`, or `gsd_milestone_generate_id` as appropriate.",
|
|
144
|
+
"discuss-slice":
|
|
145
|
+
"Use `ask_user_questions` to continue the slice interview, then persist outcomes with `gsd_summary_save` or `gsd_decision_save` as appropriate.",
|
|
146
|
+
"discuss-project":
|
|
147
|
+
"Use `ask_user_questions` to continue the project interview, then persist outcomes with `gsd_summary_save`, `gsd_decision_save`, or `gsd_requirement_save` as appropriate.",
|
|
148
|
+
"discuss-requirements":
|
|
149
|
+
"Use `ask_user_questions` to continue the requirements interview, then persist outcomes with `gsd_requirement_save` or `gsd_summary_save` as appropriate.",
|
|
150
|
+
"replan-slice":
|
|
151
|
+
"Use `gsd_replan_slice` to persist the revised slice plan, and `gsd_decision_save` for planning decisions that need durable rationale.",
|
|
152
|
+
"reassess-roadmap":
|
|
153
|
+
"Use `gsd_milestone_status` to inspect current milestone state, then `gsd_reassess_roadmap` to persist the roadmap reassessment.",
|
|
144
154
|
"run-uat":
|
|
145
155
|
"Use `gsd_uat_exec` for acceptance checks so evidence is typed as UAT-owned, and `gsd_resume` after compaction or resume.",
|
|
156
|
+
"research-project":
|
|
157
|
+
"Dispatch parallel scout subagents for stack, features, architecture, and pitfalls research; each writes one file under `.gsd/research/` (`STACK.md`, `FEATURES.md`, `ARCHITECTURE.md`, `PITFALLS.md`).",
|
|
158
|
+
"gate-evaluate":
|
|
159
|
+
"Use `subagent` to dispatch tester agents, then persist each gate with `gsd_save_gate_result`; rely on testers for verification evidence.",
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Per-unit guidance for the nested render mode (renderMode: "nested"), used when this
|
|
163
|
+
// unit's Context Mode line is embedded into a subagent prompt — e.g. the tester prompts
|
|
164
|
+
// dispatched by gate-evaluate. Must instruct the subagent on what IT should do, not
|
|
165
|
+
// re-state the parent coordinator's dispatch instructions. Falls back to
|
|
166
|
+
// CONTEXT_MODE_GUIDANCE_BY_UNIT then the lane default when no nested entry exists.
|
|
167
|
+
export const CONTEXT_MODE_NESTED_GUIDANCE_BY_UNIT: Readonly<Record<string, string>> = {
|
|
168
|
+
"gate-evaluate":
|
|
169
|
+
"Run verification checks to answer the gate question, then persist the verdict with `gsd_save_gate_result`.",
|
|
146
170
|
};
|
|
147
171
|
|
|
148
172
|
/**
|
|
@@ -160,7 +184,9 @@ export function composeContextModeInstructions(
|
|
|
160
184
|
|
|
161
185
|
const lane = CONTEXT_MODE_LANE_LABELS[manifest.contextMode];
|
|
162
186
|
const guidance =
|
|
163
|
-
|
|
187
|
+
(opts.renderMode === "nested" ? CONTEXT_MODE_NESTED_GUIDANCE_BY_UNIT[unitType] : undefined)
|
|
188
|
+
?? CONTEXT_MODE_GUIDANCE_BY_UNIT[unitType]
|
|
189
|
+
?? CONTEXT_MODE_GUIDANCE_BY_LANE[manifest.contextMode];
|
|
164
190
|
if (opts.renderMode === "nested") {
|
|
165
191
|
return `Context Mode (${lane} lane): ${guidance}`;
|
|
166
192
|
}
|
|
@@ -98,7 +98,13 @@ export const UNIT_REGISTRY = {
|
|
|
98
98
|
scopeClass: "standard",
|
|
99
99
|
phaseChain: ["research"],
|
|
100
100
|
toolContract: {
|
|
101
|
-
allowedGsdTools: [
|
|
101
|
+
allowedGsdTools: [
|
|
102
|
+
"gsd_summary_save",
|
|
103
|
+
"gsd_decision_save",
|
|
104
|
+
"gsd_exec",
|
|
105
|
+
"gsd_exec_search",
|
|
106
|
+
"gsd_resume",
|
|
107
|
+
],
|
|
102
108
|
requiredWorkflowTools: ["gsd_summary_save"],
|
|
103
109
|
},
|
|
104
110
|
},
|
|
@@ -154,7 +160,15 @@ export const UNIT_REGISTRY = {
|
|
|
154
160
|
scopeClass: "section-close",
|
|
155
161
|
phaseChain: ["validation", "planning"],
|
|
156
162
|
toolContract: {
|
|
157
|
-
allowedGsdTools: [
|
|
163
|
+
allowedGsdTools: [
|
|
164
|
+
"gsd_milestone_status",
|
|
165
|
+
"gsd_exec",
|
|
166
|
+
"gsd_exec_search",
|
|
167
|
+
"gsd_resume",
|
|
168
|
+
"gsd_validate_milestone",
|
|
169
|
+
"gsd_reassess_roadmap",
|
|
170
|
+
"subagent",
|
|
171
|
+
],
|
|
158
172
|
requiredWorkflowTools: ["gsd_milestone_status", "gsd_validate_milestone", "gsd_reassess_roadmap"],
|
|
159
173
|
},
|
|
160
174
|
},
|
|
@@ -165,6 +179,9 @@ export const UNIT_REGISTRY = {
|
|
|
165
179
|
toolContract: {
|
|
166
180
|
allowedGsdTools: [
|
|
167
181
|
"gsd_milestone_status",
|
|
182
|
+
"gsd_exec",
|
|
183
|
+
"gsd_exec_search",
|
|
184
|
+
"gsd_resume",
|
|
168
185
|
"gsd_requirement_update",
|
|
169
186
|
"gsd_summary_save",
|
|
170
187
|
"gsd_complete_milestone",
|
|
@@ -244,6 +261,8 @@ export const UNIT_REGISTRY = {
|
|
|
244
261
|
allowedGsdTools: [
|
|
245
262
|
"gsd_milestone_status",
|
|
246
263
|
"gsd_exec",
|
|
264
|
+
"gsd_exec_search",
|
|
265
|
+
"gsd_resume",
|
|
247
266
|
"gsd_slice_complete",
|
|
248
267
|
"gsd_task_reopen",
|
|
249
268
|
"gsd_replan_slice",
|
|
@@ -413,12 +432,15 @@ export const UNIT_REGISTRY = {
|
|
|
413
432
|
requiredWorkflowTools: ["ask_user_questions"],
|
|
414
433
|
},
|
|
415
434
|
},
|
|
435
|
+
// research-project dispatches 4 parallel scout subagents (Task calls); each scout
|
|
436
|
+
// writes one file under .gsd/research/ directly. The parent coordinator does not
|
|
437
|
+
// call gsd_summary_save or gsd_decision_save — the scouts own their own output.
|
|
416
438
|
"research-project": {
|
|
417
439
|
kind: "primary",
|
|
418
440
|
scopeClass: "standard",
|
|
419
441
|
phaseChain: ["research"],
|
|
420
442
|
toolContract: {
|
|
421
|
-
allowedGsdTools: [
|
|
443
|
+
allowedGsdTools: [],
|
|
422
444
|
requiredWorkflowTools: [],
|
|
423
445
|
},
|
|
424
446
|
},
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
2
|
// File Purpose: Block new workflow entry when completed milestone branches are still unmerged.
|
|
3
3
|
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
|
|
4
6
|
import {
|
|
5
7
|
nativeBranchExists,
|
|
6
8
|
nativeDetectMainBranch,
|
|
@@ -12,17 +14,23 @@ import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
|
|
|
12
14
|
import { getAllMilestones } from "./gsd-db.js";
|
|
13
15
|
import { resolveMilestoneIntegrationBranch } from "./git-service.js";
|
|
14
16
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
15
|
-
import { captureRootDirtySnapshot, type RootDirtyEntry } from "./root-write-leak-guard.js";
|
|
16
17
|
import { isClosedStatus } from "./status-guards.js";
|
|
17
18
|
|
|
19
|
+
export interface UnmergedMilestoneDirtyEntry {
|
|
20
|
+
path: string;
|
|
21
|
+
status: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
export interface UnmergedMilestoneBlocker {
|
|
19
25
|
milestoneId: string;
|
|
20
26
|
branch: string;
|
|
21
27
|
integrationBranch: string;
|
|
22
28
|
files: string[];
|
|
23
|
-
dirtyOverlap:
|
|
29
|
+
dirtyOverlap: UnmergedMilestoneDirtyEntry[];
|
|
24
30
|
}
|
|
25
31
|
|
|
32
|
+
type UnmergedMilestoneDirtySnapshot = Map<string, UnmergedMilestoneDirtyEntry>;
|
|
33
|
+
|
|
26
34
|
const BLOCKED_COMMANDS = new Set([
|
|
27
35
|
"auto",
|
|
28
36
|
"next",
|
|
@@ -67,6 +75,32 @@ function isRuntimePath(path: string): boolean {
|
|
|
67
75
|
return path === ".gsd" || path.startsWith(".gsd/");
|
|
68
76
|
}
|
|
69
77
|
|
|
78
|
+
function captureDirtyPathStatusSnapshot(rootPath: string): UnmergedMilestoneDirtySnapshot {
|
|
79
|
+
const snapshot: UnmergedMilestoneDirtySnapshot = new Map();
|
|
80
|
+
let status = "";
|
|
81
|
+
try {
|
|
82
|
+
status = execFileSync("git", ["status", "--porcelain", "--untracked-files=all"], {
|
|
83
|
+
cwd: rootPath,
|
|
84
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
85
|
+
encoding: "utf-8",
|
|
86
|
+
});
|
|
87
|
+
} catch {
|
|
88
|
+
return snapshot;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (const line of status.split("\n")) {
|
|
92
|
+
if (!line.trim()) continue;
|
|
93
|
+
const code = line.slice(0, 2);
|
|
94
|
+
const path = line.slice(3).replace(/^"|"$/g, "");
|
|
95
|
+
if (!path || isRuntimePath(path)) continue;
|
|
96
|
+
snapshot.set(path, {
|
|
97
|
+
path,
|
|
98
|
+
status: code.trim() || code,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return snapshot;
|
|
102
|
+
}
|
|
103
|
+
|
|
70
104
|
function formatCommandLabel(attemptedCommand: string): string {
|
|
71
105
|
const trimmed = attemptedCommand.trim();
|
|
72
106
|
return trimmed ? `/gsd ${trimmed}` : "/gsd";
|
|
@@ -132,7 +166,7 @@ export async function findUnmergedCompletedMilestones(base: string): Promise<Unm
|
|
|
132
166
|
await ensureDbOpen(base);
|
|
133
167
|
|
|
134
168
|
const blockers: UnmergedMilestoneBlocker[] = [];
|
|
135
|
-
|
|
169
|
+
let dirtyByPath: UnmergedMilestoneDirtySnapshot | null = null;
|
|
136
170
|
|
|
137
171
|
for (const milestone of getAllMilestones()) {
|
|
138
172
|
if (!isClosedStatus(milestone.status)) continue;
|
|
@@ -158,14 +192,16 @@ export async function findUnmergedCompletedMilestones(base: string): Promise<Unm
|
|
|
158
192
|
const uniqueFiles = [...new Set(files)].sort();
|
|
159
193
|
if (uniqueFiles.length === 0) continue;
|
|
160
194
|
|
|
195
|
+
if (!dirtyByPath) dirtyByPath = captureDirtyPathStatusSnapshot(base);
|
|
196
|
+
const dirtySnapshot = dirtyByPath;
|
|
161
197
|
blockers.push({
|
|
162
198
|
milestoneId: milestone.id,
|
|
163
199
|
branch,
|
|
164
200
|
integrationBranch,
|
|
165
201
|
files: uniqueFiles,
|
|
166
202
|
dirtyOverlap: uniqueFiles
|
|
167
|
-
.map((path) =>
|
|
168
|
-
.filter((entry): entry is
|
|
203
|
+
.map((path) => dirtySnapshot.get(path))
|
|
204
|
+
.filter((entry): entry is UnmergedMilestoneDirtyEntry => Boolean(entry)),
|
|
169
205
|
});
|
|
170
206
|
}
|
|
171
207
|
|
|
@@ -7,7 +7,7 @@ import { getAutoWorktreePath, isInAutoWorktree } from "./auto-worktree.js";
|
|
|
7
7
|
import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
|
|
8
8
|
import { refreshWorkflowDatabaseFromDisk } from "./db-workspace.js";
|
|
9
9
|
import { getIsolationMode } from "./preferences.js";
|
|
10
|
-
import { deriveState } from "./state.js";
|
|
10
|
+
import { deriveState, invalidateStateCache } from "./state.js";
|
|
11
11
|
import type { GSDState } from "./types.js";
|
|
12
12
|
import { detectWorktreeName } from "./worktree.js";
|
|
13
13
|
|
|
@@ -133,12 +133,7 @@ export function formatValidationBlockedMessage(
|
|
|
133
133
|
].join("\n\n");
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
base: string,
|
|
138
|
-
attemptedCommand = "",
|
|
139
|
-
): Promise<string | null> {
|
|
140
|
-
await ensureDbOpen(base);
|
|
141
|
-
refreshWorkflowDatabaseFromDisk();
|
|
136
|
+
async function deriveValidationBlockState(base: string): Promise<GSDState> {
|
|
142
137
|
let state = await deriveState(base);
|
|
143
138
|
|
|
144
139
|
if (
|
|
@@ -153,5 +148,16 @@ export async function getValidationBlockMessageForBase(
|
|
|
153
148
|
}
|
|
154
149
|
}
|
|
155
150
|
|
|
151
|
+
return state;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function getValidationBlockMessageForBase(
|
|
155
|
+
base: string,
|
|
156
|
+
attemptedCommand = "",
|
|
157
|
+
): Promise<string | null> {
|
|
158
|
+
await ensureDbOpen(base);
|
|
159
|
+
refreshWorkflowDatabaseFromDisk();
|
|
160
|
+
invalidateStateCache();
|
|
161
|
+
const state = await deriveValidationBlockState(base);
|
|
156
162
|
return formatValidationBlockedMessage(state, attemptedCommand);
|
|
157
163
|
}
|
|
@@ -7,6 +7,7 @@ import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
|
7
7
|
import { isSafeToAutoResolve } from "./auto-worktree.js";
|
|
8
8
|
import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
|
|
9
9
|
import {
|
|
10
|
+
listMergeStateBlockers,
|
|
10
11
|
probeGitConflictState,
|
|
11
12
|
reconcileGitConflictsOnSignal,
|
|
12
13
|
type GitConflictProbeResult,
|
|
@@ -30,6 +31,9 @@ export type WorkspaceGitReadyResult =
|
|
|
30
31
|
targets: string[];
|
|
31
32
|
};
|
|
32
33
|
|
|
34
|
+
const CLEAN_TARGET_CACHE_TTL_MS = 10_000;
|
|
35
|
+
const cleanTargetProbeCache = new Map<string, number>();
|
|
36
|
+
|
|
33
37
|
function normalizeTargetPath(path: string): string {
|
|
34
38
|
try {
|
|
35
39
|
return realpathSync(path);
|
|
@@ -110,6 +114,25 @@ function formatBlockReason(
|
|
|
110
114
|
|
|
111
115
|
async function ensureTargetGitReady(target: string): Promise<WorkspaceGitReadyResult> {
|
|
112
116
|
const fixesApplied: string[] = [];
|
|
117
|
+
const cacheKey = normalizeTargetPath(target);
|
|
118
|
+
const cachedCleanAt = cleanTargetProbeCache.get(cacheKey);
|
|
119
|
+
if (cachedCleanAt !== undefined) {
|
|
120
|
+
if (Date.now() - cachedCleanAt < CLEAN_TARGET_CACHE_TTL_MS) {
|
|
121
|
+
// Merge-state markers (MERGE_HEAD, rebase-apply, rebase-merge) are the most
|
|
122
|
+
// common way a repo transitions from clean to dirty within the TTL window,
|
|
123
|
+
// including when a non-git folder is initialized as a repo mid-TTL and a
|
|
124
|
+
// merge immediately introduces conflicts. Check them here — they are pure
|
|
125
|
+
// existsSync calls with no git subprocess — and fall through to a full probe
|
|
126
|
+
// only when markers are present.
|
|
127
|
+
if (listMergeStateBlockers(cacheKey).length === 0) {
|
|
128
|
+
return { ok: true, fixesApplied };
|
|
129
|
+
}
|
|
130
|
+
cleanTargetProbeCache.delete(cacheKey);
|
|
131
|
+
} else {
|
|
132
|
+
cleanTargetProbeCache.delete(cacheKey);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
113
136
|
let probe = probeGitConflictState(target);
|
|
114
137
|
|
|
115
138
|
for (let attempt = 0; attempt < 3 && probe.status === "dirty"; attempt++) {
|
|
@@ -129,6 +152,7 @@ async function ensureTargetGitReady(target: string): Promise<WorkspaceGitReadyRe
|
|
|
129
152
|
}
|
|
130
153
|
|
|
131
154
|
if (probe.status === "unknown") {
|
|
155
|
+
cleanTargetProbeCache.delete(cacheKey);
|
|
132
156
|
return {
|
|
133
157
|
ok: false,
|
|
134
158
|
reason: formatBlockReason("unrecoverable", [], [target]),
|
|
@@ -141,6 +165,7 @@ async function ensureTargetGitReady(target: string): Promise<WorkspaceGitReadyRe
|
|
|
141
165
|
|
|
142
166
|
const conflictedPaths = productConflictPaths(probe);
|
|
143
167
|
if (conflictedPaths.length > 0 || probe.checkFailures.length > 0) {
|
|
168
|
+
cleanTargetProbeCache.delete(cacheKey);
|
|
144
169
|
return {
|
|
145
170
|
ok: false,
|
|
146
171
|
reason: formatBlockReason("product-conflicts", conflictedPaths, [target]),
|
|
@@ -151,6 +176,12 @@ async function ensureTargetGitReady(target: string): Promise<WorkspaceGitReadyRe
|
|
|
151
176
|
};
|
|
152
177
|
}
|
|
153
178
|
|
|
179
|
+
if (probe.status === "clean") {
|
|
180
|
+
cleanTargetProbeCache.set(cacheKey, Date.now());
|
|
181
|
+
} else {
|
|
182
|
+
cleanTargetProbeCache.delete(cacheKey);
|
|
183
|
+
}
|
|
184
|
+
|
|
154
185
|
return { ok: true, fixesApplied };
|
|
155
186
|
}
|
|
156
187
|
|
|
File without changes
|
|
File without changes
|