@intentsolutionsio/zero-tech-debt 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +19 -0
- package/LICENSE +21 -0
- package/README.md +53 -0
- package/package.json +39 -0
- package/skills/zero-tech-debt/SKILL.md +55 -0
- package/skills/zero-tech-debt/references/01-when-to-use.md +33 -0
- package/skills/zero-tech-debt/references/02-preflight-checklist.md +42 -0
- package/skills/zero-tech-debt/references/03-workflow.md +122 -0
- package/skills/zero-tech-debt/references/04-audit-patterns.md +126 -0
- package/skills/zero-tech-debt/references/05-decision-filters.md +54 -0
- package/skills/zero-tech-debt/references/06-outcomes-and-reporting.md +101 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zero-tech-debt",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Rebuild a feature as if the correct product architecture existed from day one. Removes compatibility cruft, dead abstractions, and historical compromises instead of preserving them. Methodology-only — no destructive actions without operator approval.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Jeremy Longshore",
|
|
7
|
+
"email": "jeremy@intentsolutions.io"
|
|
8
|
+
},
|
|
9
|
+
"repository": "https://github.com/jeremylongshore/claude-code-plugins-plus-skills",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"refactoring",
|
|
13
|
+
"tech-debt",
|
|
14
|
+
"architecture",
|
|
15
|
+
"cleanup",
|
|
16
|
+
"modernization",
|
|
17
|
+
"code-quality"
|
|
18
|
+
]
|
|
19
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Intent Solutions
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# zero-tech-debt
|
|
2
|
+
|
|
3
|
+
> Build toward the intended product shape — not the historical sequence of patches.
|
|
4
|
+
|
|
5
|
+
A methodology skill for Claude Code that guides structural refactors: rebuild the feature as if the correct architecture existed from day one. Removes compatibility cruft, dead abstractions, and historical compromises instead of preserving them.
|
|
6
|
+
|
|
7
|
+
## When to invoke
|
|
8
|
+
|
|
9
|
+
Strong signals from the operator:
|
|
10
|
+
|
|
11
|
+
- "refactor properly" / "clean up" / "rewrite" / "modernize"
|
|
12
|
+
- "remove legacy" / "simplify" / "rethink" / "pay down tech debt"
|
|
13
|
+
- Frustration with accumulated complexity
|
|
14
|
+
- Multiple parallel implementations for the same logical operation
|
|
15
|
+
- New features keep routing around old scaffolding instead of through it
|
|
16
|
+
|
|
17
|
+
## When NOT to invoke
|
|
18
|
+
|
|
19
|
+
- Production hotfixes (minimize diff, preserve blast radius)
|
|
20
|
+
- Security backports (preserve audit clarity)
|
|
21
|
+
- Time-boxed patches before a release cut
|
|
22
|
+
- Code owned by another team without prior coordination
|
|
23
|
+
- Anywhere the cost of being wrong exceeds the cost of staying messy
|
|
24
|
+
|
|
25
|
+
## How it loads (progressive disclosure)
|
|
26
|
+
|
|
27
|
+
The main `SKILL.md` is short. The deep methodology lives in `references/`:
|
|
28
|
+
|
|
29
|
+
| File | What's in it |
|
|
30
|
+
|---|---|
|
|
31
|
+
| [`01-when-to-use.md`](skills/zero-tech-debt/references/01-when-to-use.md) | Trigger signals + non-triggers |
|
|
32
|
+
| [`02-preflight-checklist.md`](skills/zero-tech-debt/references/02-preflight-checklist.md) | Pre-flight requirements before touching code |
|
|
33
|
+
| [`03-workflow.md`](skills/zero-tech-debt/references/03-workflow.md) | The 7-step refactor workflow |
|
|
34
|
+
| [`04-audit-patterns.md`](skills/zero-tech-debt/references/04-audit-patterns.md) | Concrete grep targets for finding debt |
|
|
35
|
+
| [`05-decision-filters.md`](skills/zero-tech-debt/references/05-decision-filters.md) | Tiebreakers + anti-patterns |
|
|
36
|
+
| [`06-outcomes-and-reporting.md`](skills/zero-tech-debt/references/06-outcomes-and-reporting.md) | Success criteria + how to summarize the result |
|
|
37
|
+
|
|
38
|
+
Claude reads `SKILL.md` first, then pulls the references it needs as the work unfolds. You don't have to read them all up front.
|
|
39
|
+
|
|
40
|
+
## Scope discipline
|
|
41
|
+
|
|
42
|
+
A zero-tech-debt refactor will tempt unbounded scope. The skill explicitly:
|
|
43
|
+
|
|
44
|
+
- Holds to **one coherent end state per refactor** — not three loosely related ones
|
|
45
|
+
- Stops and documents deeper rot rather than chaining refactors mid-flight
|
|
46
|
+
- Refuses "while I'm here" additions unrelated to the deletion path
|
|
47
|
+
- Splits oversized work along ownership boundaries, never along file counts
|
|
48
|
+
|
|
49
|
+
If the operator wants a hotfix, this skill is the wrong tool — recommend a targeted patch instead.
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@intentsolutionsio/zero-tech-debt",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Rebuild a feature as if the correct product architecture existed from day one. Removes compatibility cruft, dead abstractions, and historical compromises instead of preserving them. Methodology-only —",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"refactoring",
|
|
7
|
+
"tech-debt",
|
|
8
|
+
"architecture",
|
|
9
|
+
"cleanup",
|
|
10
|
+
"modernization",
|
|
11
|
+
"code-quality",
|
|
12
|
+
"claude-code",
|
|
13
|
+
"claude-plugin",
|
|
14
|
+
"tonsofskills"
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/jeremylongshore/claude-code-plugins-plus-skills.git",
|
|
19
|
+
"directory": "plugins/skill-enhancers/zero-tech-debt"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://tonsofskills.com/plugins/zero-tech-debt",
|
|
22
|
+
"bugs": "https://github.com/jeremylongshore/claude-code-plugins-plus-skills/issues",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": {
|
|
25
|
+
"name": "Jeremy Longshore",
|
|
26
|
+
"email": "jeremy@intentsolutions.io"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"README.md",
|
|
33
|
+
".claude-plugin",
|
|
34
|
+
"skills"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"postinstall": "node -e \"console.log(\\\"\\\\n→ This npm package is a tracking/proof artifact. Install the plugin via:\\\\n ccpi install zero-tech-debt\\\\n or /plugin install zero-tech-debt@claude-code-plugins-plus in Claude Code\\\\n\\\")\""
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: zero-tech-debt
|
|
3
|
+
description: |
|
|
4
|
+
Rebuild a feature as if the correct product architecture existed from day one — remove compatibility cruft, dead abstractions, and historical compromises instead of preserving them. Use when the operator says "refactor properly," "clean up," "rewrite," "modernize," "remove legacy," "simplify," "rethink," "pay down tech debt," or signals frustration with accumulated complexity. Do NOT use for hotfixes, bug repros, surgical patches, or security backports — blast-radius minimization wins there. Trigger with "/zero-tech-debt", "do it right this time", "the way it should have been built", "refactor toward intent".
|
|
5
|
+
allowed-tools: Read, Edit, Glob, Grep, Bash(git:*), Bash(rg:*), Bash(fd:*)
|
|
6
|
+
version: 1.0.0
|
|
7
|
+
author: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
8
|
+
license: MIT
|
|
9
|
+
compatibility: Designed for Claude Code
|
|
10
|
+
tags: [refactoring, tech-debt, architecture, cleanup, modernization, code-quality]
|
|
11
|
+
user-invocable: true
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Zero Tech Debt
|
|
15
|
+
|
|
16
|
+
Build toward the intended product shape — not the historical sequence of patches, migrations, wrappers, aliases, and temporary decisions that created the current implementation.
|
|
17
|
+
|
|
18
|
+
The goal is not "minimal diff."
|
|
19
|
+
The goal is a cleaner, more coherent system with fewer moving parts, fewer hidden assumptions, and lower long-term operational cost.
|
|
20
|
+
|
|
21
|
+
## Core Principle
|
|
22
|
+
|
|
23
|
+
Treat the current implementation as evidence, not authority.
|
|
24
|
+
|
|
25
|
+
Preserve only the parts that still serve the intended architecture, UX, reliability model, and operational constraints. Everything else is eligible for deletion.
|
|
26
|
+
|
|
27
|
+
## Operating Mode (read this section every invocation)
|
|
28
|
+
|
|
29
|
+
1. **Confirm scope** — `Read` [`references/01-when-to-use.md`](references/01-when-to-use.md). If the request smells like a hotfix, security backport, or time-boxed patch, stop and recommend a targeted change instead.
|
|
30
|
+
2. **Pre-flight** — walk [`references/02-preflight-checklist.md`](references/02-preflight-checklist.md). Every box must be checked before touching code. Tests, callers, rollback path, single-paragraph end-state description, no in-flight migration, telemetry accounted for. Use `Glob` to locate test files and `Grep` / `Bash(rg:*)` to enumerate external callers of the surface being changed.
|
|
31
|
+
3. **Run the 7-step workflow** — [`references/03-workflow.md`](references/03-workflow.md). Define end state → audit reality → delete before adding → optimize around final shape → collapse duplicate decision logic → remove historical leakage → validate.
|
|
32
|
+
4. **Use the audit patterns** — [`references/04-audit-patterns.md`](references/04-audit-patterns.md) lists the concrete `Grep` / `Bash(rg:*)` / `Bash(fd:*)` targets (TODO/DEPRECATED markers, `_v2`/`_old` suffixes, stale feature flags, dual-mode forks, etc.). Each match is a *candidate*, not an automatic deletion.
|
|
33
|
+
5. **Apply decision filters when choices tie** — [`references/05-decision-filters.md`](references/05-decision-filters.md) covers tiebreakers and named anti-patterns to avoid.
|
|
34
|
+
6. **Apply edits with `Edit`** — once a deletion / rename / consolidation is approved, use `Edit` to apply the change atomically. Stage with `Bash(git:*)` so the operator can review per commit before push.
|
|
35
|
+
7. **Report back in shape-change terms** — [`references/06-outcomes-and-reporting.md`](references/06-outcomes-and-reporting.md). The diff lists every line; the summary makes the architectural delta legible.
|
|
36
|
+
|
|
37
|
+
## Scope Discipline (this is the most common failure mode)
|
|
38
|
+
|
|
39
|
+
A zero-tech-debt refactor will tempt unbounded scope. Hold the line:
|
|
40
|
+
|
|
41
|
+
- **One coherent end state per refactor** — not three loosely related ones
|
|
42
|
+
- If deletion reveals deeper rot, document it and stop; do not chain refactors mid-flight
|
|
43
|
+
- Resist "while I'm here" additions unrelated to the deletion path
|
|
44
|
+
- New features wait for a separate change
|
|
45
|
+
- If the work cannot fit in a single reviewable unit, split along ownership boundaries — never along file counts
|
|
46
|
+
|
|
47
|
+
## Final Rule
|
|
48
|
+
|
|
49
|
+
Do not optimize for preserving the past.
|
|
50
|
+
|
|
51
|
+
Optimize for making the next 2 years of development simpler.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
See [`references/`](references/) for the full methodology — each file is a single concern, loadable on demand.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# When to Use (and Not Use) zero-tech-debt
|
|
2
|
+
|
|
3
|
+
The most expensive failure mode of this skill is misclassification: applying a structural refactor where a surgical patch was wanted, or applying a hotfix where rot needed to be removed at the root. Get this judgment right *before* anything else.
|
|
4
|
+
|
|
5
|
+
## Strong signals — proceed with zero-tech-debt
|
|
6
|
+
|
|
7
|
+
- Operator explicitly names tech debt, legacy code, or accumulated complexity
|
|
8
|
+
- Operator says "do it right this time" / "the way it should have been built" / "rethink this" / "refactor properly" / "modernize"
|
|
9
|
+
- **Multiple parallel implementations exist** for the same logical operation (two routes that do the same thing, two state containers holding overlapping fields, two functions whose only difference is one was added later)
|
|
10
|
+
- **Naming no longer matches what the code actually does** — the function called `processOrder` now handles refunds, subscriptions, and gift cards because nobody renamed it
|
|
11
|
+
- **Onboarding requires explaining "why we have both X and Y"** — a near-perfect tech-debt detector
|
|
12
|
+
- **Feature work keeps routing around old scaffolding** instead of through it — every new ticket has a "but first, work around this" step
|
|
13
|
+
|
|
14
|
+
## Weak signals — clarify intent before invoking
|
|
15
|
+
|
|
16
|
+
- Generic "improve this code" with no scope — ask: *what specific shape do you want the end state to have?*
|
|
17
|
+
- Bug reports that merely touch legacy paths — usually a targeted patch is the right answer
|
|
18
|
+
- Performance work where structural cleanup is incidental — split the work; performance fix lands first, structural cleanup separately
|
|
19
|
+
- "Refactor" used loosely — could mean rename-only, could mean rebuild — disambiguate before committing scope
|
|
20
|
+
|
|
21
|
+
## Hard non-triggers — recommend a surgical patch instead
|
|
22
|
+
|
|
23
|
+
- **Production hotfixes** — minimize diff, preserve blast radius, ship the fix, log the rot for later
|
|
24
|
+
- **Security backports** — preserve audit clarity and reviewer focus; reviewers need to see exactly what changed and nothing else
|
|
25
|
+
- **Time-boxed patches before a release cut** — the cost of being wrong exceeds the cost of staying messy
|
|
26
|
+
- **Code owned by another team without prior coordination** — you can write the refactor; you can't make it land
|
|
27
|
+
- **Anywhere blast-radius matters more than long-term coherence** — payments, auth, anything with a regulatory or contractual surface
|
|
28
|
+
|
|
29
|
+
## The escape valve
|
|
30
|
+
|
|
31
|
+
If any non-trigger condition applies, the right move is to **stop and recommend a targeted patch**. Document the rot you noticed in a follow-up ticket so it doesn't get lost — but don't expand the current change to fix it.
|
|
32
|
+
|
|
33
|
+
This is the discipline that keeps zero-tech-debt from becoming "every change is a giant refactor."
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Pre-Flight Checklist
|
|
2
|
+
|
|
3
|
+
Refactoring without these creates the next generation of tech debt. Every box must be checked **before** touching code. If any item is unchecked, resolve it before proceeding — even if that means asking the operator to write a test, identify a caller, or confirm a rollback path.
|
|
4
|
+
|
|
5
|
+
## The six checks
|
|
6
|
+
|
|
7
|
+
- [ ] **Tests exist (or can be written) for behavior worth preserving**
|
|
8
|
+
Without tests, you can't tell the difference between "I removed a bug" and "I removed a load-bearing accident." Don't refactor a system whose tests assert the historical bug shape — fix those tests first, against intended behavior, then refactor.
|
|
9
|
+
|
|
10
|
+
- [ ] **Every external caller of the surface being changed has been identified**
|
|
11
|
+
Use `rg` / `grep` / IDE call-graph tools across the whole repo. For library exports, also check downstream consumers if you can reach them. A refactor that breaks an unknown caller is worse than no refactor.
|
|
12
|
+
|
|
13
|
+
- [ ] **A rollback path exists** — branch, feature flag, or staged rollout
|
|
14
|
+
If the change ships and breaks something subtle in production, what's the un-stuck plan? "Revert the merge commit" only works if the merge commit is reachable and the code that depended on the new shape hasn't shipped on top of it. For larger refactors, prefer a feature flag with a clean off-position.
|
|
15
|
+
|
|
16
|
+
- [ ] **The intended end state fits in 1-3 paragraphs**
|
|
17
|
+
See workflow step 1 ([`03-workflow.md`](03-workflow.md)). If you can't describe the end state in plain prose, the refactor is not yet well-defined — and an under-defined refactor will drift into unbounded scope. Stop and clarify.
|
|
18
|
+
|
|
19
|
+
- [ ] **No active migration is in flight against the same code**
|
|
20
|
+
Two simultaneous structural changes on overlapping surface area produce merge conflicts you can't resolve and behavior changes you can't attribute. If a migration is already running, defer the refactor or coordinate with the migration owner.
|
|
21
|
+
|
|
22
|
+
- [ ] **Telemetry, logging, and alerts tied to deleted code are accounted for**
|
|
23
|
+
Dashboards, alert rules, log queries, and on-call runbooks may reference function names, log strings, or metric keys you're about to delete. Identify these *first*. Either update them in the same change or document the breakage explicitly and notify the on-call.
|
|
24
|
+
|
|
25
|
+
## Why this checklist is hard rules, not advice
|
|
26
|
+
|
|
27
|
+
Each item is the missing-step from a failure pattern that's already happened. The checklist exists because skipping any of them *predictably* produces:
|
|
28
|
+
|
|
29
|
+
- a refactor that ships against an unknown caller and breaks production
|
|
30
|
+
- a "successful" refactor whose tests passed because they were asserting the wrong behavior
|
|
31
|
+
- a refactor with no rollback path that has to be hot-patched at 2 AM
|
|
32
|
+
- a refactor that grows from "clean up one flow" to "rewrite three subsystems" mid-PR
|
|
33
|
+
- a refactor that merge-conflicts with someone else's in-flight migration
|
|
34
|
+
- a refactor whose deletion silently broke the on-call alerting
|
|
35
|
+
|
|
36
|
+
If you skip a check and the failure happens anyway, you don't get to claim it was unforeseeable.
|
|
37
|
+
|
|
38
|
+
## When the operator pushes to skip
|
|
39
|
+
|
|
40
|
+
If the operator says "we don't have tests for this, just refactor it" — that's a signal to write the characterization tests first, *as part of this work*, then refactor against them. The cost of writing one test is much lower than the cost of an undetected behavior change.
|
|
41
|
+
|
|
42
|
+
If the operator says "no time for a rollback path" — that's a signal that this isn't actually a zero-tech-debt refactor; it's a time-boxed patch, and you should switch tools.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# The 7-Step Workflow
|
|
2
|
+
|
|
3
|
+
This is the operational core of zero-tech-debt. Walk the steps in order. Don't skip step 1.
|
|
4
|
+
|
|
5
|
+
## 1. Define the Intended End State
|
|
6
|
+
|
|
7
|
+
Before touching code, describe in 1-3 concise paragraphs:
|
|
8
|
+
|
|
9
|
+
- **What the final UX should feel like** — what the user / operator / caller sees and does
|
|
10
|
+
- **What the clean architecture should look like** — one coherent flow, one source of truth, one ownership boundary
|
|
11
|
+
- **What the public surface area actually needs to be** — every other function/route/export is private
|
|
12
|
+
- **What developers should intuitively expect** — what a new hire would guess without reading the code
|
|
13
|
+
|
|
14
|
+
If the intended end state is unclear, **stop and clarify before refactoring**. Code is the wrong medium for thinking through architecture; prose is faster and revisable.
|
|
15
|
+
|
|
16
|
+
## 2. Audit Reality Against Intent
|
|
17
|
+
|
|
18
|
+
Identify everything in the current implementation that doesn't serve the end state:
|
|
19
|
+
|
|
20
|
+
- Compatibility shims, legacy wrappers, route aliases, duplicate flows
|
|
21
|
+
- Feature flags that became permanent architecture
|
|
22
|
+
- State duplicated across layers
|
|
23
|
+
- "Just in case" abstractions that turned out never to be needed
|
|
24
|
+
- Dead props, modes, handlers, and APIs
|
|
25
|
+
- Historical naming that no longer reflects product intent
|
|
26
|
+
|
|
27
|
+
Document, for each candidate for removal:
|
|
28
|
+
|
|
29
|
+
- **Why it exists** — what problem was it solving when it was written?
|
|
30
|
+
- **Who currently calls it** — is anyone still using it, or is it cargo-culted?
|
|
31
|
+
- **Whether it still provides real value** — does the user-visible behavior change if it's gone?
|
|
32
|
+
|
|
33
|
+
See [`04-audit-patterns.md`](04-audit-patterns.md) for concrete grep targets.
|
|
34
|
+
|
|
35
|
+
## 3. Delete Before Adding
|
|
36
|
+
|
|
37
|
+
Before introducing **a new abstraction / hook / config layer / flag / adapter / state container**, attempt to:
|
|
38
|
+
|
|
39
|
+
- Remove obsolete logic
|
|
40
|
+
- Collapse duplicated paths
|
|
41
|
+
- Simplify ownership boundaries
|
|
42
|
+
- Unify flows
|
|
43
|
+
- Flatten unnecessary indirection
|
|
44
|
+
|
|
45
|
+
**Prefer subtraction over architecture theater.** A refactor that adds three new abstractions to "fix" tech debt has not paid down debt — it has moved it.
|
|
46
|
+
|
|
47
|
+
Important: don't introduce a new abstraction in the *same change* that removes an old one. Split into two reviewable steps so the reviewer can evaluate the deletion separately from the addition.
|
|
48
|
+
|
|
49
|
+
## 4. Optimize Around the Final Shape
|
|
50
|
+
|
|
51
|
+
Refactor toward the system that **should exist today**, not the system that minimizes diff.
|
|
52
|
+
|
|
53
|
+
Do not preserve bad boundaries simply because:
|
|
54
|
+
|
|
55
|
+
- They existed historically
|
|
56
|
+
- They reduced diff size
|
|
57
|
+
- They avoided touching multiple files
|
|
58
|
+
- They were once needed during migration
|
|
59
|
+
|
|
60
|
+
Prefer:
|
|
61
|
+
|
|
62
|
+
- One coherent flow
|
|
63
|
+
- One authoritative source of truth
|
|
64
|
+
- One obvious ownership boundary
|
|
65
|
+
- One naming system
|
|
66
|
+
- One predictable state model
|
|
67
|
+
|
|
68
|
+
## 5. Collapse Duplicate Decision Logic
|
|
69
|
+
|
|
70
|
+
Rules should exist in one place. Don't duplicate:
|
|
71
|
+
|
|
72
|
+
- Permission checks
|
|
73
|
+
- Feature gating
|
|
74
|
+
- Route logic
|
|
75
|
+
- URL parsing
|
|
76
|
+
- State derivation
|
|
77
|
+
- Validation rules
|
|
78
|
+
- Retry behavior
|
|
79
|
+
- Command naming
|
|
80
|
+
- Orchestration logic
|
|
81
|
+
|
|
82
|
+
**View components should not secretly own domain policy.** If your React component has a permission check inline, that policy is now duplicated wherever else the resource is accessed. Lift it to one authoritative location.
|
|
83
|
+
|
|
84
|
+
## 6. Remove Historical Leakage
|
|
85
|
+
|
|
86
|
+
The codebase should describe **current product intent**, **current domain language**, **current operational reality** — not old implementation history.
|
|
87
|
+
|
|
88
|
+
Rename:
|
|
89
|
+
|
|
90
|
+
- Legacy terminology that no longer matches the product
|
|
91
|
+
- Migration-oriented naming (`*_v2`, `*_new`, `Legacy*`)
|
|
92
|
+
- Implementation-leaked concepts (e.g., a class called `MongoOrderStore` when Mongo was replaced with Postgres last year)
|
|
93
|
+
- Misleading abstractions whose names suggest one responsibility but implement another
|
|
94
|
+
|
|
95
|
+
Prefer names aligned with:
|
|
96
|
+
|
|
97
|
+
- User behavior
|
|
98
|
+
- Domain intent
|
|
99
|
+
- System responsibility
|
|
100
|
+
|
|
101
|
+
A rename without redirecting callers leaves a search-and-find trail of broken references. Always update every caller in the same commit as the rename.
|
|
102
|
+
|
|
103
|
+
## 7. Validate the New Shape
|
|
104
|
+
|
|
105
|
+
After refactoring, verify:
|
|
106
|
+
|
|
107
|
+
- **Deleted paths truly have no callers** — re-grep, including in test files, config files, docs, and CI
|
|
108
|
+
- **Navigation still works** — for UI work, click through the affected flows manually, don't rely on snapshot tests
|
|
109
|
+
- **Persisted state still behaves correctly** — for storage refactors, migrate-up then migrate-down then migrate-up again on a real database snapshot
|
|
110
|
+
- **Permission boundaries remain correct** — re-walk the auth flow with a non-admin account
|
|
111
|
+
- **APIs remain coherent** — for public surfaces, run any contract tests; for internal APIs, walk the call graph
|
|
112
|
+
- **Tests validate intended behavior** rather than historical quirks — if a test references a deleted concept, the test was probably wrong, not the refactor
|
|
113
|
+
|
|
114
|
+
Add regression coverage for:
|
|
115
|
+
|
|
116
|
+
- Removed assumptions
|
|
117
|
+
- State transitions
|
|
118
|
+
- Orchestration boundaries
|
|
119
|
+
- Routing behavior
|
|
120
|
+
- Migration-sensitive logic
|
|
121
|
+
|
|
122
|
+
The new tests should describe the *intended shape*, not the historical shape. If you find yourself writing a test that asserts the old behavior, stop and ask whether the old behavior was actually correct.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Audit Patterns — Concrete Things to Grep For
|
|
2
|
+
|
|
3
|
+
This is the hands-on toolkit for step 2 of the workflow (Audit Reality Against Intent). Each pattern below is a *candidate* for deletion, not an automatic delete. Examine the context, then decide.
|
|
4
|
+
|
|
5
|
+
## Marker comments
|
|
6
|
+
|
|
7
|
+
The most reliable signal of intentional debt: someone already flagged it.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
rg -n "TODO: remove after|DEPRECATED|legacy|temp:|hack:|XXX:|FIXME"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Look especially for `// TODO: remove after <date>` where the date is past, and `// temporary` comments that have been there for over a year.
|
|
14
|
+
|
|
15
|
+
## Versioned identifiers
|
|
16
|
+
|
|
17
|
+
Functions, files, classes, types, or modules whose name encodes a version or generation:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
rg -n "_v2|_v3|_new|_old|_legacy|V2$|V3$|Legacy[A-Z]"
|
|
21
|
+
fd -e ts -e tsx -e py -e go -e rs "(_v2|_new|_old|_legacy)"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
These are usually one of three things:
|
|
25
|
+
1. A successful migration where the `_old` should be deleted
|
|
26
|
+
2. A failed migration where the `_new` should be deleted
|
|
27
|
+
3. An in-flight migration — confirm with the owner before touching
|
|
28
|
+
|
|
29
|
+
## Stale feature flags
|
|
30
|
+
|
|
31
|
+
Feature flags older than ~6 months that still default to a single branch:
|
|
32
|
+
|
|
33
|
+
- Check the flag-management system's "default value never changed" view
|
|
34
|
+
- Grep for `if (flags.<flagName>)` and check how many code paths each side has
|
|
35
|
+
- A flag with a one-line `else` branch (or no `else` at all) is a candidate for inlining
|
|
36
|
+
|
|
37
|
+
## Pass-through wrappers
|
|
38
|
+
|
|
39
|
+
Wrappers whose only job is renaming arguments or repackaging return values:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
def get_user(user_id):
|
|
43
|
+
return fetch_user(user_id)
|
|
44
|
+
|
|
45
|
+
def fetch_user(user_id):
|
|
46
|
+
return _user_repo.find(user_id)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Three layers, one operation. Collapse to one.
|
|
50
|
+
|
|
51
|
+
## Delegating route handlers
|
|
52
|
+
|
|
53
|
+
Route handlers that only delegate to other route handlers (often a legacy URL alias kept "just in case"):
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
app.get('/api/v1/users', (req, res) => app._router.handle({...req, url: '/api/users'}, res))
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Either kill the alias or document why it's load-bearing.
|
|
60
|
+
|
|
61
|
+
## Dual-mode forks
|
|
62
|
+
|
|
63
|
+
`if (env === 'old' || env === 'legacy')` or `if (clientVersion < N)` branches:
|
|
64
|
+
|
|
65
|
+
- The "old mode" path is rarely tested
|
|
66
|
+
- It accumulates new bugs because nobody exercises it
|
|
67
|
+
- Killing it requires confirming all consumers are off the old mode — but the data to confirm is usually accessible
|
|
68
|
+
|
|
69
|
+
## Duplicate config keys
|
|
70
|
+
|
|
71
|
+
Config keys with near-identical names that diverged over time:
|
|
72
|
+
|
|
73
|
+
- `database_url` and `db_url`
|
|
74
|
+
- `retry_count` and `max_retries`
|
|
75
|
+
- `enabled` and `is_enabled`
|
|
76
|
+
|
|
77
|
+
Pick one, alias the other for one release, then remove.
|
|
78
|
+
|
|
79
|
+
## Naming-vs-behavior mismatches
|
|
80
|
+
|
|
81
|
+
Comments explaining why a name is "actually" different from its behavior:
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
// note: getUser actually returns a UserProfile, not a User
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The comment is telling you to rename the function.
|
|
88
|
+
|
|
89
|
+
## Tests asserting historical bugs
|
|
90
|
+
|
|
91
|
+
Tests whose assertions describe behavior the operator/user never wanted:
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
it('returns null when input is empty (legacy behavior)', () => { ... })
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Either the legacy behavior was correct (then drop the parenthetical) or it wasn't (then the test is wrong, not the candidate refactor).
|
|
98
|
+
|
|
99
|
+
## Indirection without callers
|
|
100
|
+
|
|
101
|
+
Abstract classes / interfaces / factories with exactly one implementation. Often introduced "in case we need to swap implementations later" — and we never did. Inline them.
|
|
102
|
+
|
|
103
|
+
## Configuration-driven chaos
|
|
104
|
+
|
|
105
|
+
Code shaped like:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
HANDLERS = {
|
|
109
|
+
"type_a": handle_a,
|
|
110
|
+
"type_b": handle_b,
|
|
111
|
+
"type_c": handle_c, # only used by one old code path
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
If `type_c` has one caller and no plans to grow, inline it and delete the registry entry.
|
|
116
|
+
|
|
117
|
+
## What to do with each match
|
|
118
|
+
|
|
119
|
+
For each candidate:
|
|
120
|
+
|
|
121
|
+
1. **Find every caller** (workflow step 2)
|
|
122
|
+
2. **Decide**: keep / delete / rename
|
|
123
|
+
3. **If deleting**: confirm no telemetry references the names you're removing (workflow pre-flight)
|
|
124
|
+
4. **Document** in the PR summary as one line: `deleted: <name> — <one-sentence reason>`
|
|
125
|
+
|
|
126
|
+
A clean audit produces a punch list. The workflow turns that punch list into commits.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Decision Filters + Anti-Patterns
|
|
2
|
+
|
|
3
|
+
When the audit has produced candidates and the workflow is in motion, choices accumulate fast. These filters resolve them. The anti-patterns at the bottom name the failure modes to refuse on sight.
|
|
4
|
+
|
|
5
|
+
## Decision Filters — when choosing between implementations
|
|
6
|
+
|
|
7
|
+
Prefer the option that:
|
|
8
|
+
|
|
9
|
+
- **Removes more complexity** — fewer concepts to memorize
|
|
10
|
+
- **Reduces future branching** — fewer `if (mode === ...)` paths
|
|
11
|
+
- **Reduces special cases** — fewer "this is true except when"
|
|
12
|
+
- **Improves discoverability** — a new hire finds it without being told where to look
|
|
13
|
+
- **Eliminates hidden behavior** — no surprise side effects, no shared mutable state
|
|
14
|
+
- **Centralizes ownership** — one module owns one concept
|
|
15
|
+
- **Lowers operational ambiguity** — on-call can act on the alert without paging the author
|
|
16
|
+
- **Makes intent obvious from structure** — the directory layout tells you what's where
|
|
17
|
+
|
|
18
|
+
## Tiebreakers — when the filters above are equal
|
|
19
|
+
|
|
20
|
+
- **Prefer the option a new hire would understand without a meeting.**
|
|
21
|
+
- **Prefer the option that fails loudly over one that fails silently.** Silent fallbacks are how production gets weirder over time.
|
|
22
|
+
- **Prefer the option with fewer configuration knobs.** Every config key is a future bug surface.
|
|
23
|
+
- **Prefer the option that does not need a comment to justify its existence.** Comments are a code smell when they're load-bearing.
|
|
24
|
+
- **Prefer the option that survives unchanged when the next adjacent feature ships.** Tight coupling to the next feature means you'll be back here in two months.
|
|
25
|
+
|
|
26
|
+
## Anti-Patterns — refuse on sight
|
|
27
|
+
|
|
28
|
+
These appear under different names but are the same shape: complexity preserved as an end in itself.
|
|
29
|
+
|
|
30
|
+
### Architectural anti-patterns
|
|
31
|
+
|
|
32
|
+
- **Preserving dead compatibility paths forever** — "we might need it" without a concrete caller is not a reason
|
|
33
|
+
- **"Safe" wrappers that hide bad architecture** — moving the smell behind a facade doesn't remove it
|
|
34
|
+
- **Generic frameworks for single-use problems** — the YAGNI principle, lived
|
|
35
|
+
- **Feature flags that became permanent architecture** — a flag with no plan to remove it is a config knob in disguise
|
|
36
|
+
- **Duplicated state ownership** — two stores believing they own the same field is a recipe for divergence
|
|
37
|
+
- **Configuration-driven chaos** — when every value is in a YAML, refactoring the code doesn't help; the YAML *is* the code
|
|
38
|
+
- **Indirection without operational value** — if removing the abstraction doesn't make anything worse, the abstraction wasn't doing work
|
|
39
|
+
- **Abstractions created solely to avoid touching old code** — the abstraction is the debt
|
|
40
|
+
|
|
41
|
+
### Process anti-patterns specific to this skill
|
|
42
|
+
|
|
43
|
+
- **Treating `git blame` as architectural authority.** *Who wrote this* is not *why it should stay*. The original author may have been correcting an even worse decision that's since been undone.
|
|
44
|
+
- **Refactoring tests to match the new code instead of validating intended behavior.** If the test breaks, the test might be right and the refactor wrong. Don't assume.
|
|
45
|
+
- **Introducing a new abstraction in the same change that removes an old one** — split into two reviewable steps. Reviewers cannot evaluate "remove A and add B" as cleanly as "remove A" then "add B".
|
|
46
|
+
- **Renaming without redirecting callers** — leaving a search-and-find trail of broken references. Every rename updates every caller in the same commit.
|
|
47
|
+
- **Deleting telemetry, metrics, or alerts along with the code they instrumented** without acknowledging it. The dashboard breaks silently and on-call learns about it the hard way.
|
|
48
|
+
- **Confusing "fewer lines" with "simpler."** Density is not clarity. A 200-line function can be simpler than ten 20-line ones that pass state through a chain.
|
|
49
|
+
|
|
50
|
+
## When the operator pushes back on a filter
|
|
51
|
+
|
|
52
|
+
The filters are heuristics, not laws. If the operator has context you don't have — a regulatory requirement, a contract with a downstream consumer, a known-bug they're tracking — the operator wins. Document the deviation in the PR summary so the next reader doesn't undo it.
|
|
53
|
+
|
|
54
|
+
But: if the operator's pushback amounts to "this is how we've always done it," that's not context, that's inertia. Push back politely with the filter that resolves the choice and let them decide.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Preferred Outcomes + Reporting Back
|
|
2
|
+
|
|
3
|
+
This file describes what a successful zero-tech-debt refactor looks like, and how to summarize the result so reviewers and future readers can evaluate the *shape change* rather than re-reading the diff.
|
|
4
|
+
|
|
5
|
+
## What success looks like
|
|
6
|
+
|
|
7
|
+
A successful refactor produces:
|
|
8
|
+
|
|
9
|
+
- **Fewer files** — collapsed wrappers, deleted dead modules
|
|
10
|
+
- **Fewer modes** — `if (legacyMode)` branches removed, one path through the code
|
|
11
|
+
- **Fewer flags** — feature flags that became permanent are inlined
|
|
12
|
+
- **Fewer state transitions** — the state machine has fewer arrows on the diagram
|
|
13
|
+
- **Fewer routing paths** — aliases removed, one URL per resource
|
|
14
|
+
- **Fewer adapters** — pass-through layers flattened
|
|
15
|
+
- **Fewer abstractions** — single-implementation interfaces inlined
|
|
16
|
+
- **Fewer concepts developers must memorize** — fewer "you also need to know about X" caveats
|
|
17
|
+
|
|
18
|
+
...while improving:
|
|
19
|
+
|
|
20
|
+
- **Clarity** — a new hire reads the code and understands the intent
|
|
21
|
+
- **Reliability** — fewer paths means fewer untested paths means fewer bugs
|
|
22
|
+
- **Maintainability** — the next change is cheaper because there's less to navigate around
|
|
23
|
+
- **Onboarding speed** — measured in "minutes to first useful PR," not "weeks to feel comfortable"
|
|
24
|
+
- **Operational confidence** — on-call can act on alerts without paging the original author
|
|
25
|
+
- **Implementation velocity** — features land faster because there's less scaffolding to route around
|
|
26
|
+
|
|
27
|
+
If the refactor produced more of any of the first list and didn't improve any of the second list, it wasn't a zero-tech-debt refactor — it was reshuffling.
|
|
28
|
+
|
|
29
|
+
## How to summarize the result
|
|
30
|
+
|
|
31
|
+
The diff lists every line. The summary exists to make the architectural delta legible to reviewers and future readers. A good summary fits in five sections:
|
|
32
|
+
|
|
33
|
+
### 1. Deleted
|
|
34
|
+
|
|
35
|
+
Counts, not lists. The reviewer can `git log` for the names.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
Deleted:
|
|
39
|
+
- 12 files
|
|
40
|
+
- 47 functions
|
|
41
|
+
- 3 feature flags (NEW_USER_FLOW, ENABLE_V2_PRICING, USE_OLD_AUTH)
|
|
42
|
+
- 5 route aliases (/api/v1/users → /api/users, etc.)
|
|
43
|
+
- 8 config keys (consolidated into `service.retry`)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Unified
|
|
47
|
+
|
|
48
|
+
Before/after shape, not before/after diff. One line per consolidated flow.
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
Unified:
|
|
52
|
+
- 3 user-lookup paths → 1 (UserService.findById is the single entry point)
|
|
53
|
+
- 2 retry implementations → 1 (utils/retry.ts; the http/retry.ts and queue/retry.ts variants are gone)
|
|
54
|
+
- 4 permission checks scattered across UI components → 1 (PolicyService.canUserAccess)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 3. Renamed
|
|
58
|
+
|
|
59
|
+
Old name → new name, with the domain reason. The reason matters more than the names.
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
Renamed:
|
|
63
|
+
- MongoOrderStore → OrderRepository
|
|
64
|
+
(we replaced Mongo with Postgres in 2024; the class name leaked the old infra)
|
|
65
|
+
- processOrder → fulfillOrder
|
|
66
|
+
(function handles fulfillment, payment is now in PaymentService)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 4. Intentionally Left Alone
|
|
70
|
+
|
|
71
|
+
Things the audit flagged but you chose not to touch, with the reason. This prevents the next refactor from re-noticing the same items.
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
Intentionally left alone:
|
|
75
|
+
- LegacyExportAdapter — still has 3 enterprise customers on the old schema; sunset is on the Q3 roadmap
|
|
76
|
+
- the `mode` parameter on Search — used by the analytics pipeline, removal would require a coordinated change with that team
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 5. Deferred
|
|
80
|
+
|
|
81
|
+
Scoped as separate follow-up tickets, not buried in this change. This is the single most important section — it's where you preserve the rot you noticed but chose not to fix.
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
Deferred (filed as separate tickets):
|
|
85
|
+
- #1234 — consolidate notification dispatch (3 paths, but each has independent retry/dedup logic)
|
|
86
|
+
- #1235 — remove DUAL_WRITE flag (still needed until the Postgres backfill completes Q1)
|
|
87
|
+
- #1236 — rename ProductCatalog (now handles services + bundles + products)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Why this format
|
|
91
|
+
|
|
92
|
+
Reviewers reading a 2,000-line diff need to understand the *shape* of the change in 60 seconds. The summary is that 60-second read. If it doesn't fit on one screen, the refactor was too big — split it.
|
|
93
|
+
|
|
94
|
+
Future readers (including future you) come back to figure out "why did we have X and now we have Y?" The summary is the answer. Make it good.
|
|
95
|
+
|
|
96
|
+
## The thing that does NOT belong in the summary
|
|
97
|
+
|
|
98
|
+
- Apologies for breaking changes ("sorry, this was unavoidable") — if it's unavoidable, just say what broke and how to migrate; no apology
|
|
99
|
+
- A diff narrative ("first I deleted X, then I noticed Y, so I refactored Z") — the diff has the history; the summary has the shape
|
|
100
|
+
- Marketing language ("dramatically improves," "industry-leading," "significantly cleaner") — the reviewer can evaluate; don't editorialize
|
|
101
|
+
- Speculation about future benefits ("this will make adding new features easier") — sometimes true, often not; prove it with the next feature, don't promise it in the summary
|