From 224475c861ef992a877ebc7df3c2eaf0f7e4a303 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Sat, 25 Jul 2026 02:40:41 -0700 Subject: [PATCH 1/6] chore(porch): 1239 init air --- .../status.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 codev/projects/1239-agent-farm-migration-backups-a/status.yaml diff --git a/codev/projects/1239-agent-farm-migration-backups-a/status.yaml b/codev/projects/1239-agent-farm-migration-backups-a/status.yaml new file mode 100644 index 000000000..497935f53 --- /dev/null +++ b/codev/projects/1239-agent-farm-migration-backups-a/status.yaml @@ -0,0 +1,14 @@ +id: '1239' +title: agent-farm-migration-backups-a +protocol: air +phase: implement +plan_phases: [] +current_plan_phase: null +gates: + pr: + status: pending +iteration: 1 +build_complete: false +history: [] +started_at: '2026-07-25T09:40:41.705Z' +updated_at: '2026-07-25T09:40:41.706Z' From 22b6670d031a63ca324229ef4f58945b2a10d2e3 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Sat, 25 Jul 2026 02:46:57 -0700 Subject: [PATCH 2/6] [AIR #1239] Surface unreaped migration backups in codev doctor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit State migrations leave copies of the pre-migration state behind and nothing ever removes them — a disk audit found an 18 GB ~/.agent-farm.bak-* directory still present three weeks after the migration that created it. Adds lib/migration-backup-audit.ts, which finds both leftover families: whole-directory home copies (~/.agent-farm.bak-*, ~/agent-farm-db-backup-*) and the *.pre-merge- renames that db/consolidate.ts leaves behind after merging a state.db into global.db. Each is reported with age and size on disk (measured via du -sk, since a Node walk would stat tens of thousands of log files on exactly the multi-GB dirs this exists to find). codev doctor gains a quiet-by-default Migration Backups section: it prints only when a leftover exists, warns on anything past a 7-day verification window, and gives the exact rm -rf command. It never deletes — these are the user's own state copies, so the removal decision stays with the human. --- codev-skeleton/resources/commands/codev.md | 5 + codev/resources/commands/codev.md | 5 + codev/state/air-1239_thread.md | 32 +++ .../__tests__/migration-backup-audit.test.ts | 182 +++++++++++++++++ packages/codev/src/commands/doctor.ts | 45 ++++- .../codev/src/lib/migration-backup-audit.ts | 188 ++++++++++++++++++ 6 files changed, 456 insertions(+), 1 deletion(-) create mode 100644 codev/state/air-1239_thread.md create mode 100644 packages/codev/src/__tests__/migration-backup-audit.test.ts create mode 100644 packages/codev/src/lib/migration-backup-audit.ts diff --git a/codev-skeleton/resources/commands/codev.md b/codev-skeleton/resources/commands/codev.md index 88d336d94..28fca3d4e 100644 --- a/codev-skeleton/resources/commands/codev.md +++ b/codev-skeleton/resources/commands/codev.md @@ -102,6 +102,11 @@ Verifies that all required dependencies are installed and properly configured: - Gemini (Antigravity CLI, `agy`) - Codex (`@openai/codex`) +**Migration Backups (report-only):** +- Leftover copies of pre-migration state — whole-directory `~/.agent-farm.bak-*` / `~/agent-farm-db-backup-*` copies and the `*.pre-merge-` renames left by `afx db consolidate` — listed with age and size on disk. +- Nothing reaps these automatically, so doctor surfaces each one together with the exact `rm -rf` command and leaves the decision to you. It never deletes anything. +- Backups younger than 7 days are listed but not counted as warnings; the section is omitted entirely when there are none. + **Exit Codes:** - `0` - All OK or warnings only - `1` - Required dependencies missing diff --git a/codev/resources/commands/codev.md b/codev/resources/commands/codev.md index 88ed4b9b0..7837ca259 100644 --- a/codev/resources/commands/codev.md +++ b/codev/resources/commands/codev.md @@ -102,6 +102,11 @@ Verifies that all required dependencies are installed and properly configured: - Gemini (Antigravity CLI, `agy`) - Codex (`@openai/codex`) +**Migration Backups (report-only):** +- Leftover copies of pre-migration state — whole-directory `~/.agent-farm.bak-*` / `~/agent-farm-db-backup-*` copies and the `*.pre-merge-` renames left by `afx db consolidate` — listed with age and size on disk. +- Nothing reaps these automatically, so doctor surfaces each one together with the exact `rm -rf` command and leaves the decision to you. It never deletes anything. +- Backups younger than 7 days are listed but not counted as warnings; the section is omitted entirely when there are none. + **Exit Codes:** - `0` - All OK or warnings only - `1` - Required dependencies missing diff --git a/codev/state/air-1239_thread.md b/codev/state/air-1239_thread.md new file mode 100644 index 000000000..7b2a5f645 --- /dev/null +++ b/codev/state/air-1239_thread.md @@ -0,0 +1,32 @@ +# air-1239 — migration backups are never reaped + +## Implement phase + +**Investigation finding (important):** no code in this repo creates the +`~/.agent-farm.bak-*` directory named in the issue. `git grep -i backup` over +`packages/**` returns nothing. Those directories were produced by ad-hoc +migration procedure during the #1118 state consolidation (a manual `cp -R`), +not by a shipped code path. On this machine they exist as +`~/agent-farm-db-backup-20260702-113930` (4.5M) and +`~/agent-farm-db-backup-premulti-20260702-115840` (4.2M). + +Consequence: the issue's **first** proposal ("migrations that create a `.bak` +should record it and reap it") has no code hook to attach to — there is no +migration that creates one. The *only* migration leftover the codebase itself +creates is `state.db.pre-merge-` (`db/consolidate.ts:335` +`renameWithSidecars`), which is also never reaped. + +So I'm implementing the issue's **second** proposal, which is the explicitly +sanctioned minimum and the one that actually fits the code: `codev doctor` +surfaces every migration-backup leftover with age + size and the exact removal +command. Deliberately NOT auto-deleting: these are multi-GB copies of user +state, and silently `rm -rf`-ing them is exactly the class of action that needs +human consent. + +Scope covers both leftover families: +1. `~/{.,}agent-farm*bak*` sibling backups of the agent-farm home dir +2. `*.pre-merge-*` renames inside `~/.agent-farm/` and the workspace + `.agent-farm/` (the ones `db/consolidate.ts` genuinely creates) + +New lib `packages/codev/src/lib/migration-backup-audit.ts` + doctor section, +following the `pr-gate-audit.ts` / `gitignore.ts` audit-lib precedent. diff --git a/packages/codev/src/__tests__/migration-backup-audit.test.ts b/packages/codev/src/__tests__/migration-backup-audit.test.ts new file mode 100644 index 000000000..a8eac4a9f --- /dev/null +++ b/packages/codev/src/__tests__/migration-backup-audit.test.ts @@ -0,0 +1,182 @@ +/** + * Tests for the migration-backup audit (#1239). + * + * Verifies that both leftover families are found (whole-directory home copies + * and `*.pre-merge-*` renames), that age drives the stale flag, and that + * unrelated entries are left alone. + */ + +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { tmpdir } from 'node:os'; +import { + findMigrationBackups, + formatMigrationBackup, + formatBytes, + totalBytes, + STALE_BACKUP_AGE_DAYS, +} from '../lib/migration-backup-audit.js'; + +const NOW = new Date('2026-07-25T12:00:00Z'); + +/** Create a directory with one file in it, aged to `daysOld`. */ +function makeAgedDir(parent: string, name: string, daysOld: number): string { + const dir = path.join(parent, name); + fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(path.join(dir, 'global.db'), 'x'.repeat(4096)); + const mtime = new Date(NOW.getTime() - daysOld * 86_400_000); + fs.utimesSync(dir, mtime, mtime); + return dir; +} + +/** Create a file aged to `daysOld`. */ +function makeAgedFile(parent: string, name: string, daysOld: number): string { + fs.mkdirSync(parent, { recursive: true }); + const file = path.join(parent, name); + fs.writeFileSync(file, 'x'.repeat(1024)); + const mtime = new Date(NOW.getTime() - daysOld * 86_400_000); + fs.utimesSync(file, mtime, mtime); + return file; +} + +describe('migration-backup-audit', () => { + let home: string; + + beforeEach(() => { + home = fs.mkdtempSync(path.join(tmpdir(), 'migration-backup-')); + }); + + afterEach(() => { + fs.rmSync(home, { recursive: true, force: true }); + }); + + it('finds nothing in a clean home', () => { + fs.mkdirSync(path.join(home, '.agent-farm'), { recursive: true }); + fs.writeFileSync(path.join(home, '.agent-farm', 'global.db'), 'x'); + expect(findMigrationBackups({ home, now: NOW })).toEqual([]); + }); + + it('finds a dotted whole-directory home copy', () => { + const dir = makeAgedDir(home, '.agent-farm.bak-20260702-113930', 23); + const found = findMigrationBackups({ home, now: NOW }); + expect(found).toHaveLength(1); + expect(found[0].path).toBe(dir); + expect(found[0].kind).toBe('home-copy'); + expect(found[0].ageDays).toBe(23); + expect(found[0].stale).toBe(true); + }); + + it('finds undotted home copies from the #1118 consolidation', () => { + makeAgedDir(home, 'agent-farm-db-backup-20260702-113930', 23); + makeAgedDir(home, 'agent-farm-db-backup-premulti-20260702-115840', 23); + const found = findMigrationBackups({ home, now: NOW }); + expect(found.map(b => path.basename(b.path)).sort()).toEqual([ + 'agent-farm-db-backup-20260702-113930', + 'agent-farm-db-backup-premulti-20260702-115840', + ]); + }); + + it('ignores the live agent-farm dir and unrelated entries', () => { + fs.mkdirSync(path.join(home, '.agent-farm'), { recursive: true }); + makeAgedDir(home, 'Documents', 100); + makeAgedDir(home, 'my-backups', 100); + makeAgedDir(home, 'agent-farm-notes', 100); + expect(findMigrationBackups({ home, now: NOW })).toEqual([]); + }); + + it('requires the agent-farm prefix, not just a backup marker', () => { + // `my-backups/` in a real home must never be reported, let alone recommended + // for `rm -rf`. + makeAgedDir(home, 'my-backups', 100); + makeAgedDir(home, 'Backup', 100); + expect(findMigrationBackups({ home, now: NOW })).toEqual([]); + }); + + it('finds *.pre-merge-* renames inside the agent-farm home', () => { + makeAgedFile(path.join(home, '.agent-farm'), 'state.db.pre-merge-20260702-113930', 23); + const found = findMigrationBackups({ home, now: NOW }); + expect(found).toHaveLength(1); + expect(found[0].kind).toBe('pre-merge'); + }); + + it('finds *.pre-merge-* renames inside the workspace .agent-farm', () => { + const workspaceRoot = fs.mkdtempSync(path.join(tmpdir(), 'migration-backup-ws-')); + try { + makeAgedFile(path.join(workspaceRoot, '.agent-farm'), 'state.db.pre-merge-20260702-113930', 30); + makeAgedFile(path.join(workspaceRoot, '.agent-farm'), 'state.db.pre-merge-20260702-113930-wal', 30); + const found = findMigrationBackups({ home, workspaceRoot, now: NOW }); + expect(found).toHaveLength(2); + expect(found.every(b => b.kind === 'pre-merge')).toBe(true); + } finally { + fs.rmSync(workspaceRoot, { recursive: true, force: true }); + } + }); + + it('does not scan a workspace when none is given', () => { + const workspaceRoot = fs.mkdtempSync(path.join(tmpdir(), 'migration-backup-ws-')); + try { + makeAgedFile(path.join(workspaceRoot, '.agent-farm'), 'state.db.pre-merge-1', 30); + expect(findMigrationBackups({ home, workspaceRoot: null, now: NOW })).toEqual([]); + } finally { + fs.rmSync(workspaceRoot, { recursive: true, force: true }); + } + }); + + it('marks backups stale only at or beyond the verification window', () => { + makeAgedDir(home, '.agent-farm.bak-fresh', STALE_BACKUP_AGE_DAYS - 1); + makeAgedDir(home, '.agent-farm.bak-due', STALE_BACKUP_AGE_DAYS); + const found = findMigrationBackups({ home, now: NOW }); + const byName = Object.fromEntries(found.map(b => [path.basename(b.path), b])); + expect(byName['.agent-farm.bak-fresh'].stale).toBe(false); + expect(byName['.agent-farm.bak-due'].stale).toBe(true); + }); + + it('sorts newest first', () => { + makeAgedDir(home, '.agent-farm.bak-old', 90); + makeAgedDir(home, '.agent-farm.bak-new', 2); + const found = findMigrationBackups({ home, now: NOW }); + expect(found.map(b => path.basename(b.path))).toEqual([ + '.agent-farm.bak-new', + '.agent-farm.bak-old', + ]); + }); + + it('measures size on disk', () => { + makeAgedDir(home, '.agent-farm.bak-sized', 10); + const found = findMigrationBackups({ home, now: NOW }); + expect(found[0].sizeBytes).toBeGreaterThan(0); + expect(totalBytes(found)).toBe(found[0].sizeBytes); + }); + + it('treats unmeasurable backups as zero in the total', () => { + expect(totalBytes([ + { path: '/a', kind: 'home-copy', ageDays: 9, sizeBytes: null, stale: true }, + { path: '/b', kind: 'home-copy', ageDays: 9, sizeBytes: 2048, stale: true }, + ])).toBe(2048); + }); + + it('formats bytes at human scale', () => { + expect(formatBytes(512)).toBe('512 B'); + expect(formatBytes(1536)).toBe('1.5 KB'); + expect(formatBytes(18 * 1024 ** 3)).toBe('18.0 GB'); + }); + + it('renders age and size, and says so when size is unknown', () => { + expect(formatMigrationBackup({ + path: '/home/u/.agent-farm.bak-20260702-113930', + kind: 'home-copy', + ageDays: 23, + sizeBytes: 18 * 1024 ** 3, + stale: true, + })).toBe('/home/u/.agent-farm.bak-20260702-113930 — 23 days old, 18.0 GB'); + + expect(formatMigrationBackup({ + path: '/home/u/.agent-farm.bak-x', + kind: 'home-copy', + ageDays: 1, + sizeBytes: null, + stale: false, + })).toBe('/home/u/.agent-farm.bak-x — 1 day old, size unknown'); + }); +}); diff --git a/packages/codev/src/commands/doctor.ts b/packages/codev/src/commands/doctor.ts index 5bdb83182..17b3ca1d3 100644 --- a/packages/codev/src/commands/doctor.ts +++ b/packages/codev/src/commands/doctor.ts @@ -21,6 +21,13 @@ import { formatDriftFinding, formatStaleness, } from '../lib/protocol-drift-audit.js'; +import { + findMigrationBackups, + formatMigrationBackup, + formatBytes, + totalBytes, + STALE_BACKUP_AGE_DAYS, +} from '../lib/migration-backup-audit.js'; import { resolveAgyBin, AGY_OAUTH_MARKERS } from './consult/index.js'; const __filename = fileURLToPath(import.meta.url); @@ -733,8 +740,44 @@ export async function doctor(): Promise { console.log(''); - // Check codev directory structure (only if we're in a codev project) const workspaceRoot = findWorkspaceRoot(); + + // Migration backups (#1239): state migrations leave copies of the pre-migration + // state behind — whole-directory `~/.agent-farm.bak-*` copies and the + // `*.pre-merge-*` renames from db/consolidate.ts — and nothing ever removes + // them. One was found at 18 GB three weeks after the migration that made it. + // Report-only: these are the user's own state copies, so doctor surfaces age + + // size + the removal command and leaves the decision to the human. QUIET BY + // DEFAULT — the section prints only when a leftover exists. + const migrationBackups = findMigrationBackups({ workspaceRoot }); + if (migrationBackups.length > 0) { + const reclaimable = totalBytes(migrationBackups); + console.log(chalk.bold('Migration Backups') + ' (leftover copies of pre-migration state)'); + console.log(''); + for (const backup of migrationBackups) { + if (backup.stale) { + console.log(` ${chalk.yellow('⚠')} ${formatMigrationBackup(backup)}`); + warnings++; + warningDetails.push({ + name: 'Migration backup', + issue: formatMigrationBackup(backup), + recommendation: `nothing reaps these — if the migrated state is healthy, run: rm -rf "${backup.path}"`, + }); + } else { + console.log( + ` ${chalk.dim('○')} ${formatMigrationBackup(backup)}` + + chalk.dim(` (within the ${STALE_BACKUP_AGE_DAYS}-day verification window)`) + ); + } + } + if (reclaimable > 0) { + console.log(''); + console.log(chalk.dim(` ${formatBytes(reclaimable)} reclaimable across ${migrationBackups.length} backup(s).`)); + } + console.log(''); + } + + // Check codev directory structure (only if we're in a codev project) if (workspaceRoot && existsSync(resolve(workspaceRoot, 'codev'))) { console.log(chalk.bold('Codev Structure') + ' (project configuration)'); console.log(''); diff --git a/packages/codev/src/lib/migration-backup-audit.ts b/packages/codev/src/lib/migration-backup-audit.ts new file mode 100644 index 000000000..7cd6469bd --- /dev/null +++ b/packages/codev/src/lib/migration-backup-audit.ts @@ -0,0 +1,188 @@ +/** + * Migration-backup audit for `codev doctor` (issue #1239). + * + * State migrations leave copies of the pre-migration state behind and nothing + * ever removes them. A disk audit on 2026-07-25 found an 18 GB + * `~/.agent-farm.bak-*` directory still on disk three weeks after the migration + * that created it — a full copy of the agent-farm dir, dominated by the logs it + * duplicated. + * + * Two leftover families exist: + * + * 1. `~/{.,}agent-farm*bak*` — whole-directory copies of the agent-farm home, + * taken by hand during the #1118 state consolidation. These are the big + * ones (they include `logs/`). + * 2. `*.pre-merge-` — the source `state.db` (plus `-wal`/`-shm` + * sidecars) that `db/consolidate.ts` renames rather than deletes after + * merging it into `global.db`. Small, but equally unreaped. + * + * This module reports; it never deletes. These are multi-gigabyte copies of the + * user's own state, and the decision to remove them is the human's. Doctor + * prints age + size + the exact `rm -rf` command so that decision is one + * paste away. + */ + +import { readdirSync, lstatSync, existsSync } from 'node:fs'; +import { homedir } from 'node:os'; +import { join } from 'node:path'; +import { spawnSync } from 'node:child_process'; + +/** + * A leftover is only worth nagging about once the migration that produced it + * has demonstrably held up. Below this age it is reported informationally; + * at or above it, doctor counts it as a warning. + */ +export const STALE_BACKUP_AGE_DAYS = 7; + +export type MigrationBackupKind = 'home-copy' | 'pre-merge'; + +export interface MigrationBackup { + /** Absolute path to the leftover file or directory. */ + path: string; + kind: MigrationBackupKind; + /** Whole days since last modification. */ + ageDays: number; + /** Size on disk in bytes, or null when it could not be measured. */ + sizeBytes: number | null; + /** True once `ageDays >= STALE_BACKUP_AGE_DAYS`. */ + stale: boolean; +} + +/** + * Matches a hand-taken whole-directory copy of the agent-farm home. + * + * Requires BOTH an `agent-farm` prefix (so an unrelated `my-backups/` is never + * touched) and a backup marker. The marker spelling has varied across the + * migrations that produced these — `.agent-farm.bak-` and + * `agent-farm-db-backup-` are both real — so match `bak` and `back` alike. + * The live `.agent-farm` itself has no separator after the prefix and is never + * matched. + */ +function isHomeCopy(name: string): boolean { + return /^\.?agent-farm[.-]/i.test(name) && /bac?k/i.test(name); +} + +/** Matches the `*.pre-merge-` renames left by db/consolidate.ts. */ +function isPreMerge(name: string): boolean { + return name.includes('.pre-merge-'); +} + +/** + * Measure a path's size on disk with `du -sk`. + * + * A recursive Node walk would stat tens of thousands of log files on exactly + * the multi-GB directories this audit exists to find, so shell out instead. + * Returns null (reported as "size unknown") when `du` is unavailable or times + * out — never a guessed number. + */ +function measureBytes(path: string): number | null { + const result = spawnSync('du', ['-sk', path], { encoding: 'utf-8', timeout: 20000 }); + if (result.status !== 0 || !result.stdout) return null; + const kb = parseInt(result.stdout.trim().split(/\s+/)[0], 10); + return Number.isFinite(kb) ? kb * 1024 : null; +} + +/** Human-readable byte size, e.g. `18.0 GB`. */ +export function formatBytes(bytes: number): string { + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + let value = bytes; + let unit = 0; + while (value >= 1024 && unit < units.length - 1) { + value /= 1024; + unit++; + } + return unit === 0 ? `${value} B` : `${value.toFixed(1)} ${units[unit]}`; +} + +function inspect( + path: string, + kind: MigrationBackupKind, + now: number +): MigrationBackup | null { + let stat; + try { + stat = lstatSync(path); + } catch { + // Vanished between readdir and lstat — nothing to report. + return null; + } + const ageDays = Math.floor((now - stat.mtimeMs) / 86_400_000); + return { + path, + kind, + ageDays, + sizeBytes: measureBytes(path), + stale: ageDays >= STALE_BACKUP_AGE_DAYS, + }; +} + +function scanDir( + dir: string, + match: (name: string) => boolean, + kind: MigrationBackupKind, + now: number +): MigrationBackup[] { + if (!existsSync(dir)) return []; + let names: string[]; + try { + names = readdirSync(dir); + } catch { + // Unreadable directory (permissions) — not something doctor should fail on. + return []; + } + const found: MigrationBackup[] = []; + for (const name of names) { + if (!match(name)) continue; + const entry = inspect(join(dir, name), kind, now); + if (entry) found.push(entry); + } + return found; +} + +export interface FindMigrationBackupsOptions { + /** Home directory to scan. Defaults to the real home; overridable for tests. */ + home?: string; + /** Workspace root whose `.agent-farm/` is scanned for `*.pre-merge-*`. */ + workspaceRoot?: string | null; + /** Reference time for age computation. Defaults to now. */ + now?: Date; +} + +/** + * Find every migration leftover on disk, newest first. + * + * Scans the home directory for whole-directory copies, and both the agent-farm + * home (`~/.agent-farm/`) and the workspace's `.agent-farm/` for `*.pre-merge-*` + * renames. Read-only; safe to call unconditionally. + */ +export function findMigrationBackups( + options: FindMigrationBackupsOptions = {} +): MigrationBackup[] { + const home = options.home ?? homedir(); + const now = (options.now ?? new Date()).getTime(); + + const backups = [ + ...scanDir(home, isHomeCopy, 'home-copy', now), + ...scanDir(join(home, '.agent-farm'), isPreMerge, 'pre-merge', now), + ]; + + if (options.workspaceRoot) { + backups.push( + ...scanDir(join(options.workspaceRoot, '.agent-farm'), isPreMerge, 'pre-merge', now) + ); + } + + return backups.sort((a, b) => a.ageDays - b.ageDays); +} + +/** One-line doctor rendering: path, age, size. */ +export function formatMigrationBackup(backup: MigrationBackup): string { + const age = backup.ageDays === 1 ? '1 day old' : `${backup.ageDays} days old`; + const size = backup.sizeBytes === null ? 'size unknown' : formatBytes(backup.sizeBytes); + return `${backup.path} — ${age}, ${size}`; +} + +/** Total measured bytes across a set of leftovers (unmeasurable ones count 0). */ +export function totalBytes(backups: MigrationBackup[]): number { + return backups.reduce((sum, b) => sum + (b.sizeBytes ?? 0), 0); +} From 709ae9082a5e6197b03360eb786ed644fd8df89a Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Sat, 25 Jul 2026 02:48:08 -0700 Subject: [PATCH 3/6] chore(porch): 1239 pr phase-transition --- .../projects/1239-agent-farm-migration-backups-a/status.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codev/projects/1239-agent-farm-migration-backups-a/status.yaml b/codev/projects/1239-agent-farm-migration-backups-a/status.yaml index 497935f53..24b913221 100644 --- a/codev/projects/1239-agent-farm-migration-backups-a/status.yaml +++ b/codev/projects/1239-agent-farm-migration-backups-a/status.yaml @@ -1,7 +1,7 @@ id: '1239' title: agent-farm-migration-backups-a protocol: air -phase: implement +phase: pr plan_phases: [] current_plan_phase: null gates: @@ -11,4 +11,4 @@ iteration: 1 build_complete: false history: [] started_at: '2026-07-25T09:40:41.705Z' -updated_at: '2026-07-25T09:40:41.706Z' +updated_at: '2026-07-25T09:48:08.852Z' From 4051a191463173446186a91cf771dcc8b6e334a8 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Sat, 25 Jul 2026 02:52:22 -0700 Subject: [PATCH 4/6] chore(porch): 1239 pr gate-requested --- .../projects/1239-agent-farm-migration-backups-a/status.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codev/projects/1239-agent-farm-migration-backups-a/status.yaml b/codev/projects/1239-agent-farm-migration-backups-a/status.yaml index 24b913221..10ef83bf9 100644 --- a/codev/projects/1239-agent-farm-migration-backups-a/status.yaml +++ b/codev/projects/1239-agent-farm-migration-backups-a/status.yaml @@ -7,8 +7,10 @@ current_plan_phase: null gates: pr: status: pending + requested_at: '2026-07-25T09:52:22.852Z' iteration: 1 build_complete: false history: [] started_at: '2026-07-25T09:40:41.705Z' -updated_at: '2026-07-25T09:48:08.852Z' +updated_at: '2026-07-25T09:52:22.853Z' +pr_ready_for_human: true From 94f8089bb6d71d48be08a8032632f72cfbc50719 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Sat, 25 Jul 2026 02:52:39 -0700 Subject: [PATCH 5/6] [AIR #1239] Update builder thread with CMAP results and gate status --- codev/state/air-1239_thread.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/codev/state/air-1239_thread.md b/codev/state/air-1239_thread.md index 7b2a5f645..7b1726eb1 100644 --- a/codev/state/air-1239_thread.md +++ b/codev/state/air-1239_thread.md @@ -30,3 +30,37 @@ Scope covers both leftover families: New lib `packages/codev/src/lib/migration-backup-audit.ts` + doctor section, following the `pr-gate-audit.ts` / `gitignore.ts` audit-lib precedent. + +**Bug the tests caught, worth remembering:** the first match pattern was +`/bak/i`. It reads as obviously correct and is wrong — `backup` contains +`back`, not `bak` — so every `agent-farm-db-backup-*` dir (i.e. both of the +ones actually on the reporter's disk) was silently skipped. The test asserting +the *literal* directory names from #1118 is what pinned it. Generic pattern +tests would have passed. Pattern is now `/bac?k/i`. + +## PR phase + +Implement checks green (build + unit tests). Full suite: 183 files / 3655 +tests passed. Ran the built `codev doctor` against the real home dir — found +all five leftovers including the `*.pre-merge-*` files, sizes cross-checked +against `du -sh`. + +PR #1242 created, review in the body (AIR ships no review file). Architect +notified. CMAP consultation (codex + claude) running — skipping the gemini/agy +lane, which is known broken for `--type` reviews (no VERDICT emitted). + +Gotcha for siblings: `consult --protocol air --type impl` from a builder +worktree did NOT auto-detect the project as the skill doc claims — it printed +the full multi-project list and bailed. `--issue 1239` is required. + +CMAP results: **codex APPROVE / HIGH**, **claude APPROVE / HIGH**, no key +issues from either. Codex poked at a doc-sync concern between +`codev/resources/commands/codev.md` and the skeleton mirror but did not raise +it as an issue — the two files were already drifted before this change (the +skeleton is missing the `codev update --agent` docs); the sections I added are +byte-identical in both trees, which claude independently verified. That +pre-existing drift is out of scope here. + +PR-phase checks green (`pr_exists`, `e2e_tests`). `porch gate 1239` registered +the **pr gate — waiting for human approval**. Not running `porch approve`; +that's the human's call. From 9bc4fe111a0b201e4ec64359000ff3ff6e68f670 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Sat, 25 Jul 2026 05:37:53 -0700 Subject: [PATCH 6/6] chore(porch): 1239 pr gate-approved --- .../1239-agent-farm-migration-backups-a/status.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/codev/projects/1239-agent-farm-migration-backups-a/status.yaml b/codev/projects/1239-agent-farm-migration-backups-a/status.yaml index 10ef83bf9..2102c91d5 100644 --- a/codev/projects/1239-agent-farm-migration-backups-a/status.yaml +++ b/codev/projects/1239-agent-farm-migration-backups-a/status.yaml @@ -6,11 +6,12 @@ plan_phases: [] current_plan_phase: null gates: pr: - status: pending + status: approved requested_at: '2026-07-25T09:52:22.852Z' + approved_at: '2026-07-25T12:37:53.126Z' iteration: 1 build_complete: false history: [] started_at: '2026-07-25T09:40:41.705Z' -updated_at: '2026-07-25T09:52:22.853Z' -pr_ready_for_human: true +updated_at: '2026-07-25T12:37:53.126Z' +pr_ready_for_human: false