sf-technical-debt-audit
Skill2 starsChanged 2 months agoMITView on GitHub
Standing technical-debt audit of a Salesforce project and org - inventory the metadata surface, measure API version drift, test and coverage debt, trigger and automation sprawl, retired Workflow Rules and Process Builder still running, hardcoded ids and endpoints, profile-based permissions, dead fields and classes, Apex volume against the 6 MB org limit, and package and dependency debt; then rank findings by impact and effort into a debt register with an owner and a remediation skill per item. Use when taking over an unfamiliar org, planning a refactor or a release-hardening sprint, answering "why is every change here slow", or producing a debt report for an architecture review. Not for reviewing a diff - that is skill sf-code-analyzer-quality and the sf-quality-gate agent.
In this repository34 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- sf-agentforce-developmentSkill
- sf-apex-developmentSkill
- sf-apex-testingSkill
- sf-async-apex-patternsSkill
- sf-cli-operationsSkill
- sf-code-analyzer-qualitySkill
- sf-data-cloudSkill
- sf-data-managementSkill
- sf-debugging-logsSkill
- sf-deployment-strategiesSkill
- sf-fflib-domain-service-uowSkill
- sf-fflib-foundationsSkill
- sf-fflib-operationsSkill
- sf-fflib-selector-layerSkill
- sf-fflib-testingSkill
- sf-flow-automationSkill
- sf-governor-limitsSkill
- sf-integration-patternsSkill
- sf-local-developmentSkill
- sf-lwc-developmentSkill
- sf-lwc-jest-testingSkill
- sf-minimal-changeSkill
- sf-org-security-auditSkill
- sf-packaging-releaseSkill
- sf-post-deploy-verificationSkill
- sf-process-builder-migrationSkill
- sf-project-structureSkill
- sf-scratch-orgs-sandboxesSkill
- sf-security-modelSkill
- sf-setup-automationSkill
- sf-soql-sosl-optimizationSkill
- sf-ui-test-automationSkill
- sf-workflow-orchestrationSkill
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 123
Every hit is an environment coupling: it works in the org it was written in and breaks everywhere else.
- Very specific: line 220
Write it to .vibeforce/reports/debt-<ISO>.md so the next audit can diff against it.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Salesforce Technical Debt Audit
When to use
| Situation | Use this skill |
| Taking over an org or codebase you did not build | Yes |
| Planning a hardening sprint and needing a ranked debt register | Yes |
| Explaining why small changes take days in this org | Yes |
| Reviewing the diff of the change you just made | Skill sf-code-analyzer-quality, agent sf-quality-gate |
| Security posture of the org | Skill sf-org-security-audit |
| Deciding how small a specific change should be | Skill sf-minimal-change |
| A specific governor limit being hit at runtime | Skill sf-governor-limits |
| Deployment failures and release mechanics | Skill sf-deployment-strategies |
An audit is read-only. It runs against source plus a connected org, never writes, and is safe to point at production - see Verification for the production posture.
What counts as debt here
Debt is anything that makes the next change more expensive than it should be. Six categories, each with a measurable signal:
| Category | Signal you can measure | Why it costs | Remediation skill |
| Version drift | <apiVersion> spread across metadata; sourceApiVersion in sfdx-project.json | Behaviour differs per class; upgrades become archaeology | sf-project-structure |
| Test debt | Classes without a test, coverage below the gate, SeeAllData=true, assertion-free tests | No safety net, so every change is manual QA | sf-apex-testing, sf-lwc-jest-testing |
| Automation sprawl | Triggers per object, Workflow Rules and Process Builder still active beside Flows | Order of execution becomes unknowable | sf-flow-automation, sf-process-builder-migration, sf-apex-development |
| Structural debt | Hardcoded ids and URLs, profiles instead of permission sets, no layering, duplicated selectors | Every environment needs manual fixes | sf-minimal-change, sf-fflib-foundations |
| Dead weight | Fields, objects, classes and permission sets nothing references; Apex volume against the org limit | Search noise, deploy time, limit headroom | sf-minimal-change |
| Delivery debt | Untracked manual org changes, no package boundaries, no CI gate | Releases are irreproducible | sf-deployment-strategies, sf-packaging-release |
Core patterns
1. Fix the scope before measuring anything
sf org display --target-org vf-int
sf org list limits --target-org vf-int
sf org list metadata-types --target-org vf-int --output-file .vibeforce/reports/metadata-types.jsonRecord: org alias and type, API version, the package directories from sfdx-project.json, and
whether the source you hold is the whole org or one package. An audit of a partial source tree that
claims org-wide coverage is worse than no audit.
2. API version drift, measured from source
# every declared apiVersion, most common first
grep -rho "<apiVersion>[0-9.]*</apiVersion>" force-app | sort | uniq -c | sort -rn
# the files furthest behind the project baseline
grep -rl "<apiVersion>4[0-9]\." force-app --include="*-meta.xml" | head -50The baseline is sourceApiVersion in sfdx-project.json and apiVersion in
config/vibe-force.defaults.json (67.0). The post-edit hook already flags a mismatch on files you
touch (scripts/lib/xml-lint.js); the audit is the org-wide version of the same question.
Org-side equivalent, when the source is not the whole truth:
sf data query --use-tooling-api --target-org vf-int \
--query "SELECT Name, ApiVersion FROM ApexClass WHERE NamespacePrefix = null ORDER BY ApiVersion"[unverified: the ApexClass.ApiVersion field name was not confirmed against the Tooling API
reference in this session - run sf sobject describe --sobject ApexClass --use-tooling-api first]
3. Test debt, in the two places it hides
# structural: which classes and bundles have no test at all
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" pairing --json
# behavioural: coverage per class, from a real run
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" apex --target-org vf-int --json
# tests that read org data instead of creating it
grep -rn "SeeAllData\s*=\s*true" force-app --include="*.cls"
# tests with no assertion at all - one file per iteration, so an empty list is simply an empty loop
grep -rli "@isTest" force-app --include="*.cls" | while read -r f; do
grep -q "Assert\.\|System.assert" "$f" || echo "no assertion: $f"
doneCoverage above the gate with assertion-free tests is not coverage; report both numbers.
4. Automation sprawl per object
# triggers per object, from source
grep -rho "trigger [A-Za-z0-9_]* on [A-Za-z0-9_]*" force-app --include="*.trigger" \
| awk '{print $4}' | sort | uniq -c | sort -rn
# what still runs beside your Flows
sf data query --use-tooling-api --target-org vf-int \
--query "SELECT MasterLabel, ProcessType, TriggerType, Status FROM FlowDefinitionView WHERE Status = 'Active'"Two triggers on one object is a finding on its own: the platform does not define their order.
Workflow Rules and Process Builder lost support on 31 December 2025 - an active one beside a
record-triggered Flow on the same object is the single most common source of "it depends" bugs
(skills sf-flow-automation for workflow rules, sf-process-builder-migration for processes).
5. Hardcoded ids, endpoints and environment assumptions
# 15 and 18 character ids in code
grep -rnE "'[a-zA-Z0-9]{15}'|'[a-zA-Z0-9]{18}'" force-app --include="*.cls" --include="*.js"
# endpoints that should be Named Credentials
grep -rnE "https?://" force-app --include="*.cls" | grep -v "schema.org\|w3.org"Every hit is an environment coupling: it works in the org it was written in and breaks everywhere
else. Fixes are Custom Metadata, Custom Settings, Schema.SObjectType describes, and Named
Credentials (skill sf-integration-patterns). Turn the recurring ones into a Code Analyzer regex
rule so the audit does not have to find them twice (skill sf-code-analyzer-quality, pattern 5).
6. Permission debt
ls force-app/main/default/profiles/ 2>/dev/null | wc -l
ls force-app/main/default/permissionsets/ 2>/dev/null | wc -l
sf data query --target-org vf-int \
--query "SELECT Assignee.Username FROM PermissionSetAssignment WHERE PermissionSet.PermissionsModifyAllData = true"Profiles carrying feature access instead of permission sets is debt: profiles cannot be composed,
and a profile diff is unreviewable. The deep version of this audit - object matrices, dangerous
permissions, guest access - is skill sf-org-security-audit.
7. Dead weight and limit headroom
# Apex volume against the org limit
find force-app -name "*.cls" -not -name "*Test.cls" -exec cat {} + | wc -c
# classes nothing references - skip tests and every entry point the platform calls for you
for f in force-app/main/default/classes/*.cls; do
n=$(basename "$f" .cls)
grep -qiE "@isTest|@AuraEnabled|@RestResource|@InvocableMethod|webService|implements +(Schedulable|Batchable|Queueable|Database\.)" "$f" && continue
refs=$(grep -rl "\b$n\b" force-app --include="*.cls" --include="*.js" --include="*.xml" | grep -cv "/$n\.")
[ "$refs" -eq 0 ] && echo "unreferenced: $n"
done
# custom fields on an object, to compare against what the UI and code actually use
sf data query --use-tooling-api --target-org vf-int \
--query "SELECT QualifiedApiName, DataType FROM FieldDefinition WHERE EntityDefinition.QualifiedApiName = 'Account'"Limits that matter for this category: 1 million characters per class or trigger, 6 MB of Apex per
org (10 MB in scratch orgs, raisable by support case), 7,500 code units per deployment - all from
skill sf-governor-limits. Report headroom as a percentage, not a byte count.
8. Delivery debt
sf project deploy preview --target-org vf-int # what differs between source and org
sf project retrieve preview --target-org vf-int # what was changed in the org by handAnything that shows up only in retrieve preview was changed in Setup and exists in no branch.
Count those components: they are the debt that makes every deployment a negotiation.
9. Rank, do not list
An audit that returns 300 findings changes nothing. Rank each finding on two axes and report the top of the list:
| Impact | Meaning |
| 3 | Blocks or breaks releases, or risks data loss |
| 2 | Slows every change in one area |
| 1 | Local annoyance |
| Effort | Meaning |
| 1 | One PR, no behaviour change |
| 2 | One sprint, needs tests first |
| 3 | Needs a migration plan and a rollback path |
Order by impact descending, then effort ascending. The first five items are the report; the rest is an appendix.
10. The debt register
## Debt register - <org alias> - <ISO date>
| # | Category | Finding | Evidence | Impact | Effort | Owner | Remediation skill |
| --- | --- | --- | --- | --- | --- | --- | --- |
## Measured baseline
| Metric | Value | Gate or limit |
| --- | --- | --- |
| Apex classes / triggers | | |
| Org Apex volume | | 6 MB |
| Apex coverage (org) | | gates.apexOrgCoverageMin |
| Classes with no test | | 0 |
| LWC bundles with no spec | | 0 |
| Active Workflow Rules / Process Builders | | 0 for new automation |
| Objects with more than one trigger | | 0 |
| Profiles / permission sets in source | | |
| Components changed in the org only | | 0 |
## Not measured
<what the audit could not see, and why>Write it to .vibeforce/reports/debt-<ISO>.md so the next audit can diff against it. A debt
register without a baseline cannot show progress, and progress is the only reason to run it twice.
Anti-patterns
| Anti-pattern | Consequence | Fix |
| Auditing source and calling it an org audit | Org-only changes stay invisible | Run sf project retrieve preview and say what you could not see |
| Counting findings instead of ranking them | The report is ignored | Impact x effort, top five first |
| "Refactor everything to fflib" as a finding | Rewrite proposal, not a debt item | Name the cost of the current shape in changes per week |
| Treating coverage percentage as test quality | Assertion-free tests pass the gate | Report coverage and assertion density together |
| Deleting a field because nothing in source references it | Reports, list views and Flows reference fields too | Check FieldDefinition, reports and Flow metadata before proposing deletion |
| Running the audit against production with a mutating check | Guard trips, or worse, it does not | Only the read-only checks; see Verification |
| Filing debt with no owner | Nothing moves | Every register row names a person and a remediation skill |
Verification
# the local gates, on the whole tree rather than the diff
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" static --json
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" pairing --json
# org-side, read-only
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" apex --target-org vf-int --json
sf org list limits --target-org vf-int
sf project deploy preview --target-org vf-intProduction posture: the queries and previews in this skill are read-only and legitimate against a
production alias, and vf-check prints a production banner rather than refusing for the read-only
family. vf-check apex runs tests in the org and is not part of a production audit - take its
numbers from the last release pipeline instead. Never set VF_ALLOW_PROD=1 to force a mutating
check through as part of an audit.
Exit codes are the runner contract: 0 pass, 1 gate failed, 2 misconfiguration or missing
tool, 3 org or network error.
References
references/debt-signals.md- the full signal catalogue: detection command, threshold, remediation and owning skill per signal.- Related skills:
sf-org-security-audit(security posture),sf-code-analyzer-quality(static analysis and custom rules),sf-minimal-change(how small a change should be),sf-governor-limits(the limit tables cited here),sf-flow-automation(automation consolidation),sf-apex-testingandsf-lwc-jest-testing(test debt),sf-deployment-strategiesandsf-packaging-release(delivery debt),sf-project-structure(API version baseline). - Official: Salesforce CLI Command Reference, org list metadata, Apex Governor Limits, Migrate to Flow.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

