---
name: lfo-job-folder-diff
description: Invoke when Rocky-types "diff job folder against {path}" / "compare job vs {comparator}" / "what's new in the zip vs vault" — typically when an external comparator (Liam quote, Hus version, Drive bulk-download zip, prior snapshot) needs structural comparison against the live vault folder. Outputs a variance report with NEW (in source, not vault), CHANGED (size/hash differs), ORPHAN (in vault, not source), SAME (matching bytes). Adjacent to but NOT replacing `lfo-blind-driver` — BCS handles sealed methodology comparison; this handles open file-tree diff.
model: claude-sonnet-4-6
phase: ad-hoc (any time external snapshot needs reconciliation)
invoked_by: Rocky-typed command
preconditions:
  - both vault job folder + comparator path are readable
  - vault folder is NOT under active BCS (per CLAUDE.md §3.4) — if it is, refuse and surface to Rocky
inputs:
  - vault_folder_path (e.g. `Operations/Jobs/Upcoming/2627 - Airey Park Homebush West/`)
  - comparator_path (zip file OR directory OR Drive folder ID)
  - mode (`structural` | `byte-content` | `metadata-only`)
  - dedupe_strategy (`naming-aware` — vault `02 - Scope and Pricing/02b - BOQ and Rate Schedules/` matches comparator `02 - Scope & Pricing/BOQ & Rate Schedules/`; default true)
outputs:
  - `_Internal/diffs/diff-{job_no}-vs-{comparator-label}-{date}.md` — structured variance report
  - per-class file lists: NEW / CHANGED / ORPHAN / SAME counts + samples
  - recommended actions: pull-into-vault / leave-as-is / archive / update-existing
side_effects:
  - read-only on both sides
  - never auto-merges (always Rocky-decision per file class)
authored_by: claude-code-orchestrator
created: 2026-05-10
---

# lfo-job-folder-diff

Today's manual diff work (zip vs vault for 2626/2630/2631) showed the gap. Repeated comparison without tooling is fragile; codifying makes it fast + reproducible.

## Why this exists

