The Archive Was Never Lost — It Was Gitignored

The Broader Goal at the End of the Previous PR

PR #1994 (post #244) suspended the heuristic Default Agent Role lock in favor of empirical capability measurement, and named project-docs/costs.md — specifically cost_entries/capability_ratings — as the data the new capability_policy would eventually measure routing against. The goalpost coming out of that PR was building the enforcement gates (#1990, #1991) and the capability report generator (#1993) that policy depends on.

This PR is a detour forced by a standing problem with that very data source: gh:#1995, a bug that had already triggered three manual "restore the rows that got dropped" commits on this branch before this PR shipped an actual fix, rather than a fourth restoration.

Strategic Shift in This PR

No roadmap shift — this is debugging work that had to happen before the capability-measurement goalpost above could trust its own inputs. But it's worth naming the shift in how the investigation itself went, because it's the most useful thing in this post: the first root-cause theory was wrong, and the PR body says so rather than quietly fixing the write-up after the fact.

What This PR Shipped

First theory (retracted): _rotate_project_doc() writes an archive file for rows rotated out of the live costs.md/roadmap.md/memory.md view. The initial hypothesis was that a dispatched job's ephemeral git worktree — deleted post-merge per this repo's Worktree Hygiene Protocol — was silently destroying that archive before it ever got committed. Plausible, and matched git log --oneline -- project-docs/archive/ showing zero history.

What was actually true: _synlynk_project_docs_dir() resolves through _project_root(), which calls git rev-parse --git-common-dir — identical across every linked worktree of a repo, main checkout or nested job worktree alike (verified directly from both). The archive was never worktree-scoped; it lands in the shared, persistent <main-repo>/.synlynk/project-docs/archive/, which survives worktree cleanup fine. It's absent from git log because .synlynk/* is bulk-gitignored by this repo's own .gitignore — by design, not by accident. Nothing was silently destroyed.

The real cause of the visible symptom: _PROJECT_DOC_KEEP_N was 50, far smaller than the rate cost_entries grows. Nearly every regen truncated the git-tracked costs.md to its last 50 rows, so the live window slid on almost every PR touching cost logging — indistinguishable, in a PR diff, from data loss. The fix: raise it to 500, shared automatically by the roadmap and memory generators since all three call the same function.

A more severe bug found along the way, not fixed here: the archive file itself is massively duplicated. One monthly shard, .synlynk/project-docs/archive/costs-2026-H10.md, had 268,445 lines but only ~3,262 unique rows — one row repeated 45+ times, a 160MB file for what should be a few hundred KB. _rotate_project_doc() re-appends the entire overflow slice on every call instead of tracking a high-water mark of what it already archived. Filed separately as gh:#1999 rather than folded into this PR, since fixing it changes archival semantics more than a keep_n bump does and deserves its own review.

Also shipped: _rotate_project_doc() now best-effort git adds a freshly written archive file. In this repo's actual migrated-and-gitignored configuration that's a no-op in practice (git add refuses an ignored path without -f, and force-adding hundred-MB generated files into git history would be actively harmful — not done). It's still correct, tested code that closes the real worktree-loss case for any non-migrated synlynk-managed repo, where the archive path is the tracked, worktree-local one.

Verification: direct sqlite3 query against state.db confirmed the cost_entries table never lost the rows in question — all three "regen-drop" incidents were generated-view symptoms, not data-loss incidents, throughout.

Caught in Review

The retraction itself: after pushing the first fix and posting the initial root-cause comment to gh:#1995, re-checking _project_root()'s actual implementation (not just its name) disproved the ephemeral-worktree framing. Rather than leave the wrong claim standing in the issue thread, a correction comment went up immediately, and the PR description was rewritten before requesting review — the dispatched reviewer saw the corrected version, not the retracted one.

What Was Achieved Toward the Long-Arc Goal

project-docs/costs.md stops exhibiting window-slide churn that looked like data loss, closing the loop that had produced three restoration commits on this branch alone. More importantly for the capability-measurement goalpost from PR #1994: the data this policy will eventually route on was confirmed, end to end, to be durable in state.db regardless of what the generated markdown views show — the measurement substrate is sound even though its rendered view had a cosmetic bug.

The New Goalpost

Two threads now open against the cost-ledger surface: gh:#1999 (archive duplication — needs a high-water-mark design, not a quick patch) and the still-unbuilt capability report generator (gh:#1993) that PR #1994 flagged as blocked on state.db consolidation (gh:#1926). Either is a reasonable next pull on this thread; neither has started.

Refs #1995, #1999, #1994, #1993, #1926.