v0.2.5 — field-audit surface + delete-safety edge fixes + refresh data-loss fixes
Closes five ways sfi.safe_to_delete_field could report a field safe when the platform refuses to delete it, and fixes four pre-existing refresh bugs found along the way — one of which deleted the vault it was refreshing and reported success.
Delete-safety gaps closed
Every one sat inside a metadata family the refresh had fully retrieved, so no coverageCaveat fired and the verdict presented as clean rather than hedged. A tool can only warn about the gaps it knows it has.
| Gap | Symptom |
|---|---|
| Flow record-trigger entry criteria | Flow ships two XML spellings of one condition triplet; only <leftValueReference> was parsed. On the reference org: 449 filters across 160 of 275 flows, 100% in the unparsed <field> dialect |
| Roll-up coupling | Held as a node property, never an edge — declared on the parent, so a child-field walk never reached it |
| Condition field refs | Held as a property; firesWhen runs firer → context, so nothing reached the field |
Formula __r traversals |
Skipped — a field read only via Parent__r.Field__c showed zero referrers |
FlexiPage relatedListFieldAliases |
Bare names on the related object, invisible to the dotted sweep |
85 fields on the reference org had zero dependency evidence before this release.
Citation honesty
A correct verdict with invented evidence is still a defect. Fixed: a roll-up summary cited on 127 fields where none exists; a traversalPath promised in a note that no renderer emitted; two of three roll-up roles described; 4 of 7 condition firer families named; and one validation rule counted as two blockers in two categories. Every citation now names the component to actually go change.
Refresh bugs (all pre-existing)
reconcileSourceDeletionsdeleted the vault it was refreshing. It compared raw paths across two layouts, so every in-scope file read as "deleted in the org" — 974 nodes,status: success, exit 0. Reproduced by executing the shipped code: 8 of 8 files deleted. Now layout-agnostic, with a wholesale-deletion guard that refuses and explains.- The additive retrieve had never worked. It named an
--output-dirmodernsfrejects. Both explicit targets are refused; the accepted form names none. - A failed report pull was swallowed, leaving a vault byte-identical to a successful one. Now recorded on the manifest and printed.
- Report coverage asserted a confirmed zero it could never contradict —
retrievedwas counted after the usage fold drops those nodes, yetretrieveConfirmed: truewas stamped anyway, about an org with 4,296 reports.
⚠️ Behaviour change
safe is harder to reach on a coverage-degraded vault. Four more condition-firer families are attested, and a pre-0.2.5 vault routes safe → review until re-refreshed. Deliberate: an unretrieved family can hide a condition blocker. Re-run sfi refresh after upgrading.
New surface
salesforce-field-audit (26th skill) · salesforce-field-auditor + salesforce-field-refuter (the plugin's first subagents) · /sfi-field-audit · sfi.field_audit MCP prompt · field_360.rollups
209 tools · 26 skills · 5 slash commands · 2 subagents
Verification
9,895 tests, lint, all gates, CI on Node 20/22 + macOS. Real-org end-to-end through the MCP server: each fixed path returns blocking with a checkable citation, and a field with no dependencies still returns review — not blocking — so the tool still discriminates. All three refresh paths reconcile at identical counts.
Known limits
windows-build-test is skipped by repo config. Report coverage is honestly partial (1,959 of 4,296 retrieved, reported pending). The manifest edge tally is render-derived and under-counts; documented, not fixed.
📖 Can I delete this Salesforce field? — the method behind the release.