External snapshots arrive routinely:
- Drive bulk-download zips (e.g. today's 9 zips)
- Hus's per-job memory note updates
- Liam's quote PDFs as alternate-pricing comparators
- Prior session backups
- VPS-mirror snapshots

Each must be reconciled against the curated vault folder. Vault uses one naming convention (`and` + sub-numbered tier-2 + `2626_DWG_*` rename); comparator may use a different one. Naive byte-diff fails — gives 100% mismatch even when content is identical. Naming-aware matching is the only useful comparison.

This skill encodes the naming-aware matching + classification logic.

## Procedure

### 1. Pre-flight

- Confirm both paths readable
- Read `state.json` for active BCS — if `{vault_folder_path}` corresponds to an active BCS bid, REFUSE per CLAUDE.md §3.4 (BCS handles its own comparison; this skill would leak comparator data). Surface to Rocky.
- Determine comparator type: zip / directory / Drive folder ID. Different read paths.

### 2. Build vault file index

Walk vault_folder_path; for each file, capture:
- relative path (vault-naming convention)
- canonical path (apply naming-aware transform — see step 4)
- size_bytes
- (optional in `byte-content` mode) sha256

### 3. Build comparator file index

For zip: use `zipfile.namelist()` + per-entry `info.file_size` + sha256 if requested.
For directory: walk + os.path.getsize().
For Drive folder: use gws CLI `gws drive files list --params '{"q":"... in parents",...}'` recursively.

### 4. Naming-aware transform (critical)

Apply transform to BOTH sides to compare in canonical space:

| Source style | Canonical form |
|---|---|
| `02 - Scope & Pricing/BOQ & Rate Schedules/` | `02-scope-pricing/02b-boq-rate-schedules/` |
| `02 - Scope and Pricing/02b - BOQ and Rate Schedules/` | `02-scope-pricing/02b-boq-rate-schedules/` (vault canonical) |
| `EXA01A08 Cast in situ pit details.pdf` | `cast-in-situ-pit-details.pdf` (strip job-code prefix) |
| `2626_DWG_Cast-In-Situ-Pit-Details.pdf` | `cast-in-situ-pit-details.pdf` (strip vault rename prefix) |

Transform rules:
- Lowercase
- Replace `&` with `and` (vault convention wins)
- Strip job-number prefixes (`2626_`, `EXA01A08 `, `P424-HCB-`)
- Strip semantic prefixes (`DWG_`, `BOQ_`, `RFI_`)
- Replace spaces with hyphens
- Strip revision suffixes (`_v-CC`, `_rev2`, `(1)`) for filename match
- Keep original suffix (file extension)

### 5. Classify

Compare canonical path sets:

- **SAME:** canonical path in both AND size_bytes match (or sha256 match in byte-content mode)
- **CHANGED:** canonical path in both BUT size_bytes differ (likely revision update)
- **NEW (in comparator):** canonical path only in comparator
- **ORPHAN (in vault):** canonical path only in vault

### 6. Recommend per-class actions

For each class, default recommendation:

| Class | Action | Rationale |
|---|---|---|
| **SAME** | leave-as-is | no change needed |
| **CHANGED** | review-then-update | comparator may have newer revision; Rocky decides |
| **NEW (in comparator)** | pull-into-vault | likely missing curation; pull + apply vault rename convention |
| **ORPHAN (in vault)** | leave-as-is | vault has files comparator doesn't (orchestrator-added artefacts, archived versions, _Internal stuff) |

Special-case ORPHAN:
- If orphan is in `_Internal/`, `_Archive/`, `_audit/`, or starts with `CLAUDE.md` / `_Log.md` / `Pre-Pricing-Grill-*.md` / `Priced-Take-Off-*.md` — these are EXPECTED orphans (orchestrator/worker outputs); flag as `expected-orphan`
- Otherwise → flag as `unexpected-orphan` for Rocky review

### 7. Write diff report

`_Internal/diffs/diff-{job_no}-vs-{comparator-label}-{date}.md`:

```markdown
---
job_no: {job_no}
vault_folder: {path}
comparator_path: {path}
comparator_label: {short label, e.g. "zip-2026-05-09"}
diff_mode: {structural | byte-content | metadata-only}
created: {iso-now}
authored_by: claude-code-orchestrator (lfo-job-folder-diff)
---

# Diff — Job {job_no} vs {comparator_label}

## Summary

| Class | Count | Recommended action |
|---|---|---|
| SAME | {N} | leave-as-is |
| CHANGED | {N} | review-then-update |
| NEW (in comparator) | {N} | pull-into-vault |
| ORPHAN (vault) — expected | {N} | leave-as-is (orchestrator-added) |
| ORPHAN (vault) — unexpected | {N} | Rocky review |

## NEW (in comparator, pull candidates)

(per-file: comparator path, canonical path, size, recommended-vault-rename)

## CHANGED

(per-file: vault path | comparator path | sizes | hash differences)

## ORPHAN (vault)

### Expected
(per-file: vault path, reason for expected orphan)

### Unexpected (Rocky review)
(per-file: vault path)

## SAME (top-20 by size)

(sample only; full list in JSON sidecar if requested)
```

### 8. Surface to Rocky

Use `AskUserQuestion` ONCE:
> "Diff complete. {NEW} new in comparator, {CHANGED} changed, {ORPHAN-unexpected} unexpected vault orphans. Open report to review or batch-pull NEW into vault now?"

Rocky decides per-class action.

### 9. Optional: execute pull

If Rocky says "pull NEW", apply vault naming convention to each NEW file + copy into vault destination. Append to session log.

## Failure modes

- **Comparator path is a Drive folder + gws CLI not authenticated:** surface re-auth requirement.
- **Canonical-transform produces collisions** (two source files map to same canonical): flag both; Rocky disambiguates.
- **Vault folder under active BCS:** STOP per CLAUDE.md §3.4.
- **Both folders empty:** trivial result; log + return.
- **Comparator is huge (>1GB):** confirm with Rocky before walking; suggest `mode=metadata-only` to skip hashing.

## Notes

- This skill is the GENERAL diff tool. BCS-specific comparison goes via `lfo-blind-driver` (sealed reasoning + REVEAL gate). Don't confuse the two.
- The naming-aware transform table is v1; expect to extend as new file naming patterns surface across HCs.
- Future enhancement: cross-bid pattern detection — "files named X always orphan in vault" might surface a missing skill or convention.
