fix: sync worktree state to project root after each unit (#654)
When auto-mode runs in a worktree, .gsd/ metadata (STATE.md, roadmap checkboxes, slice plans, task summaries) only updates inside the worktree directory. The project root on main retains stale state. If auto-mode restarts, startAutoMode() calls deriveState(projectRoot) which reads the stale .gsd/ from main, sees completed units as incomplete, and re-dispatches them — causing an infinite loop on already-finished work. Add syncStateToProjectRoot() that copies STATE.md and the active milestone directory from worktree → project root after each unit's rebuildState + autoCommit. This ensures deriveState(projectRoot) on restart reads current completion state. The sync is fully non-fatal (try/catch wrapped). Failure falls back to existing behavior. Uses cpSync with recursive:true for the milestone directory tree.
This commit is contained in:
parent
a90aa0c8d6
commit
bdbd3579c9
1 changed files with 51 additions and 1 deletions
|
|
@ -82,7 +82,7 @@ import {
|
|||
import { join } from "node:path";
|
||||
import { sep as pathSep } from "node:path";
|
||||
import { homedir } from "node:os";
|
||||
import { readdirSync, readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync, statSync } from "node:fs";
|
||||
import { readdirSync, readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync, statSync, cpSync } from "node:fs";
|
||||
import { nativeIsRepo, nativeInit, nativeAddPaths, nativeCommit } from "./native-git-bridge.js";
|
||||
import {
|
||||
autoCommitCurrentBranch,
|
||||
|
|
@ -146,6 +146,45 @@ import {
|
|||
import { isDbAvailable } from "./gsd-db.js";
|
||||
import { hasPendingCaptures, loadPendingCaptures, countPendingCaptures } from "./captures.js";
|
||||
|
||||
// ─── Worktree → Project Root State Sync ───────────────────────────────────────
|
||||
// When running in an auto-worktree, dispatch state (.gsd/ metadata) diverges
|
||||
// between the worktree (where work happens) and the project root (where
|
||||
// startAutoMode reads initial state on restart). Without syncing, restarting
|
||||
// auto-mode reads stale state from the project root and re-dispatches
|
||||
// already-completed units.
|
||||
|
||||
/**
|
||||
* Sync dispatch-critical .gsd/ state files from worktree to project root.
|
||||
* Only runs when inside an auto-worktree (worktreePath differs from projectRoot).
|
||||
* Copies: STATE.md + active milestone directory (roadmap, slice plans, task summaries).
|
||||
* Non-fatal — sync failure should never block dispatch.
|
||||
*/
|
||||
function syncStateToProjectRoot(worktreePath: string, projectRoot: string, milestoneId: string | null): void {
|
||||
if (!worktreePath || !projectRoot || worktreePath === projectRoot) return;
|
||||
if (!milestoneId) return;
|
||||
|
||||
const wtGsd = join(worktreePath, ".gsd");
|
||||
const prGsd = join(projectRoot, ".gsd");
|
||||
|
||||
// 1. STATE.md — the quick-glance status used by initial deriveState()
|
||||
try {
|
||||
const src = join(wtGsd, "STATE.md");
|
||||
const dst = join(prGsd, "STATE.md");
|
||||
if (existsSync(src)) cpSync(src, dst, { force: true });
|
||||
} catch { /* non-fatal */ }
|
||||
|
||||
// 2. Milestone directory — ROADMAP, slice PLANs, task summaries
|
||||
// Copy the entire milestone .gsd subtree so deriveState reads current checkboxes
|
||||
try {
|
||||
const srcMilestone = join(wtGsd, "milestones", milestoneId);
|
||||
const dstMilestone = join(prGsd, "milestones", milestoneId);
|
||||
if (existsSync(srcMilestone)) {
|
||||
mkdirSync(dstMilestone, { recursive: true });
|
||||
cpSync(srcMilestone, dstMilestone, { recursive: true, force: true });
|
||||
}
|
||||
} catch { /* non-fatal */ }
|
||||
}
|
||||
|
||||
// ─── State ────────────────────────────────────────────────────────────────────
|
||||
|
||||
let active = false;
|
||||
|
|
@ -1214,6 +1253,17 @@ export async function handleAgentEnd(
|
|||
// Non-fatal
|
||||
}
|
||||
|
||||
// ── Sync worktree state back to project root ──────────────────────────
|
||||
// Ensures that if auto-mode restarts, deriveState(projectRoot) reads
|
||||
// current milestone progress instead of stale pre-worktree state (#654).
|
||||
if (originalBasePath && originalBasePath !== basePath) {
|
||||
try {
|
||||
syncStateToProjectRoot(basePath, originalBasePath, currentMilestoneId);
|
||||
} catch {
|
||||
// Non-fatal — stale state is the existing behavior, sync is an improvement
|
||||
}
|
||||
}
|
||||
|
||||
// ── Rewrite-docs completion: resolve overrides and reset circuit breaker ──
|
||||
if (currentUnit.type === "rewrite-docs") {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue