Context
Sub-leaf of #13015 (FM MVP), continuing the repo-provisioning surface. #13145 / PR #13146 shipped the pure path-derivation (the "where" — deriveAgentRepoPath). This is the next named sub-concern of the epic's "create, locate, health-check": given a derived checkout path, classify what is actually there into a provisioning-relevant state, so the (later) clone/repair leaf knows whether to clone, reuse, or surface a conflict.
The Problem
A provisioner that blindly clones fails on an existing checkout; one that blindly reuses corrupts on a path collision or a non-checkout occupant. The decision needs a read-only inspection first: absent → clone; an existing valid checkout → reuse (path-stability honored, no reclone — auto-memory is path-keyed); a path occupied by a non-checkout → conflict (never clobber). Isolating this read-only classification keeps it fully testable (temp-dir fixtures) and keeps the side-effecting clone/repair leaf thin.
The Architectural Reality
ai/services/fleet/inspectAgentRepo.mjs (Brain side, beside deriveAgentRepoPath / FleetRegistryService). A read-only, fs-only function (no git binary, no network) — fs.existsSync / statSync / readdirSync only, so it is deterministic + temp-dir-testable.
- Decoupled from
deriveAgentRepoPath (takes the resolved repoPath, does not import it) — single responsibility, the same decoupling the resolveCallTarget review established. The composition (derive → inspect → act) lives in the future I/O shell.
- Consumed by both the repo-provisioning clone/repair leaf AND the FM status surface ("missing / present / conflict" per agent).
The Fix
export function inspectAgentRepo({repoPath}) → {repoPath, exists, isCheckout, state, provisioningAction} where state ∈ {absent, empty, occupied-non-checkout, checkout} and provisioningAction ∈ {clone, reuse, conflict}:
- not present →
absent / clone
- present but not a directory, or a non-empty directory without
.git → occupied-non-checkout / conflict
- present empty directory →
empty / clone
- directory containing
.git → checkout / reuse
Throws on contract violation (non-string / empty / non-absolute repoPath).
Acceptance Criteria
Out of Scope
- The side-effecting clone / repair / worktree creation (the next leaf, consuming this +
deriveAgentRepoPath).
- Remote-URL match / repair (a refinement once
.git/config parsing is needed — this slice is presence + checkout-validity only).
- Path derivation (#13145 / #13146).
Decision Record impact
none — a new read-only fs helper consistent with existing fleet-service conventions.
Related
- Parent #13015; grandparent #13012. Sibling: #13145 (path derivation, PR #13146). Pattern precedent:
#13130 / #13140 / #13126 (pure / near-pure fleet + NL helpers with flattened specs).
Live latest-open sweep: checked latest open issues + a fleet / inspect / health-check / locate / provision scan at 2026-06-13T23:11Z; no equivalent found.
Release classification: post-release (FM product line; nothing v13.x-blocking).
Origin Session ID: 4c598c8f-d8a7-4288-9420-e825a45d310e
Context
Sub-leaf of #13015 (FM MVP), continuing the repo-provisioning surface. #13145 / PR #13146 shipped the pure path-derivation (the "where" —
deriveAgentRepoPath). This is the next named sub-concern of the epic's "create, locate, health-check": given a derived checkout path, classify what is actually there into a provisioning-relevant state, so the (later) clone/repair leaf knows whether to clone, reuse, or surface a conflict.The Problem
A provisioner that blindly clones fails on an existing checkout; one that blindly reuses corrupts on a path collision or a non-checkout occupant. The decision needs a read-only inspection first: absent → clone; an existing valid checkout → reuse (path-stability honored, no reclone — auto-memory is path-keyed); a path occupied by a non-checkout → conflict (never clobber). Isolating this read-only classification keeps it fully testable (temp-dir fixtures) and keeps the side-effecting clone/repair leaf thin.
The Architectural Reality
ai/services/fleet/inspectAgentRepo.mjs(Brain side, besidederiveAgentRepoPath/FleetRegistryService). A read-only, fs-only function (no git binary, no network) —fs.existsSync/statSync/readdirSynconly, so it is deterministic + temp-dir-testable.deriveAgentRepoPath(takes the resolvedrepoPath, does not import it) — single responsibility, the same decoupling theresolveCallTargetreview established. The composition (derive → inspect → act) lives in the future I/O shell.The Fix
export function inspectAgentRepo({repoPath})→{repoPath, exists, isCheckout, state, provisioningAction}wherestate ∈ {absent, empty, occupied-non-checkout, checkout}andprovisioningAction ∈ {clone, reuse, conflict}:absent/clone.git→occupied-non-checkout/conflictempty/clone.git→checkout/reuseThrows on contract violation (non-string / empty / non-absolute
repoPath).Acceptance Criteria
ai/services/fleet/inspectAgentRepo.mjsexportsinspectAgentRepo({repoPath}); read-only, fs-only (no git / network / writes).cloneover an existing checkout (→reuse), and neverreuse/cloneover a non-empty non-checkout (→conflict; no clobber).repoPath.test/playwright/unit/ai/inspectAgentRepo.spec.mjsexercises each state via temp-dir fixtures; green under the custom unit config.Out of Scope
deriveAgentRepoPath)..git/configparsing is needed — this slice is presence + checkout-validity only).Decision Record impact
none— a new read-only fs helper consistent with existing fleet-service conventions.Related
#13130/#13140/#13126(pure / near-pure fleet + NL helpers with flattened specs).Live latest-open sweep: checked latest open issues + a fleet / inspect / health-check / locate / provision scan at 2026-06-13T23:11Z; no equivalent found.
Release classification: post-release (FM product line; nothing v13.x-blocking).
Origin Session ID: 4c598c8f-d8a7-4288-9420-e825a45d310e