gsd-pi 2.45.0-dev.fdcf73c → 2.46.0-dev.cc9d310
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/extensions/gsd/auto/phases.js +14 -35
- package/dist/resources/extensions/gsd/auto/session.js +0 -11
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +112 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +25 -96
- package/dist/resources/extensions/gsd/auto-start.js +2 -3
- package/dist/resources/extensions/gsd/auto.js +8 -52
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +18 -0
- package/dist/resources/extensions/gsd/commands/context.js +0 -4
- package/dist/resources/extensions/gsd/commands/handlers/parallel.js +1 -1
- package/dist/resources/extensions/gsd/crash-recovery.js +2 -4
- package/dist/resources/extensions/gsd/dashboard-overlay.js +0 -44
- package/dist/resources/extensions/gsd/doctor-checks.js +166 -1
- package/dist/resources/extensions/gsd/doctor.js +3 -1
- package/dist/resources/extensions/gsd/gsd-db.js +11 -2
- package/dist/resources/extensions/gsd/guided-flow.js +1 -2
- package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +5 -18
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +10 -23
- package/dist/resources/extensions/gsd/prompts/discuss.md +2 -2
- package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -15
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/dist/resources/extensions/gsd/prompts/queue.md +2 -2
- package/dist/resources/extensions/gsd/prompts/quick-task.md +2 -0
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -3
- package/dist/resources/extensions/gsd/prompts/rethink.md +7 -2
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/session-lock.js +1 -3
- package/dist/resources/extensions/gsd/state.js +7 -0
- package/dist/resources/extensions/gsd/sync-lock.js +89 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +58 -12
- package/dist/resources/extensions/gsd/tools/complete-slice.js +56 -11
- package/dist/resources/extensions/gsd/tools/complete-task.js +50 -2
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +37 -1
- package/dist/resources/extensions/gsd/tools/plan-slice.js +30 -1
- package/dist/resources/extensions/gsd/tools/plan-task.js +27 -1
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +32 -2
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +86 -0
- package/dist/resources/extensions/gsd/tools/reopen-task.js +90 -0
- package/dist/resources/extensions/gsd/tools/replan-slice.js +32 -2
- package/dist/resources/extensions/gsd/unit-ownership.js +85 -0
- package/dist/resources/extensions/gsd/workflow-events.js +102 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +56 -1
- package/dist/resources/extensions/gsd/workflow-manifest.js +244 -0
- package/dist/resources/extensions/gsd/workflow-migration.js +280 -0
- package/dist/resources/extensions/gsd/workflow-projections.js +373 -0
- package/dist/resources/extensions/gsd/workflow-reconcile.js +411 -0
- package/dist/resources/extensions/gsd/write-intercept.js +84 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -19
- package/src/resources/extensions/gsd/auto/phases.ts +11 -35
- package/src/resources/extensions/gsd/auto/session.ts +0 -18
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +131 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +0 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +25 -106
- package/src/resources/extensions/gsd/auto-start.ts +1 -3
- package/src/resources/extensions/gsd/auto.ts +4 -80
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
- package/src/resources/extensions/gsd/commands/context.ts +0 -5
- package/src/resources/extensions/gsd/commands/handlers/parallel.ts +1 -1
- package/src/resources/extensions/gsd/crash-recovery.ts +1 -5
- package/src/resources/extensions/gsd/dashboard-overlay.ts +0 -50
- package/src/resources/extensions/gsd/doctor-checks.ts +179 -1
- package/src/resources/extensions/gsd/doctor-types.ts +7 -1
- package/src/resources/extensions/gsd/doctor.ts +4 -1
- package/src/resources/extensions/gsd/gsd-db.ts +11 -2
- package/src/resources/extensions/gsd/guided-flow.ts +1 -2
- package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +5 -21
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +10 -23
- package/src/resources/extensions/gsd/prompts/discuss.md +2 -2
- package/src/resources/extensions/gsd/prompts/execute-task.md +5 -15
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/src/resources/extensions/gsd/prompts/queue.md +2 -2
- package/src/resources/extensions/gsd/prompts/quick-task.md +2 -0
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/rethink.md +7 -2
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/session-lock.ts +0 -4
- package/src/resources/extensions/gsd/state.ts +8 -0
- package/src/resources/extensions/gsd/sync-lock.ts +94 -0
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +5 -13
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +6 -10
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +264 -228
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +317 -250
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +2 -8
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration-proof.test.ts +15 -24
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +8 -9
- package/src/resources/extensions/gsd/tests/parallel-budget-atomicity.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +0 -7
- package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +20 -24
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +0 -2
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +9 -6
- package/src/resources/extensions/gsd/tests/post-mutation-hook.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/projection-regression.test.ts +174 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +15 -14
- package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +155 -0
- package/src/resources/extensions/gsd/tests/reopen-task.test.ts +165 -0
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +1 -4
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/sync-lock.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/unit-ownership.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/workflow-events.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +76 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +70 -13
- package/src/resources/extensions/gsd/tools/complete-slice.ts +68 -11
- package/src/resources/extensions/gsd/tools/complete-task.ts +63 -1
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +45 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +38 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +35 -1
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +39 -1
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +125 -0
- package/src/resources/extensions/gsd/tools/reopen-task.ts +129 -0
- package/src/resources/extensions/gsd/tools/replan-slice.ts +38 -1
- package/src/resources/extensions/gsd/types.ts +8 -0
- package/src/resources/extensions/gsd/unit-ownership.ts +104 -0
- package/src/resources/extensions/gsd/workflow-events.ts +154 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +51 -1
- package/src/resources/extensions/gsd/workflow-manifest.ts +334 -0
- package/src/resources/extensions/gsd/workflow-migration.ts +345 -0
- package/src/resources/extensions/gsd/workflow-projections.ts +425 -0
- package/src/resources/extensions/gsd/workflow-reconcile.ts +503 -0
- package/src/resources/extensions/gsd/write-intercept.ts +90 -0
- /package/dist/web/standalone/.next/static/{zWYDSwB-terOjfhmWzqk1 → ZIDqryyYDroh_8AnaAOSG}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{zWYDSwB-terOjfhmWzqk1 → ZIDqryyYDroh_8AnaAOSG}/_ssgManifest.js +0 -0
|
@@ -11,13 +11,15 @@ import { MAX_RECOVERY_CHARS, BUDGET_THRESHOLDS, } from "./types.js";
|
|
|
11
11
|
import { detectStuck } from "./detect-stuck.js";
|
|
12
12
|
import { runUnit } from "./run-unit.js";
|
|
13
13
|
import { debugLog } from "../debug-logger.js";
|
|
14
|
-
import { gsdRoot } from "../paths.js";
|
|
15
|
-
import { atomicWriteSync } from "../atomic-write.js";
|
|
16
14
|
import { PROJECT_FILES } from "../detection.js";
|
|
17
15
|
import { MergeConflictError } from "../git-service.js";
|
|
18
16
|
import { join } from "node:path";
|
|
19
17
|
import { existsSync, cpSync } from "node:fs";
|
|
20
18
|
import { logWarning } from "../workflow-logger.js";
|
|
19
|
+
import { gsdRoot } from "../paths.js";
|
|
20
|
+
import { atomicWriteSync } from "../atomic-write.js";
|
|
21
|
+
import { verifyExpectedArtifact } from "../auto-recovery.js";
|
|
22
|
+
import { writeUnitRuntimeRecord } from "../unit-runtime.js";
|
|
21
23
|
// ─── generateMilestoneReport ──────────────────────────────────────────────────
|
|
22
24
|
/**
|
|
23
25
|
* Generate and write an HTML milestone report snapshot.
|
|
@@ -179,11 +181,7 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
179
181
|
.filter((m) => m.status !== "complete" && m.status !== "parked")
|
|
180
182
|
.map((m) => m.id);
|
|
181
183
|
deps.pruneQueueOrder(s.basePath, pendingIds);
|
|
182
|
-
// Reset completed-units tracking for the new milestone — stale entries
|
|
183
|
-
// from the previous milestone cause the dispatch loop to skip units
|
|
184
|
-
// that haven't actually been completed in the new milestone's context.
|
|
185
184
|
// Archive the old completed-units.json instead of wiping it (#2313).
|
|
186
|
-
s.completedUnits = [];
|
|
187
185
|
try {
|
|
188
186
|
const completedKeysPath = join(gsdRoot(s.basePath), "completed-units.json");
|
|
189
187
|
if (existsSync(completedKeysPath) && s.currentMilestoneId) {
|
|
@@ -368,7 +366,7 @@ export async function runDispatch(ic, preData, loopState) {
|
|
|
368
366
|
if (loopState.stuckRecoveryAttempts === 0) {
|
|
369
367
|
// Level 1: try verifying the artifact, then cache invalidation + retry
|
|
370
368
|
loopState.stuckRecoveryAttempts++;
|
|
371
|
-
const artifactExists =
|
|
369
|
+
const artifactExists = verifyExpectedArtifact(unitType, unitId, s.basePath);
|
|
372
370
|
if (artifactExists) {
|
|
373
371
|
debugLog("autoLoop", {
|
|
374
372
|
phase: "stuck-recovery",
|
|
@@ -588,7 +586,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
588
586
|
const unitStartSeq = ic.nextSeq();
|
|
589
587
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
|
|
590
588
|
deps.captureAvailableSkills();
|
|
591
|
-
|
|
589
|
+
writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
|
|
592
590
|
phase: "dispatched",
|
|
593
591
|
wrapupWarningSent: false,
|
|
594
592
|
timeoutAt: null,
|
|
@@ -703,7 +701,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
703
701
|
});
|
|
704
702
|
// Write preliminary lock (no session path yet — runUnit creates a new session).
|
|
705
703
|
// Crash recovery can still identify the in-flight unit from this lock.
|
|
706
|
-
deps.writeLock(deps.lockBase(), unitType, unitId
|
|
704
|
+
deps.writeLock(deps.lockBase(), unitType, unitId);
|
|
707
705
|
debugLog("autoLoop", {
|
|
708
706
|
phase: "runUnit-start",
|
|
709
707
|
iteration: ic.iteration,
|
|
@@ -720,8 +718,8 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
720
718
|
});
|
|
721
719
|
// Now that runUnit has called newSession(), the session file path is correct.
|
|
722
720
|
const sessionFile = deps.getSessionFile(ctx);
|
|
723
|
-
deps.updateSessionLock(deps.lockBase(), unitType, unitId,
|
|
724
|
-
deps.writeLock(deps.lockBase(), unitType, unitId,
|
|
721
|
+
deps.updateSessionLock(deps.lockBase(), unitType, unitId, sessionFile);
|
|
722
|
+
deps.writeLock(deps.lockBase(), unitType, unitId, sessionFile);
|
|
725
723
|
// Tag the most recent window entry with error info for stuck detection
|
|
726
724
|
if (unitResult.status === "error" || unitResult.status === "cancelled") {
|
|
727
725
|
const lastEntry = loopState.recentUnits[loopState.recentUnits.length - 1];
|
|
@@ -765,8 +763,8 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
765
763
|
warning: "Task completed with 0 tool calls — likely hallucinated, marking as failed",
|
|
766
764
|
});
|
|
767
765
|
ctx.ui.notify(`${unitType} ${unitId} completed with 0 tool calls — hallucinated summary, will retry`, "warning");
|
|
768
|
-
//
|
|
769
|
-
//
|
|
766
|
+
// Fall through to next iteration where dispatch will re-derive
|
|
767
|
+
// and re-dispatch this task.
|
|
770
768
|
return { action: "next", data: { unitStartedAt: s.currentUnit.startedAt } };
|
|
771
769
|
}
|
|
772
770
|
}
|
|
@@ -776,27 +774,8 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
776
774
|
}
|
|
777
775
|
const skipArtifactVerification = unitType.startsWith("hook/") || unitType === "custom-step";
|
|
778
776
|
const artifactVerified = skipArtifactVerification ||
|
|
779
|
-
|
|
777
|
+
verifyExpectedArtifact(unitType, unitId, s.basePath);
|
|
780
778
|
if (artifactVerified) {
|
|
781
|
-
s.completedUnits.push({
|
|
782
|
-
type: unitType,
|
|
783
|
-
id: unitId,
|
|
784
|
-
startedAt: s.currentUnit.startedAt,
|
|
785
|
-
finishedAt: Date.now(),
|
|
786
|
-
});
|
|
787
|
-
if (s.completedUnits.length > 200) {
|
|
788
|
-
s.completedUnits = s.completedUnits.slice(-200);
|
|
789
|
-
}
|
|
790
|
-
// Flush completed-units to disk so the record survives crashes
|
|
791
|
-
try {
|
|
792
|
-
const completedKeysPath = join(gsdRoot(s.basePath), "completed-units.json");
|
|
793
|
-
const keys = s.completedUnits.map((u) => `${u.type}/${u.id}`);
|
|
794
|
-
atomicWriteSync(completedKeysPath, JSON.stringify(keys, null, 2));
|
|
795
|
-
}
|
|
796
|
-
catch (e) {
|
|
797
|
-
logWarning("engine", "Failed to flush completed-units to disk", { error: String(e) });
|
|
798
|
-
}
|
|
799
|
-
deps.clearUnitRuntimeRecord(s.basePath, unitType, unitId);
|
|
800
779
|
s.unitDispatchCount.delete(`${unitType}/${unitId}`);
|
|
801
780
|
s.unitRecoveryCount.delete(`${unitType}/${unitId}`);
|
|
802
781
|
}
|
|
@@ -829,8 +808,8 @@ export async function runFinalize(ic, iterData, sidecarItem) {
|
|
|
829
808
|
// Sidecar items use lightweight pre-verification opts
|
|
830
809
|
const preVerificationOpts = sidecarItem
|
|
831
810
|
? sidecarItem.kind === "hook"
|
|
832
|
-
? { skipSettleDelay: true,
|
|
833
|
-
: { skipSettleDelay: true
|
|
811
|
+
? { skipSettleDelay: true, skipWorktreeSync: true }
|
|
812
|
+
: { skipSettleDelay: true }
|
|
834
813
|
: undefined;
|
|
835
814
|
const preResult = await deps.postUnitPreVerification(postUnitCtx, preVerificationOpts);
|
|
836
815
|
if (preResult === "dispatched") {
|
|
@@ -46,7 +46,6 @@ export class AutoSession {
|
|
|
46
46
|
// ── Current unit ─────────────────────────────────────────────────────────
|
|
47
47
|
currentUnit = null;
|
|
48
48
|
currentUnitRouting = null;
|
|
49
|
-
completedUnits = [];
|
|
50
49
|
currentMilestoneId = null;
|
|
51
50
|
// ── Model state ──────────────────────────────────────────────────────────
|
|
52
51
|
autoModeStartModel = null;
|
|
@@ -100,14 +99,6 @@ export class AutoSession {
|
|
|
100
99
|
get lockBasePath() {
|
|
101
100
|
return this.originalBasePath || this.basePath;
|
|
102
101
|
}
|
|
103
|
-
completeCurrentUnit() {
|
|
104
|
-
if (!this.currentUnit)
|
|
105
|
-
return null;
|
|
106
|
-
const done = { ...this.currentUnit, finishedAt: Date.now() };
|
|
107
|
-
this.completedUnits.push(done);
|
|
108
|
-
this.currentUnit = null;
|
|
109
|
-
return done;
|
|
110
|
-
}
|
|
111
102
|
reset() {
|
|
112
103
|
this.clearTimers();
|
|
113
104
|
// Lifecycle
|
|
@@ -129,7 +120,6 @@ export class AutoSession {
|
|
|
129
120
|
// Unit
|
|
130
121
|
this.currentUnit = null;
|
|
131
122
|
this.currentUnitRouting = null;
|
|
132
|
-
this.completedUnits = [];
|
|
133
123
|
this.currentMilestoneId = null;
|
|
134
124
|
// Model
|
|
135
125
|
this.autoModeStartModel = null;
|
|
@@ -164,7 +154,6 @@ export class AutoSession {
|
|
|
164
154
|
activeRunDir: this.activeRunDir,
|
|
165
155
|
currentMilestoneId: this.currentMilestoneId,
|
|
166
156
|
currentUnit: this.currentUnit,
|
|
167
|
-
completedUnits: this.completedUnits.length,
|
|
168
157
|
unitDispatchCount: Object.fromEntries(this.unitDispatchCount),
|
|
169
158
|
};
|
|
170
159
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// GSD Auto-mode — Artifact Path Resolution
|
|
2
|
+
//
|
|
3
|
+
// resolveExpectedArtifactPath and diagnoseExpectedArtifact moved here from
|
|
4
|
+
// auto-recovery.ts (Phase 5 dead-code cleanup). The artifact verification
|
|
5
|
+
// function was removed entirely — callers now query WorkflowEngine directly.
|
|
6
|
+
import { resolveMilestonePath, resolveSlicePath, relMilestoneFile, relSliceFile, buildMilestoneFileName, buildSliceFileName, buildTaskFileName, } from "./paths.js";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the expected artifact for a unit to an absolute path.
|
|
10
|
+
*/
|
|
11
|
+
export function resolveExpectedArtifactPath(unitType, unitId, base) {
|
|
12
|
+
const parts = unitId.split("/");
|
|
13
|
+
const mid = parts[0];
|
|
14
|
+
const sid = parts[1];
|
|
15
|
+
switch (unitType) {
|
|
16
|
+
case "discuss-milestone": {
|
|
17
|
+
const dir = resolveMilestonePath(base, mid);
|
|
18
|
+
return dir ? join(dir, buildMilestoneFileName(mid, "CONTEXT")) : null;
|
|
19
|
+
}
|
|
20
|
+
case "research-milestone": {
|
|
21
|
+
const dir = resolveMilestonePath(base, mid);
|
|
22
|
+
return dir ? join(dir, buildMilestoneFileName(mid, "RESEARCH")) : null;
|
|
23
|
+
}
|
|
24
|
+
case "plan-milestone": {
|
|
25
|
+
const dir = resolveMilestonePath(base, mid);
|
|
26
|
+
return dir ? join(dir, buildMilestoneFileName(mid, "ROADMAP")) : null;
|
|
27
|
+
}
|
|
28
|
+
case "research-slice": {
|
|
29
|
+
const dir = resolveSlicePath(base, mid, sid);
|
|
30
|
+
return dir ? join(dir, buildSliceFileName(sid, "RESEARCH")) : null;
|
|
31
|
+
}
|
|
32
|
+
case "plan-slice": {
|
|
33
|
+
const dir = resolveSlicePath(base, mid, sid);
|
|
34
|
+
return dir ? join(dir, buildSliceFileName(sid, "PLAN")) : null;
|
|
35
|
+
}
|
|
36
|
+
case "reassess-roadmap": {
|
|
37
|
+
const dir = resolveSlicePath(base, mid, sid);
|
|
38
|
+
return dir ? join(dir, buildSliceFileName(sid, "ASSESSMENT")) : null;
|
|
39
|
+
}
|
|
40
|
+
case "run-uat": {
|
|
41
|
+
const dir = resolveSlicePath(base, mid, sid);
|
|
42
|
+
return dir ? join(dir, buildSliceFileName(sid, "UAT-RESULT")) : null;
|
|
43
|
+
}
|
|
44
|
+
case "execute-task": {
|
|
45
|
+
const tid = parts[2];
|
|
46
|
+
const dir = resolveSlicePath(base, mid, sid);
|
|
47
|
+
return dir && tid
|
|
48
|
+
? join(dir, "tasks", buildTaskFileName(tid, "SUMMARY"))
|
|
49
|
+
: null;
|
|
50
|
+
}
|
|
51
|
+
case "complete-slice": {
|
|
52
|
+
const dir = resolveSlicePath(base, mid, sid);
|
|
53
|
+
return dir ? join(dir, buildSliceFileName(sid, "SUMMARY")) : null;
|
|
54
|
+
}
|
|
55
|
+
case "validate-milestone": {
|
|
56
|
+
const dir = resolveMilestonePath(base, mid);
|
|
57
|
+
return dir ? join(dir, buildMilestoneFileName(mid, "VALIDATION")) : null;
|
|
58
|
+
}
|
|
59
|
+
case "complete-milestone": {
|
|
60
|
+
const dir = resolveMilestonePath(base, mid);
|
|
61
|
+
return dir ? join(dir, buildMilestoneFileName(mid, "SUMMARY")) : null;
|
|
62
|
+
}
|
|
63
|
+
case "replan-slice": {
|
|
64
|
+
const dir = resolveSlicePath(base, mid, sid);
|
|
65
|
+
return dir ? join(dir, buildSliceFileName(sid, "REPLAN")) : null;
|
|
66
|
+
}
|
|
67
|
+
case "rewrite-docs":
|
|
68
|
+
return null;
|
|
69
|
+
case "reactive-execute":
|
|
70
|
+
// Reactive execute produces multiple task summaries — verified separately
|
|
71
|
+
return null;
|
|
72
|
+
default:
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function diagnoseExpectedArtifact(unitType, unitId, base) {
|
|
77
|
+
const parts = unitId.split("/");
|
|
78
|
+
const mid = parts[0];
|
|
79
|
+
const sid = parts[1];
|
|
80
|
+
switch (unitType) {
|
|
81
|
+
case "discuss-milestone":
|
|
82
|
+
return `${relMilestoneFile(base, mid, "CONTEXT")} (milestone context from discussion)`;
|
|
83
|
+
case "research-milestone":
|
|
84
|
+
return `${relMilestoneFile(base, mid, "RESEARCH")} (milestone research)`;
|
|
85
|
+
case "plan-milestone":
|
|
86
|
+
return `${relMilestoneFile(base, mid, "ROADMAP")} (milestone roadmap)`;
|
|
87
|
+
case "research-slice":
|
|
88
|
+
return `${relSliceFile(base, mid, sid, "RESEARCH")} (slice research)`;
|
|
89
|
+
case "plan-slice":
|
|
90
|
+
return `${relSliceFile(base, mid, sid, "PLAN")} (slice plan)`;
|
|
91
|
+
case "execute-task": {
|
|
92
|
+
const tid = parts[2];
|
|
93
|
+
return `Task ${tid} marked [x] in ${relSliceFile(base, mid, sid, "PLAN")} + summary written`;
|
|
94
|
+
}
|
|
95
|
+
case "complete-slice":
|
|
96
|
+
return `Slice ${sid} marked [x] in ${relMilestoneFile(base, mid, "ROADMAP")} + summary + UAT written`;
|
|
97
|
+
case "replan-slice":
|
|
98
|
+
return `${relSliceFile(base, mid, sid, "REPLAN")} + updated ${relSliceFile(base, mid, sid, "PLAN")}`;
|
|
99
|
+
case "rewrite-docs":
|
|
100
|
+
return "Active overrides resolved in .gsd/OVERRIDES.md + plan documents updated";
|
|
101
|
+
case "reassess-roadmap":
|
|
102
|
+
return `${relSliceFile(base, mid, sid, "ASSESSMENT")} (roadmap reassessment)`;
|
|
103
|
+
case "run-uat":
|
|
104
|
+
return `${relSliceFile(base, mid, sid, "UAT-RESULT")} (UAT result)`;
|
|
105
|
+
case "validate-milestone":
|
|
106
|
+
return `${relMilestoneFile(base, mid, "VALIDATION")} (milestone validation report)`;
|
|
107
|
+
case "complete-milestone":
|
|
108
|
+
return `${relMilestoneFile(base, mid, "SUMMARY")} (milestone summary)`;
|
|
109
|
+
default:
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -13,14 +13,12 @@
|
|
|
13
13
|
import { deriveState } from "./state.js";
|
|
14
14
|
import { loadFile, parseSummary, resolveAllOverrides } from "./files.js";
|
|
15
15
|
import { loadPrompt } from "./prompt-loader.js";
|
|
16
|
-
import { resolveSliceFile, resolveTaskFile, resolveMilestoneFile, resolveTasksDir, buildTaskFileName,
|
|
16
|
+
import { resolveSliceFile, resolveTaskFile, resolveMilestoneFile, resolveTasksDir, buildTaskFileName, } from "./paths.js";
|
|
17
17
|
import { invalidateAllCaches } from "./cache.js";
|
|
18
18
|
import { closeoutUnit } from "./auto-unit-closeout.js";
|
|
19
19
|
import { autoCommitCurrentBranch, } from "./worktree.js";
|
|
20
20
|
import { verifyExpectedArtifact, resolveExpectedArtifactPath, } from "./auto-recovery.js";
|
|
21
|
-
import {
|
|
22
|
-
import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
|
|
23
|
-
import { recordHealthSnapshot, checkHealEscalation } from "./doctor-proactive.js";
|
|
21
|
+
import { regenerateIfMissing } from "./workflow-projections.js";
|
|
24
22
|
import { syncStateToProjectRoot } from "./auto-worktree-sync.js";
|
|
25
23
|
import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js";
|
|
26
24
|
import { renderPlanCheckboxes } from "./markdown-renderer.js";
|
|
@@ -30,7 +28,6 @@ import { hasPendingCaptures, loadPendingCaptures } from "./captures.js";
|
|
|
30
28
|
import { debugLog } from "./debug-logger.js";
|
|
31
29
|
import { existsSync, unlinkSync } from "node:fs";
|
|
32
30
|
import { join } from "node:path";
|
|
33
|
-
import { atomicWriteSync } from "./atomic-write.js";
|
|
34
31
|
import { _resetHasChangesCache } from "./native-git-bridge.js";
|
|
35
32
|
/**
|
|
36
33
|
* Detect summary files written directly to disk without the LLM calling
|
|
@@ -137,8 +134,6 @@ export function detectRogueFileWrites(unitType, unitId, basePath) {
|
|
|
137
134
|
}
|
|
138
135
|
return rogues;
|
|
139
136
|
}
|
|
140
|
-
/** Throttle STATE.md rebuilds — at most once per 30 seconds */
|
|
141
|
-
const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
|
|
142
137
|
/**
|
|
143
138
|
* Pre-verification processing: parallel worker signal check, cache invalidation,
|
|
144
139
|
* auto-commit, doctor run, state rebuild, worktree sync, artifact verification.
|
|
@@ -232,74 +227,6 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
232
227
|
catch (e) {
|
|
233
228
|
debugLog("postUnit", { phase: "github-sync", error: String(e) });
|
|
234
229
|
}
|
|
235
|
-
// Doctor: fix mechanical bookkeeping (skipped for lightweight sidecars)
|
|
236
|
-
if (!opts?.skipDoctor)
|
|
237
|
-
try {
|
|
238
|
-
const scopeParts = s.currentUnit.id.split("/").slice(0, 2);
|
|
239
|
-
const doctorScope = scopeParts.join("/");
|
|
240
|
-
const sliceTerminalUnits = new Set(["complete-slice", "run-uat"]);
|
|
241
|
-
const effectiveFixLevel = sliceTerminalUnits.has(s.currentUnit.type) ? "all" : "task";
|
|
242
|
-
const report = await runGSDDoctor(s.basePath, { fix: true, scope: doctorScope, fixLevel: effectiveFixLevel });
|
|
243
|
-
// Human-readable fix notification with details
|
|
244
|
-
if (report.fixesApplied.length > 0) {
|
|
245
|
-
const fixSummary = report.fixesApplied.length <= 2
|
|
246
|
-
? report.fixesApplied.join("; ")
|
|
247
|
-
: `${report.fixesApplied[0]}; +${report.fixesApplied.length - 1} more`;
|
|
248
|
-
ctx.ui.notify(`Doctor: ${fixSummary}`, "info");
|
|
249
|
-
}
|
|
250
|
-
// Proactive health tracking — filter to current milestone to avoid
|
|
251
|
-
// cross-milestone stale errors inflating the escalation counter
|
|
252
|
-
const currentMilestoneId = s.currentUnit.id.split("/")[0];
|
|
253
|
-
const milestoneIssues = currentMilestoneId
|
|
254
|
-
? report.issues.filter(i => i.unitId === currentMilestoneId ||
|
|
255
|
-
i.unitId.startsWith(`${currentMilestoneId}/`))
|
|
256
|
-
: report.issues;
|
|
257
|
-
const summary = summarizeDoctorIssues(milestoneIssues);
|
|
258
|
-
// Pass issue details + scope for real-time visibility in the progress widget
|
|
259
|
-
const issueDetails = milestoneIssues
|
|
260
|
-
.filter(i => i.severity === "error" || i.severity === "warning")
|
|
261
|
-
.map(i => ({ code: i.code, message: i.message, severity: i.severity, unitId: i.unitId }));
|
|
262
|
-
recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length, issueDetails, report.fixesApplied, doctorScope);
|
|
263
|
-
// Check if we should escalate to LLM-assisted heal
|
|
264
|
-
if (summary.errors > 0) {
|
|
265
|
-
const unresolvedErrors = milestoneIssues
|
|
266
|
-
.filter(i => i.severity === "error" && !i.fixable)
|
|
267
|
-
.map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
|
|
268
|
-
const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
|
|
269
|
-
if (escalation.shouldEscalate) {
|
|
270
|
-
ctx.ui.notify(`Doctor heal escalation: ${escalation.reason}. Dispatching LLM-assisted heal.`, "warning");
|
|
271
|
-
try {
|
|
272
|
-
const { formatDoctorIssuesForPrompt, formatDoctorReport } = await import("./doctor.js");
|
|
273
|
-
const { dispatchDoctorHeal } = await import("./commands-handlers.js");
|
|
274
|
-
const actionable = report.issues.filter(i => i.severity === "error");
|
|
275
|
-
const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
|
|
276
|
-
const structuredIssues = formatDoctorIssuesForPrompt(actionable);
|
|
277
|
-
dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
|
|
278
|
-
return "dispatched";
|
|
279
|
-
}
|
|
280
|
-
catch (e) {
|
|
281
|
-
debugLog("postUnit", { phase: "doctor-heal-dispatch", error: String(e) });
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
catch (e) {
|
|
287
|
-
debugLog("postUnit", { phase: "doctor", error: String(e) });
|
|
288
|
-
}
|
|
289
|
-
// Throttled STATE.md rebuild (skipped for lightweight sidecars)
|
|
290
|
-
if (!opts?.skipStateRebuild) {
|
|
291
|
-
const now = Date.now();
|
|
292
|
-
if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
|
|
293
|
-
try {
|
|
294
|
-
await rebuildState(s.basePath);
|
|
295
|
-
s.lastStateRebuildAt = now;
|
|
296
|
-
autoCommitCurrentBranch(s.basePath, "state-rebuild", s.currentUnit.id);
|
|
297
|
-
}
|
|
298
|
-
catch (e) {
|
|
299
|
-
debugLog("postUnit", { phase: "state-rebuild", error: String(e) });
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
230
|
// Prune dead bg-shell processes
|
|
304
231
|
try {
|
|
305
232
|
const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js");
|
|
@@ -411,6 +338,27 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
411
338
|
catch (e) {
|
|
412
339
|
debugLog("postUnit", { phase: "artifact-verify", error: String(e) });
|
|
413
340
|
}
|
|
341
|
+
// If verification failed, attempt to regenerate missing projection files
|
|
342
|
+
// from DB data before giving up (e.g. research-slice produces PLAN from engine).
|
|
343
|
+
if (!triggerArtifactVerified) {
|
|
344
|
+
try {
|
|
345
|
+
const parts = s.currentUnit.id.split("/");
|
|
346
|
+
const [mid, sid] = parts;
|
|
347
|
+
if (mid && sid) {
|
|
348
|
+
const regenerated = regenerateIfMissing(s.basePath, mid, sid, "PLAN");
|
|
349
|
+
if (regenerated) {
|
|
350
|
+
// Re-check after regeneration
|
|
351
|
+
triggerArtifactVerified = verifyExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.basePath);
|
|
352
|
+
if (triggerArtifactVerified) {
|
|
353
|
+
invalidateAllCaches();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
catch (e) {
|
|
359
|
+
debugLog("postUnit", { phase: "regenerate-projection", error: String(e) });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
414
362
|
// When artifact verification fails for a unit type that has a known expected
|
|
415
363
|
// artifact, return "retry" so the caller re-dispatches with failure context
|
|
416
364
|
// instead of blindly re-dispatching the same unit (#1571).
|
|
@@ -432,18 +380,7 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
432
380
|
}
|
|
433
381
|
}
|
|
434
382
|
else {
|
|
435
|
-
// Hook unit completed —
|
|
436
|
-
try {
|
|
437
|
-
writeUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, {
|
|
438
|
-
phase: "finalized",
|
|
439
|
-
progressCount: 1,
|
|
440
|
-
lastProgressKind: "hook-completed",
|
|
441
|
-
});
|
|
442
|
-
clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
|
|
443
|
-
}
|
|
444
|
-
catch (e) {
|
|
445
|
-
debugLog("postUnit", { phase: "hook-finalize", error: String(e) });
|
|
446
|
-
}
|
|
383
|
+
// Hook unit completed — no additional processing needed
|
|
447
384
|
}
|
|
448
385
|
}
|
|
449
386
|
return "continue";
|
|
@@ -517,15 +454,7 @@ export async function postUnitPostVerification(pctx) {
|
|
|
517
454
|
}
|
|
518
455
|
}
|
|
519
456
|
}
|
|
520
|
-
// 3.
|
|
521
|
-
s.completedUnits = s.completedUnits.filter(u => !(u.type === trigger.unitType && u.id === trigger.unitId));
|
|
522
|
-
try {
|
|
523
|
-
const completedKeysPath = join(gsdRoot(s.basePath), "completed-units.json");
|
|
524
|
-
const keys = s.completedUnits.map(u => `${u.type}/${u.id}`);
|
|
525
|
-
atomicWriteSync(completedKeysPath, JSON.stringify(keys, null, 2));
|
|
526
|
-
}
|
|
527
|
-
catch { /* non-fatal: disk flush failure */ }
|
|
528
|
-
// 4. Delete the retry_on artifact (e.g. NEEDS-REWORK.md)
|
|
457
|
+
// 3. Delete the retry_on artifact (e.g. NEEDS-REWORK.md)
|
|
529
458
|
if (trigger.retryArtifact) {
|
|
530
459
|
const retryArtifactPath = resolveHookArtifactPath(s.basePath, trigger.unitId, trigger.retryArtifact);
|
|
531
460
|
if (existsSync(retryArtifactPath)) {
|
|
@@ -352,7 +352,6 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
352
352
|
});
|
|
353
353
|
s.autoStartTime = Date.now();
|
|
354
354
|
s.resourceVersionOnStart = readResourceVersion();
|
|
355
|
-
s.completedUnits = [];
|
|
356
355
|
s.pendingQuickTasks = [];
|
|
357
356
|
s.currentUnit = null;
|
|
358
357
|
s.currentMilestoneId = state.activeMilestone?.id ?? null;
|
|
@@ -455,8 +454,8 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
455
454
|
? `Will loop through ${pendingCount} milestones.`
|
|
456
455
|
: "Will loop until milestone complete.";
|
|
457
456
|
ctx.ui.notify(`${modeLabel} started. ${scopeMsg}`, "info");
|
|
458
|
-
updateSessionLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown"
|
|
459
|
-
writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown"
|
|
457
|
+
updateSessionLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown");
|
|
458
|
+
writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown");
|
|
460
459
|
// Secrets collection gate
|
|
461
460
|
const mid = state.activeMilestone.id;
|
|
462
461
|
try {
|
|
@@ -19,13 +19,11 @@ import { clearActivityLogState } from "./activity-log.js";
|
|
|
19
19
|
import { synthesizeCrashRecovery, getDeepDiagnostic, } from "./session-forensics.js";
|
|
20
20
|
import { writeLock, clearLock, readCrashLock, isLockProcessAlive, } from "./crash-recovery.js";
|
|
21
21
|
import { acquireSessionLock, getSessionLockStatus, releaseSessionLock, updateSessionLock, } from "./session-lock.js";
|
|
22
|
-
import { clearUnitRuntimeRecord, readUnitRuntimeRecord, writeUnitRuntimeRecord, } from "./unit-runtime.js";
|
|
23
22
|
import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences, getIsolationMode, } from "./preferences.js";
|
|
24
23
|
import { sendDesktopNotification } from "./notifications.js";
|
|
25
24
|
import { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction, } from "./auto-budget.js";
|
|
26
25
|
import { markToolStart as _markToolStart, markToolEnd as _markToolEnd, getOldestInFlightToolAgeMs as _getOldestInFlightToolAgeMs, clearInFlightTools, } from "./auto-tool-tracking.js";
|
|
27
26
|
import { closeoutUnit } from "./auto-unit-closeout.js";
|
|
28
|
-
import { selfHealRuntimeRecords } from "./auto-recovery.js";
|
|
29
27
|
import { selectAndApplyModel, resolveModelId } from "./auto-model-selection.js";
|
|
30
28
|
import { syncProjectRootToWorktree, checkResourcesStale, escapeStaleWorktree, } from "./auto-worktree-sync.js";
|
|
31
29
|
import { resetRoutingHistory, recordOutcome } from "./routing-history.js";
|
|
@@ -44,7 +42,7 @@ import { getPriorSliceCompletionBlocker } from "./dispatch-guard.js";
|
|
|
44
42
|
import { createAutoWorktree, enterAutoWorktree, teardownAutoWorktree, isInAutoWorktree, getAutoWorktreePath, mergeMilestoneToMain, autoWorktreeBranch, syncWorktreeStateBack, } from "./auto-worktree.js";
|
|
45
43
|
import { pruneQueueOrder } from "./queue-order.js";
|
|
46
44
|
import { debugLog, isDebugEnabled, writeDebugSummary } from "./debug-logger.js";
|
|
47
|
-
import {
|
|
45
|
+
import { reconcileMergeState, } from "./auto-recovery.js";
|
|
48
46
|
import { resolveDispatch, DISPATCH_RULES } from "./auto-dispatch.js";
|
|
49
47
|
import { initRegistry, convertDispatchRules } from "./rule-registry.js";
|
|
50
48
|
import { emitJournalEvent as _emitJournalEvent } from "./journal.js";
|
|
@@ -145,7 +143,6 @@ export function getAutoDashboardData() {
|
|
|
145
143
|
? (s.autoStartTime > 0 ? Date.now() - s.autoStartTime : 0)
|
|
146
144
|
: 0,
|
|
147
145
|
currentUnit: s.currentUnit ? { ...s.currentUnit } : null,
|
|
148
|
-
completedUnits: [...s.completedUnits],
|
|
149
146
|
basePath: s.basePath,
|
|
150
147
|
totalCost: totals?.cost ?? 0,
|
|
151
148
|
totalTokens: totals?.tokens.total ?? 0,
|
|
@@ -244,7 +241,6 @@ export function checkRemoteAutoSession(projectRoot) {
|
|
|
244
241
|
unitType: lock.unitType,
|
|
245
242
|
unitId: lock.unitId,
|
|
246
243
|
startedAt: lock.startedAt,
|
|
247
|
-
completedUnits: lock.completedUnits,
|
|
248
244
|
};
|
|
249
245
|
}
|
|
250
246
|
export function isStepMode() {
|
|
@@ -269,16 +265,12 @@ function clearUnitTimeout() {
|
|
|
269
265
|
}
|
|
270
266
|
clearInFlightTools();
|
|
271
267
|
}
|
|
272
|
-
/** Build snapshot metric opts
|
|
273
|
-
function buildSnapshotOpts(
|
|
274
|
-
const runtime = s.currentUnit
|
|
275
|
-
? readUnitRuntimeRecord(s.basePath, unitType, unitId)
|
|
276
|
-
: null;
|
|
268
|
+
/** Build snapshot metric opts. */
|
|
269
|
+
function buildSnapshotOpts(_unitType, _unitId) {
|
|
277
270
|
return {
|
|
278
271
|
promptCharCount: s.lastPromptCharCount,
|
|
279
272
|
baselineCharCount: s.lastBaselineCharCount,
|
|
280
273
|
...(s.currentUnitRouting ?? {}),
|
|
281
|
-
...(runtime?.continueHereFired ? { continueHereFired: true } : {}),
|
|
282
274
|
};
|
|
283
275
|
}
|
|
284
276
|
function handleLostSessionLock(ctx, lockStatus) {
|
|
@@ -586,12 +578,6 @@ export async function pauseAuto(ctx, _pi) {
|
|
|
586
578
|
catch {
|
|
587
579
|
// Non-fatal — best-effort closeout on pause
|
|
588
580
|
}
|
|
589
|
-
try {
|
|
590
|
-
clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
|
|
591
|
-
}
|
|
592
|
-
catch {
|
|
593
|
-
// Non-fatal
|
|
594
|
-
}
|
|
595
581
|
s.currentUnit = null;
|
|
596
582
|
}
|
|
597
583
|
if (lockBase()) {
|
|
@@ -710,9 +696,6 @@ function buildLoopDeps() {
|
|
|
710
696
|
getMainBranch,
|
|
711
697
|
// Unit closeout + runtime records
|
|
712
698
|
closeoutUnit,
|
|
713
|
-
verifyExpectedArtifact,
|
|
714
|
-
clearUnitRuntimeRecord,
|
|
715
|
-
writeUnitRuntimeRecord,
|
|
716
699
|
recordOutcome,
|
|
717
700
|
writeLock,
|
|
718
701
|
captureAvailableSkills,
|
|
@@ -861,15 +844,6 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
861
844
|
});
|
|
862
845
|
}
|
|
863
846
|
invalidateAllCaches();
|
|
864
|
-
// Clean stale runtime records left from the paused session
|
|
865
|
-
try {
|
|
866
|
-
await selfHealRuntimeRecords(s.basePath, ctx);
|
|
867
|
-
}
|
|
868
|
-
catch (e) {
|
|
869
|
-
debugLog("resume-self-heal-runtime-failed", {
|
|
870
|
-
error: e instanceof Error ? e.message : String(e),
|
|
871
|
-
});
|
|
872
|
-
}
|
|
873
847
|
if (s.pausedSessionFile) {
|
|
874
848
|
const activityDir = join(gsdRoot(s.basePath), "activity");
|
|
875
849
|
const recovery = synthesizeCrashRecovery(s.basePath, s.currentUnit?.type ?? "unknown", s.currentUnit?.id ?? "unknown", s.pausedSessionFile ?? undefined, activityDir);
|
|
@@ -879,11 +853,9 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
879
853
|
}
|
|
880
854
|
s.pausedSessionFile = null;
|
|
881
855
|
}
|
|
882
|
-
updateSessionLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown"
|
|
883
|
-
writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown"
|
|
856
|
+
updateSessionLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown");
|
|
857
|
+
writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown");
|
|
884
858
|
logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
|
|
885
|
-
// Clear orphaned runtime records from prior process deaths before entering the loop
|
|
886
|
-
await selfHealRuntimeRecords(s.basePath, ctx);
|
|
887
859
|
await autoLoop(ctx, pi, s, buildLoopDeps());
|
|
888
860
|
cleanupAfterLoopExit(ctx);
|
|
889
861
|
return;
|
|
@@ -905,8 +877,6 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
905
877
|
// Best-effort only — sidebar sync must never block auto-mode startup
|
|
906
878
|
}
|
|
907
879
|
logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
|
|
908
|
-
// Clear orphaned runtime records from prior process deaths before entering the loop
|
|
909
|
-
await selfHealRuntimeRecords(s.basePath, ctx);
|
|
910
880
|
// Dispatch the first unit
|
|
911
881
|
await autoLoop(ctx, pi, s, buildLoopDeps());
|
|
912
882
|
cleanupAfterLoopExit(ctx);
|
|
@@ -1005,7 +975,6 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
|
|
|
1005
975
|
s.basePath = targetBasePath;
|
|
1006
976
|
s.autoStartTime = Date.now();
|
|
1007
977
|
s.currentUnit = null;
|
|
1008
|
-
s.completedUnits = [];
|
|
1009
978
|
s.pendingQuickTasks = [];
|
|
1010
979
|
}
|
|
1011
980
|
const hookUnitType = `hook/${hookName}`;
|
|
@@ -1025,14 +994,6 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
|
|
|
1025
994
|
id: triggerUnitId,
|
|
1026
995
|
startedAt: hookStartedAt,
|
|
1027
996
|
};
|
|
1028
|
-
writeUnitRuntimeRecord(s.basePath, hookUnitType, triggerUnitId, hookStartedAt, {
|
|
1029
|
-
phase: "dispatched",
|
|
1030
|
-
wrapupWarningSent: false,
|
|
1031
|
-
timeoutAt: null,
|
|
1032
|
-
lastProgressAt: hookStartedAt,
|
|
1033
|
-
progressCount: 0,
|
|
1034
|
-
lastProgressKind: "dispatch",
|
|
1035
|
-
});
|
|
1036
997
|
if (hookModel) {
|
|
1037
998
|
const availableModels = ctx.modelRegistry.getAvailable();
|
|
1038
999
|
const match = resolveModelId(hookModel, availableModels, ctx.model?.provider);
|
|
@@ -1050,7 +1011,7 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
|
|
|
1050
1011
|
}
|
|
1051
1012
|
}
|
|
1052
1013
|
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
1053
|
-
writeLock(lockBase(), hookUnitType, triggerUnitId,
|
|
1014
|
+
writeLock(lockBase(), hookUnitType, triggerUnitId, sessionFile);
|
|
1054
1015
|
clearUnitTimeout();
|
|
1055
1016
|
const supervisor = resolveAutoSupervisorConfig();
|
|
1056
1017
|
const hookHardTimeoutMs = (supervisor.hard_timeout_minutes ?? 30) * 60 * 1000;
|
|
@@ -1058,12 +1019,6 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
|
|
|
1058
1019
|
s.unitTimeoutHandle = null;
|
|
1059
1020
|
if (!s.active)
|
|
1060
1021
|
return;
|
|
1061
|
-
if (s.currentUnit) {
|
|
1062
|
-
writeUnitRuntimeRecord(s.basePath, hookUnitType, triggerUnitId, hookStartedAt, {
|
|
1063
|
-
phase: "timeout",
|
|
1064
|
-
timeoutAt: Date.now(),
|
|
1065
|
-
});
|
|
1066
|
-
}
|
|
1067
1022
|
ctx.ui.notify(`Hook ${hookName} exceeded ${supervisor.hard_timeout_minutes ?? 30}min timeout. Pausing auto-mode.`, "warning");
|
|
1068
1023
|
resetHookState();
|
|
1069
1024
|
await pauseAuto(ctx, pi);
|
|
@@ -1086,4 +1041,5 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
|
|
|
1086
1041
|
// Direct phase dispatch → auto-direct-dispatch.ts
|
|
1087
1042
|
export { dispatchDirectPhase } from "./auto-direct-dispatch.js";
|
|
1088
1043
|
// Re-export recovery functions for external consumers
|
|
1089
|
-
export {
|
|
1044
|
+
export { buildLoopRemediationSteps, } from "./auto-recovery.js";
|
|
1045
|
+
export { resolveExpectedArtifactPath } from "./auto-artifact-paths.js";
|
|
@@ -4,6 +4,7 @@ import { buildMilestoneFileName, resolveMilestonePath, resolveSliceFile, resolve
|
|
|
4
4
|
import { buildBeforeAgentStartResult } from "./system-context.js";
|
|
5
5
|
import { handleAgentEnd } from "./agent-end-recovery.js";
|
|
6
6
|
import { isDepthVerified, isQueuePhaseActive, markDepthVerified, resetWriteGateState, shouldBlockContextWrite } from "./write-gate.js";
|
|
7
|
+
import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
|
|
7
8
|
import { getDiscussionMilestoneId } from "../guided-flow.js";
|
|
8
9
|
import { loadToolApiKeys } from "../commands-config.js";
|
|
9
10
|
import { loadFile, saveFile, formatContinue } from "../files.js";
|
|
@@ -128,6 +129,23 @@ export function registerHooks(pi) {
|
|
|
128
129
|
if (loopCheck.block) {
|
|
129
130
|
return { block: true, reason: loopCheck.reason };
|
|
130
131
|
}
|
|
132
|
+
// ── Single-writer engine: block direct writes to STATE.md ──────────
|
|
133
|
+
// Covers write, edit, and bash tools to prevent bypass vectors.
|
|
134
|
+
if (isToolCallEventType("write", event)) {
|
|
135
|
+
if (isBlockedStateFile(event.input.path)) {
|
|
136
|
+
return { block: true, reason: BLOCKED_WRITE_ERROR };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (isToolCallEventType("edit", event)) {
|
|
140
|
+
if (isBlockedStateFile(event.input.path)) {
|
|
141
|
+
return { block: true, reason: BLOCKED_WRITE_ERROR };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (isToolCallEventType("bash", event)) {
|
|
145
|
+
if (isBashWriteToStateFile(event.input.command)) {
|
|
146
|
+
return { block: true, reason: BLOCKED_WRITE_ERROR };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
131
149
|
if (!isToolCallEventType("write", event))
|
|
132
150
|
return;
|
|
133
151
|
const result = shouldBlockContextWrite(event.toolName, event.input.path, getDiscussionMilestoneId(), isDepthVerified(), isQueuePhaseActive());
|