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
|
@@ -17,12 +17,10 @@ import { loadFile, parseSummary, resolveAllOverrides } from "./files.js";
|
|
|
17
17
|
import { loadPrompt } from "./prompt-loader.js";
|
|
18
18
|
import {
|
|
19
19
|
resolveSliceFile,
|
|
20
|
-
resolveSlicePath,
|
|
21
20
|
resolveTaskFile,
|
|
22
21
|
resolveMilestoneFile,
|
|
23
22
|
resolveTasksDir,
|
|
24
23
|
buildTaskFileName,
|
|
25
|
-
gsdRoot,
|
|
26
24
|
} from "./paths.js";
|
|
27
25
|
import { invalidateAllCaches } from "./cache.js";
|
|
28
26
|
import { closeoutUnit, type CloseoutOptions } from "./auto-unit-closeout.js";
|
|
@@ -34,9 +32,7 @@ import {
|
|
|
34
32
|
verifyExpectedArtifact,
|
|
35
33
|
resolveExpectedArtifactPath,
|
|
36
34
|
} from "./auto-recovery.js";
|
|
37
|
-
import {
|
|
38
|
-
import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
|
|
39
|
-
import { recordHealthSnapshot, checkHealEscalation } from "./doctor-proactive.js";
|
|
35
|
+
import { regenerateIfMissing } from "./workflow-projections.js";
|
|
40
36
|
import { syncStateToProjectRoot } from "./auto-worktree-sync.js";
|
|
41
37
|
import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js";
|
|
42
38
|
import { renderPlanCheckboxes } from "./markdown-renderer.js";
|
|
@@ -57,9 +53,8 @@ import {
|
|
|
57
53
|
unitVerb,
|
|
58
54
|
hideFooter,
|
|
59
55
|
} from "./auto-dashboard.js";
|
|
60
|
-
import { existsSync, unlinkSync
|
|
56
|
+
import { existsSync, unlinkSync } from "node:fs";
|
|
61
57
|
import { join } from "node:path";
|
|
62
|
-
import { atomicWriteSync } from "./atomic-write.js";
|
|
63
58
|
import { _resetHasChangesCache } from "./native-git-bridge.js";
|
|
64
59
|
|
|
65
60
|
// ─── Rogue File Detection ──────────────────────────────────────────────────
|
|
@@ -186,13 +181,8 @@ export function detectRogueFileWrites(
|
|
|
186
181
|
return rogues;
|
|
187
182
|
}
|
|
188
183
|
|
|
189
|
-
/** Throttle STATE.md rebuilds — at most once per 30 seconds */
|
|
190
|
-
const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
|
|
191
|
-
|
|
192
184
|
export interface PreVerificationOpts {
|
|
193
185
|
skipSettleDelay?: boolean;
|
|
194
|
-
skipDoctor?: boolean;
|
|
195
|
-
skipStateRebuild?: boolean;
|
|
196
186
|
skipWorktreeSync?: boolean;
|
|
197
187
|
}
|
|
198
188
|
|
|
@@ -306,78 +296,6 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
|
|
|
306
296
|
debugLog("postUnit", { phase: "github-sync", error: String(e) });
|
|
307
297
|
}
|
|
308
298
|
|
|
309
|
-
// Doctor: fix mechanical bookkeeping (skipped for lightweight sidecars)
|
|
310
|
-
if (!opts?.skipDoctor) try {
|
|
311
|
-
const scopeParts = s.currentUnit.id.split("/").slice(0, 2);
|
|
312
|
-
const doctorScope = scopeParts.join("/");
|
|
313
|
-
const sliceTerminalUnits = new Set(["complete-slice", "run-uat"]);
|
|
314
|
-
const effectiveFixLevel = sliceTerminalUnits.has(s.currentUnit.type) ? "all" as const : "task" as const;
|
|
315
|
-
const report = await runGSDDoctor(s.basePath, { fix: true, scope: doctorScope, fixLevel: effectiveFixLevel });
|
|
316
|
-
// Human-readable fix notification with details
|
|
317
|
-
if (report.fixesApplied.length > 0) {
|
|
318
|
-
const fixSummary = report.fixesApplied.length <= 2
|
|
319
|
-
? report.fixesApplied.join("; ")
|
|
320
|
-
: `${report.fixesApplied[0]}; +${report.fixesApplied.length - 1} more`;
|
|
321
|
-
ctx.ui.notify(`Doctor: ${fixSummary}`, "info");
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Proactive health tracking — filter to current milestone to avoid
|
|
325
|
-
// cross-milestone stale errors inflating the escalation counter
|
|
326
|
-
const currentMilestoneId = s.currentUnit.id.split("/")[0];
|
|
327
|
-
const milestoneIssues = currentMilestoneId
|
|
328
|
-
? report.issues.filter(i =>
|
|
329
|
-
i.unitId === currentMilestoneId ||
|
|
330
|
-
i.unitId.startsWith(`${currentMilestoneId}/`))
|
|
331
|
-
: report.issues;
|
|
332
|
-
const summary = summarizeDoctorIssues(milestoneIssues);
|
|
333
|
-
// Pass issue details + scope for real-time visibility in the progress widget
|
|
334
|
-
const issueDetails = milestoneIssues
|
|
335
|
-
.filter(i => i.severity === "error" || i.severity === "warning")
|
|
336
|
-
.map(i => ({ code: i.code, message: i.message, severity: i.severity, unitId: i.unitId }));
|
|
337
|
-
recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length, issueDetails, report.fixesApplied, doctorScope);
|
|
338
|
-
|
|
339
|
-
// Check if we should escalate to LLM-assisted heal
|
|
340
|
-
if (summary.errors > 0) {
|
|
341
|
-
const unresolvedErrors = milestoneIssues
|
|
342
|
-
.filter(i => i.severity === "error" && !i.fixable)
|
|
343
|
-
.map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
|
|
344
|
-
const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
|
|
345
|
-
if (escalation.shouldEscalate) {
|
|
346
|
-
ctx.ui.notify(
|
|
347
|
-
`Doctor heal escalation: ${escalation.reason}. Dispatching LLM-assisted heal.`,
|
|
348
|
-
"warning",
|
|
349
|
-
);
|
|
350
|
-
try {
|
|
351
|
-
const { formatDoctorIssuesForPrompt, formatDoctorReport } = await import("./doctor.js");
|
|
352
|
-
const { dispatchDoctorHeal } = await import("./commands-handlers.js");
|
|
353
|
-
const actionable = report.issues.filter(i => i.severity === "error");
|
|
354
|
-
const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
|
|
355
|
-
const structuredIssues = formatDoctorIssuesForPrompt(actionable);
|
|
356
|
-
dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
|
|
357
|
-
return "dispatched";
|
|
358
|
-
} catch (e) {
|
|
359
|
-
debugLog("postUnit", { phase: "doctor-heal-dispatch", error: String(e) });
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
} catch (e) {
|
|
364
|
-
debugLog("postUnit", { phase: "doctor", error: String(e) });
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Throttled STATE.md rebuild (skipped for lightweight sidecars)
|
|
368
|
-
if (!opts?.skipStateRebuild) {
|
|
369
|
-
const now = Date.now();
|
|
370
|
-
if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
|
|
371
|
-
try {
|
|
372
|
-
await rebuildState(s.basePath);
|
|
373
|
-
s.lastStateRebuildAt = now;
|
|
374
|
-
autoCommitCurrentBranch(s.basePath, "state-rebuild", s.currentUnit.id);
|
|
375
|
-
} catch (e) {
|
|
376
|
-
debugLog("postUnit", { phase: "state-rebuild", error: String(e) });
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
299
|
// Prune dead bg-shell processes
|
|
382
300
|
try {
|
|
383
301
|
const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js");
|
|
@@ -503,6 +421,27 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
|
|
|
503
421
|
debugLog("postUnit", { phase: "artifact-verify", error: String(e) });
|
|
504
422
|
}
|
|
505
423
|
|
|
424
|
+
// If verification failed, attempt to regenerate missing projection files
|
|
425
|
+
// from DB data before giving up (e.g. research-slice produces PLAN from engine).
|
|
426
|
+
if (!triggerArtifactVerified) {
|
|
427
|
+
try {
|
|
428
|
+
const parts = s.currentUnit.id.split("/");
|
|
429
|
+
const [mid, sid] = parts;
|
|
430
|
+
if (mid && sid) {
|
|
431
|
+
const regenerated = regenerateIfMissing(s.basePath, mid, sid, "PLAN");
|
|
432
|
+
if (regenerated) {
|
|
433
|
+
// Re-check after regeneration
|
|
434
|
+
triggerArtifactVerified = verifyExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.basePath);
|
|
435
|
+
if (triggerArtifactVerified) {
|
|
436
|
+
invalidateAllCaches();
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
} catch (e) {
|
|
441
|
+
debugLog("postUnit", { phase: "regenerate-projection", error: String(e) });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
506
445
|
// When artifact verification fails for a unit type that has a known expected
|
|
507
446
|
// artifact, return "retry" so the caller re-dispatches with failure context
|
|
508
447
|
// instead of blindly re-dispatching the same unit (#1571).
|
|
@@ -526,17 +465,7 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
|
|
|
526
465
|
}
|
|
527
466
|
}
|
|
528
467
|
} else {
|
|
529
|
-
// Hook unit completed —
|
|
530
|
-
try {
|
|
531
|
-
writeUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, {
|
|
532
|
-
phase: "finalized",
|
|
533
|
-
progressCount: 1,
|
|
534
|
-
lastProgressKind: "hook-completed",
|
|
535
|
-
});
|
|
536
|
-
clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
|
|
537
|
-
} catch (e) {
|
|
538
|
-
debugLog("postUnit", { phase: "hook-finalize", error: String(e) });
|
|
539
|
-
}
|
|
468
|
+
// Hook unit completed — no additional processing needed
|
|
540
469
|
}
|
|
541
470
|
}
|
|
542
471
|
|
|
@@ -625,17 +554,7 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
625
554
|
}
|
|
626
555
|
}
|
|
627
556
|
|
|
628
|
-
// 3.
|
|
629
|
-
s.completedUnits = s.completedUnits.filter(
|
|
630
|
-
u => !(u.type === trigger.unitType && u.id === trigger.unitId),
|
|
631
|
-
);
|
|
632
|
-
try {
|
|
633
|
-
const completedKeysPath = join(gsdRoot(s.basePath), "completed-units.json");
|
|
634
|
-
const keys = s.completedUnits.map(u => `${u.type}/${u.id}`);
|
|
635
|
-
atomicWriteSync(completedKeysPath, JSON.stringify(keys, null, 2));
|
|
636
|
-
} catch { /* non-fatal: disk flush failure */ }
|
|
637
|
-
|
|
638
|
-
// 4. Delete the retry_on artifact (e.g. NEEDS-REWORK.md)
|
|
557
|
+
// 3. Delete the retry_on artifact (e.g. NEEDS-REWORK.md)
|
|
639
558
|
if (trigger.retryArtifact) {
|
|
640
559
|
const retryArtifactPath = resolveHookArtifactPath(s.basePath, trigger.unitId, trigger.retryArtifact);
|
|
641
560
|
if (existsSync(retryArtifactPath)) {
|
|
@@ -494,7 +494,6 @@ export async function bootstrapAutoSession(
|
|
|
494
494
|
});
|
|
495
495
|
s.autoStartTime = Date.now();
|
|
496
496
|
s.resourceVersionOnStart = readResourceVersion();
|
|
497
|
-
s.completedUnits = [];
|
|
498
497
|
s.pendingQuickTasks = [];
|
|
499
498
|
s.currentUnit = null;
|
|
500
499
|
s.currentMilestoneId = state.activeMilestone?.id ?? null;
|
|
@@ -624,9 +623,8 @@ export async function bootstrapAutoSession(
|
|
|
624
623
|
lockBase(),
|
|
625
624
|
"starting",
|
|
626
625
|
s.currentMilestoneId ?? "unknown",
|
|
627
|
-
0,
|
|
628
626
|
);
|
|
629
|
-
writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown"
|
|
627
|
+
writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown");
|
|
630
628
|
|
|
631
629
|
// Secrets collection gate
|
|
632
630
|
const mid = state.activeMilestone!.id;
|
|
@@ -52,12 +52,6 @@ import {
|
|
|
52
52
|
updateSessionLock,
|
|
53
53
|
} from "./session-lock.js";
|
|
54
54
|
import type { SessionLockStatus } from "./session-lock.js";
|
|
55
|
-
import {
|
|
56
|
-
clearUnitRuntimeRecord,
|
|
57
|
-
inspectExecuteTaskDurability,
|
|
58
|
-
readUnitRuntimeRecord,
|
|
59
|
-
writeUnitRuntimeRecord,
|
|
60
|
-
} from "./unit-runtime.js";
|
|
61
55
|
import {
|
|
62
56
|
resolveAutoSupervisorConfig,
|
|
63
57
|
loadEffectiveGSDPreferences,
|
|
@@ -81,7 +75,6 @@ import {
|
|
|
81
75
|
} from "./auto-tool-tracking.js";
|
|
82
76
|
import { closeoutUnit } from "./auto-unit-closeout.js";
|
|
83
77
|
import { recoverTimedOutUnit } from "./auto-timeout-recovery.js";
|
|
84
|
-
import { selfHealRuntimeRecords } from "./auto-recovery.js";
|
|
85
78
|
import { selectAndApplyModel, resolveModelId } from "./auto-model-selection.js";
|
|
86
79
|
import {
|
|
87
80
|
syncProjectRootToWorktree,
|
|
@@ -155,10 +148,6 @@ import { pruneQueueOrder } from "./queue-order.js";
|
|
|
155
148
|
|
|
156
149
|
import { debugLog, isDebugEnabled, writeDebugSummary } from "./debug-logger.js";
|
|
157
150
|
import {
|
|
158
|
-
resolveExpectedArtifactPath,
|
|
159
|
-
verifyExpectedArtifact,
|
|
160
|
-
writeBlockerPlaceholder,
|
|
161
|
-
diagnoseExpectedArtifact,
|
|
162
151
|
buildLoopRemediationSteps,
|
|
163
152
|
reconcileMergeState,
|
|
164
153
|
} from "./auto-recovery.js";
|
|
@@ -213,7 +202,6 @@ import {
|
|
|
213
202
|
NEW_SESSION_TIMEOUT_MS,
|
|
214
203
|
} from "./auto/session.js";
|
|
215
204
|
import type {
|
|
216
|
-
CompletedUnit,
|
|
217
205
|
CurrentUnit,
|
|
218
206
|
UnitRouting,
|
|
219
207
|
StartModel,
|
|
@@ -225,7 +213,6 @@ export {
|
|
|
225
213
|
NEW_SESSION_TIMEOUT_MS,
|
|
226
214
|
} from "./auto/session.js";
|
|
227
215
|
export type {
|
|
228
|
-
CompletedUnit,
|
|
229
216
|
CurrentUnit,
|
|
230
217
|
UnitRouting,
|
|
231
218
|
StartModel,
|
|
@@ -335,7 +322,6 @@ export function getAutoDashboardData(): AutoDashboardData {
|
|
|
335
322
|
? (s.autoStartTime > 0 ? Date.now() - s.autoStartTime : 0)
|
|
336
323
|
: 0,
|
|
337
324
|
currentUnit: s.currentUnit ? { ...s.currentUnit } : null,
|
|
338
|
-
completedUnits: [...s.completedUnits],
|
|
339
325
|
basePath: s.basePath,
|
|
340
326
|
totalCost: totals?.cost ?? 0,
|
|
341
327
|
totalTokens: totals?.tokens.total ?? 0,
|
|
@@ -447,7 +433,6 @@ export function checkRemoteAutoSession(projectRoot: string): {
|
|
|
447
433
|
unitType?: string;
|
|
448
434
|
unitId?: string;
|
|
449
435
|
startedAt?: string;
|
|
450
|
-
completedUnits?: number;
|
|
451
436
|
} {
|
|
452
437
|
const lock = readCrashLock(projectRoot);
|
|
453
438
|
if (!lock) return { running: false };
|
|
@@ -463,7 +448,6 @@ export function checkRemoteAutoSession(projectRoot: string): {
|
|
|
463
448
|
unitType: lock.unitType,
|
|
464
449
|
unitId: lock.unitId,
|
|
465
450
|
startedAt: lock.startedAt,
|
|
466
|
-
completedUnits: lock.completedUnits,
|
|
467
451
|
};
|
|
468
452
|
}
|
|
469
453
|
|
|
@@ -491,23 +475,19 @@ function clearUnitTimeout(): void {
|
|
|
491
475
|
clearInFlightTools();
|
|
492
476
|
}
|
|
493
477
|
|
|
494
|
-
/** Build snapshot metric opts
|
|
478
|
+
/** Build snapshot metric opts. */
|
|
495
479
|
function buildSnapshotOpts(
|
|
496
|
-
|
|
497
|
-
|
|
480
|
+
_unitType: string,
|
|
481
|
+
_unitId: string,
|
|
498
482
|
): {
|
|
499
483
|
continueHereFired?: boolean;
|
|
500
484
|
promptCharCount?: number;
|
|
501
485
|
baselineCharCount?: number;
|
|
502
486
|
} & Record<string, unknown> {
|
|
503
|
-
const runtime = s.currentUnit
|
|
504
|
-
? readUnitRuntimeRecord(s.basePath, unitType, unitId)
|
|
505
|
-
: null;
|
|
506
487
|
return {
|
|
507
488
|
promptCharCount: s.lastPromptCharCount,
|
|
508
489
|
baselineCharCount: s.lastBaselineCharCount,
|
|
509
490
|
...(s.currentUnitRouting ?? {}),
|
|
510
|
-
...(runtime?.continueHereFired ? { continueHereFired: true } : {}),
|
|
511
491
|
};
|
|
512
492
|
}
|
|
513
493
|
|
|
@@ -848,11 +828,6 @@ export async function pauseAuto(
|
|
|
848
828
|
} catch {
|
|
849
829
|
// Non-fatal — best-effort closeout on pause
|
|
850
830
|
}
|
|
851
|
-
try {
|
|
852
|
-
clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
|
|
853
|
-
} catch {
|
|
854
|
-
// Non-fatal
|
|
855
|
-
}
|
|
856
831
|
s.currentUnit = null;
|
|
857
832
|
}
|
|
858
833
|
|
|
@@ -993,9 +968,6 @@ function buildLoopDeps(): LoopDeps {
|
|
|
993
968
|
getMainBranch,
|
|
994
969
|
// Unit closeout + runtime records
|
|
995
970
|
closeoutUnit,
|
|
996
|
-
verifyExpectedArtifact,
|
|
997
|
-
clearUnitRuntimeRecord,
|
|
998
|
-
writeUnitRuntimeRecord,
|
|
999
971
|
recordOutcome,
|
|
1000
972
|
writeLock,
|
|
1001
973
|
captureAvailableSkills,
|
|
@@ -1168,15 +1140,6 @@ export async function startAuto(
|
|
|
1168
1140
|
}
|
|
1169
1141
|
invalidateAllCaches();
|
|
1170
1142
|
|
|
1171
|
-
// Clean stale runtime records left from the paused session
|
|
1172
|
-
try {
|
|
1173
|
-
await selfHealRuntimeRecords(s.basePath, ctx);
|
|
1174
|
-
} catch (e) {
|
|
1175
|
-
debugLog("resume-self-heal-runtime-failed", {
|
|
1176
|
-
error: e instanceof Error ? e.message : String(e),
|
|
1177
|
-
});
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
1143
|
if (s.pausedSessionFile) {
|
|
1181
1144
|
const activityDir = join(gsdRoot(s.basePath), "activity");
|
|
1182
1145
|
const recovery = synthesizeCrashRecovery(
|
|
@@ -1200,19 +1163,14 @@ export async function startAuto(
|
|
|
1200
1163
|
lockBase(),
|
|
1201
1164
|
"resuming",
|
|
1202
1165
|
s.currentMilestoneId ?? "unknown",
|
|
1203
|
-
s.completedUnits.length,
|
|
1204
1166
|
);
|
|
1205
1167
|
writeLock(
|
|
1206
1168
|
lockBase(),
|
|
1207
1169
|
"resuming",
|
|
1208
1170
|
s.currentMilestoneId ?? "unknown",
|
|
1209
|
-
s.completedUnits.length,
|
|
1210
1171
|
);
|
|
1211
1172
|
logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
|
|
1212
1173
|
|
|
1213
|
-
// Clear orphaned runtime records from prior process deaths before entering the loop
|
|
1214
|
-
await selfHealRuntimeRecords(s.basePath, ctx);
|
|
1215
|
-
|
|
1216
1174
|
await autoLoop(ctx, pi, s, buildLoopDeps());
|
|
1217
1175
|
cleanupAfterLoopExit(ctx);
|
|
1218
1176
|
return;
|
|
@@ -1244,9 +1202,6 @@ export async function startAuto(
|
|
|
1244
1202
|
}
|
|
1245
1203
|
logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
|
|
1246
1204
|
|
|
1247
|
-
// Clear orphaned runtime records from prior process deaths before entering the loop
|
|
1248
|
-
await selfHealRuntimeRecords(s.basePath, ctx);
|
|
1249
|
-
|
|
1250
1205
|
// Dispatch the first unit
|
|
1251
1206
|
await autoLoop(ctx, pi, s, buildLoopDeps());
|
|
1252
1207
|
cleanupAfterLoopExit(ctx);
|
|
@@ -1387,7 +1342,6 @@ export async function dispatchHookUnit(
|
|
|
1387
1342
|
s.basePath = targetBasePath;
|
|
1388
1343
|
s.autoStartTime = Date.now();
|
|
1389
1344
|
s.currentUnit = null;
|
|
1390
|
-
s.completedUnits = [];
|
|
1391
1345
|
s.pendingQuickTasks = [];
|
|
1392
1346
|
}
|
|
1393
1347
|
|
|
@@ -1412,21 +1366,6 @@ export async function dispatchHookUnit(
|
|
|
1412
1366
|
startedAt: hookStartedAt,
|
|
1413
1367
|
};
|
|
1414
1368
|
|
|
1415
|
-
writeUnitRuntimeRecord(
|
|
1416
|
-
s.basePath,
|
|
1417
|
-
hookUnitType,
|
|
1418
|
-
triggerUnitId,
|
|
1419
|
-
hookStartedAt,
|
|
1420
|
-
{
|
|
1421
|
-
phase: "dispatched",
|
|
1422
|
-
wrapupWarningSent: false,
|
|
1423
|
-
timeoutAt: null,
|
|
1424
|
-
lastProgressAt: hookStartedAt,
|
|
1425
|
-
progressCount: 0,
|
|
1426
|
-
lastProgressKind: "dispatch",
|
|
1427
|
-
},
|
|
1428
|
-
);
|
|
1429
|
-
|
|
1430
1369
|
if (hookModel) {
|
|
1431
1370
|
const availableModels = ctx.modelRegistry.getAvailable();
|
|
1432
1371
|
const match = resolveModelId(hookModel, availableModels, ctx.model?.provider);
|
|
@@ -1450,7 +1389,6 @@ export async function dispatchHookUnit(
|
|
|
1450
1389
|
lockBase(),
|
|
1451
1390
|
hookUnitType,
|
|
1452
1391
|
triggerUnitId,
|
|
1453
|
-
s.completedUnits.length,
|
|
1454
1392
|
sessionFile,
|
|
1455
1393
|
);
|
|
1456
1394
|
|
|
@@ -1460,18 +1398,6 @@ export async function dispatchHookUnit(
|
|
|
1460
1398
|
s.unitTimeoutHandle = setTimeout(async () => {
|
|
1461
1399
|
s.unitTimeoutHandle = null;
|
|
1462
1400
|
if (!s.active) return;
|
|
1463
|
-
if (s.currentUnit) {
|
|
1464
|
-
writeUnitRuntimeRecord(
|
|
1465
|
-
s.basePath,
|
|
1466
|
-
hookUnitType,
|
|
1467
|
-
triggerUnitId,
|
|
1468
|
-
hookStartedAt,
|
|
1469
|
-
{
|
|
1470
|
-
phase: "timeout",
|
|
1471
|
-
timeoutAt: Date.now(),
|
|
1472
|
-
},
|
|
1473
|
-
);
|
|
1474
|
-
}
|
|
1475
1401
|
ctx.ui.notify(
|
|
1476
1402
|
`Hook ${hookName} exceeded ${supervisor.hard_timeout_minutes ?? 30}min timeout. Pausing auto-mode.`,
|
|
1477
1403
|
"warning",
|
|
@@ -1503,8 +1429,6 @@ export { dispatchDirectPhase } from "./auto-direct-dispatch.js";
|
|
|
1503
1429
|
|
|
1504
1430
|
// Re-export recovery functions for external consumers
|
|
1505
1431
|
export {
|
|
1506
|
-
resolveExpectedArtifactPath,
|
|
1507
|
-
verifyExpectedArtifact,
|
|
1508
|
-
writeBlockerPlaceholder,
|
|
1509
1432
|
buildLoopRemediationSteps,
|
|
1510
1433
|
} from "./auto-recovery.js";
|
|
1434
|
+
export { resolveExpectedArtifactPath } from "./auto-artifact-paths.js";
|
|
@@ -7,6 +7,7 @@ import { buildMilestoneFileName, resolveMilestonePath, resolveSliceFile, resolve
|
|
|
7
7
|
import { buildBeforeAgentStartResult } from "./system-context.js";
|
|
8
8
|
import { handleAgentEnd } from "./agent-end-recovery.js";
|
|
9
9
|
import { clearDiscussionFlowState, isDepthVerified, isQueuePhaseActive, markDepthVerified, resetWriteGateState, shouldBlockContextWrite } from "./write-gate.js";
|
|
10
|
+
import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
|
|
10
11
|
import { getDiscussionMilestoneId } from "../guided-flow.js";
|
|
11
12
|
import { loadToolApiKeys } from "../commands-config.js";
|
|
12
13
|
import { loadFile, saveFile, formatContinue } from "../files.js";
|
|
@@ -135,7 +136,28 @@ export function registerHooks(pi: ExtensionAPI): void {
|
|
|
135
136
|
return { block: true, reason: loopCheck.reason };
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
// ── Single-writer engine: block direct writes to STATE.md ──────────
|
|
140
|
+
// Covers write, edit, and bash tools to prevent bypass vectors.
|
|
141
|
+
if (isToolCallEventType("write", event)) {
|
|
142
|
+
if (isBlockedStateFile(event.input.path)) {
|
|
143
|
+
return { block: true, reason: BLOCKED_WRITE_ERROR };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (isToolCallEventType("edit", event)) {
|
|
148
|
+
if (isBlockedStateFile(event.input.path)) {
|
|
149
|
+
return { block: true, reason: BLOCKED_WRITE_ERROR };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (isToolCallEventType("bash", event)) {
|
|
154
|
+
if (isBashWriteToStateFile(event.input.command)) {
|
|
155
|
+
return { block: true, reason: BLOCKED_WRITE_ERROR };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
138
159
|
if (!isToolCallEventType("write", event)) return;
|
|
160
|
+
|
|
139
161
|
const result = shouldBlockContextWrite(
|
|
140
162
|
event.toolName,
|
|
141
163
|
event.input.path,
|
|
@@ -47,15 +47,10 @@ export async function guardRemoteSession(
|
|
|
47
47
|
return false;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
const unitsMsg = remote.completedUnits != null
|
|
51
|
-
? `${remote.completedUnits} units completed`
|
|
52
|
-
: "";
|
|
53
|
-
|
|
54
50
|
const choice = await showNextAction(ctx, {
|
|
55
51
|
title: `Auto-mode is running in another terminal (PID ${remote.pid})`,
|
|
56
52
|
summary: [
|
|
57
53
|
`Currently executing: ${unitLabel}`,
|
|
58
|
-
...(unitsMsg ? [unitsMsg] : []),
|
|
59
54
|
...(remote.startedAt ? [`Started: ${remote.startedAt}`] : []),
|
|
60
55
|
],
|
|
61
56
|
actions: [
|
|
@@ -63,7 +63,7 @@ export async function handleParallelCommand(trimmed: string, _ctx: ExtensionComm
|
|
|
63
63
|
}
|
|
64
64
|
const lines = ["# Parallel Workers\n"];
|
|
65
65
|
for (const worker of workers) {
|
|
66
|
-
lines.push(`- **${worker.milestoneId}** (${worker.title}) — ${worker.state} —
|
|
66
|
+
lines.push(`- **${worker.milestoneId}** (${worker.title}) — ${worker.state} — $${worker.cost.toFixed(2)}`);
|
|
67
67
|
}
|
|
68
68
|
const state = getOrchestratorState();
|
|
69
69
|
if (state) {
|
|
@@ -23,7 +23,6 @@ export interface LockData {
|
|
|
23
23
|
unitType: string;
|
|
24
24
|
unitId: string;
|
|
25
25
|
unitStartedAt: string;
|
|
26
|
-
completedUnits: number;
|
|
27
26
|
/** Path to the pi session JSONL file that was active when this unit started. */
|
|
28
27
|
sessionFile?: string;
|
|
29
28
|
}
|
|
@@ -37,7 +36,6 @@ export function writeLock(
|
|
|
37
36
|
basePath: string,
|
|
38
37
|
unitType: string,
|
|
39
38
|
unitId: string,
|
|
40
|
-
completedUnits: number,
|
|
41
39
|
sessionFile?: string,
|
|
42
40
|
): void {
|
|
43
41
|
try {
|
|
@@ -47,7 +45,6 @@ export function writeLock(
|
|
|
47
45
|
unitType,
|
|
48
46
|
unitId,
|
|
49
47
|
unitStartedAt: new Date().toISOString(),
|
|
50
|
-
completedUnits,
|
|
51
48
|
sessionFile,
|
|
52
49
|
};
|
|
53
50
|
const lp = lockPath(basePath);
|
|
@@ -102,12 +99,11 @@ export function formatCrashInfo(lock: LockData): string {
|
|
|
102
99
|
`Previous auto-mode session was interrupted.`,
|
|
103
100
|
` Was executing: ${lock.unitType} (${lock.unitId})`,
|
|
104
101
|
` Started at: ${lock.unitStartedAt}`,
|
|
105
|
-
` Units completed before crash: ${lock.completedUnits}`,
|
|
106
102
|
` PID: ${lock.pid}`,
|
|
107
103
|
];
|
|
108
104
|
|
|
109
105
|
// Add recovery guidance based on what was happening when it crashed
|
|
110
|
-
if (lock.unitType === "starting" && lock.unitId === "bootstrap"
|
|
106
|
+
if (lock.unitType === "starting" && lock.unitId === "bootstrap") {
|
|
111
107
|
lines.push(`No work was lost. Run /gsd auto to restart.`);
|
|
112
108
|
} else if (lock.unitType.includes("research") || lock.unitType.includes("plan")) {
|
|
113
109
|
lines.push(`The ${lock.unitType} unit may be incomplete. Run /gsd auto to re-run it.`);
|
|
@@ -99,18 +99,11 @@ export class GSDDashboardOverlay {
|
|
|
99
99
|
const currentUnit = dashData.currentUnit
|
|
100
100
|
? `${dashData.currentUnit.type}:${dashData.currentUnit.id}:${dashData.currentUnit.startedAt}`
|
|
101
101
|
: "-";
|
|
102
|
-
const lastCompleted = dashData.completedUnits.length > 0
|
|
103
|
-
? dashData.completedUnits[dashData.completedUnits.length - 1]
|
|
104
|
-
: null;
|
|
105
|
-
const completedKey = lastCompleted
|
|
106
|
-
? `${dashData.completedUnits.length}:${lastCompleted.type}:${lastCompleted.id}:${lastCompleted.finishedAt}`
|
|
107
|
-
: "0";
|
|
108
102
|
return [
|
|
109
103
|
base,
|
|
110
104
|
dashData.active ? "1" : "0",
|
|
111
105
|
dashData.paused ? "1" : "0",
|
|
112
106
|
currentUnit,
|
|
113
|
-
completedKey,
|
|
114
107
|
].join("|");
|
|
115
108
|
}
|
|
116
109
|
|
|
@@ -458,49 +451,6 @@ export class GSDDashboardOverlay {
|
|
|
458
451
|
lines.push(centered(th.fg("dim", "No active milestone.")));
|
|
459
452
|
}
|
|
460
453
|
|
|
461
|
-
if (this.dashData.completedUnits.length > 0) {
|
|
462
|
-
lines.push(blank());
|
|
463
|
-
lines.push(hr());
|
|
464
|
-
lines.push(row(th.fg("text", th.bold("Completed"))));
|
|
465
|
-
lines.push(blank());
|
|
466
|
-
|
|
467
|
-
// Build ledger lookup for budget indicators (last entry wins for retries)
|
|
468
|
-
const ledgerLookup = new Map<string, UnitMetrics>();
|
|
469
|
-
const currentLedger = getLedger();
|
|
470
|
-
if (currentLedger) {
|
|
471
|
-
for (const lu of currentLedger.units) {
|
|
472
|
-
ledgerLookup.set(`${lu.type}:${lu.id}`, lu);
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const recent = [...this.dashData.completedUnits].reverse().slice(0, 10);
|
|
477
|
-
for (const u of recent) {
|
|
478
|
-
// Budget indicators from ledger — use warning glyph for pressured units
|
|
479
|
-
const ledgerEntry = ledgerLookup.get(`${u.type}:${u.id}`);
|
|
480
|
-
const hadPressure = ledgerEntry?.continueHereFired === true;
|
|
481
|
-
const hadTruncation = (ledgerEntry?.truncationSections ?? 0) > 0;
|
|
482
|
-
const unitGlyph = hadPressure
|
|
483
|
-
? th.fg(STATUS_COLOR.warning, STATUS_GLYPH.warning)
|
|
484
|
-
: th.fg(STATUS_COLOR.done, STATUS_GLYPH.done);
|
|
485
|
-
const left = ` ${unitGlyph} ${th.fg("muted", unitLabel(u.type))} ${th.fg("muted", u.id)}`;
|
|
486
|
-
|
|
487
|
-
let budgetMarkers = "";
|
|
488
|
-
if (hadTruncation) {
|
|
489
|
-
budgetMarkers += th.fg("warning", ` ▼${ledgerEntry!.truncationSections}`);
|
|
490
|
-
}
|
|
491
|
-
if (hadPressure) {
|
|
492
|
-
budgetMarkers += th.fg("error", " → wrap-up");
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
const right = th.fg("dim", formatDuration(u.finishedAt - u.startedAt));
|
|
496
|
-
lines.push(row(joinColumns(`${left}${budgetMarkers}`, right, contentWidth)));
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
if (this.dashData.completedUnits.length > 10) {
|
|
500
|
-
lines.push(row(th.fg("dim", ` ...and ${this.dashData.completedUnits.length - 10} more`)));
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
454
|
const ledger = getLedger();
|
|
505
455
|
if (ledger && ledger.units.length > 0) {
|
|
506
456
|
const totals = getProjectTotals(ledger.units);
|