indexion refactor — Codebase Refactoring
Detect and eliminate duplication at three levels — textual, structural, and conceptual — using indexion's analysis commands, then verify SoT is enforced.
When to Use
- After adding a new abstraction (type, module, API layer)
- After introducing a new file format or I/O boundary
- When a fix required touching 3+ files for the same reason
- When a "guard" or "skip" was added to work around a structural problem
- When
opendir,ENOENT, or similar filesystem errors appear from unexpected paths - When extracting shared code across packages
- When cleaning up after a refactor (removing trivial wrapper functions)
- Periodic SoT health check on a codebase
Three Levels of Duplication
Textual duplication is easy to find and fix. Conceptual duplication is the hardest and most dangerous — it produces no copy-paste matches but means changing one concept requires updating every scattered implementation.
Workflow
Phase 1: Clear textual duplication (plan refactor)
Start with high-confidence matches and work down.
Read the output in three sections:
Same-file duplicates (functions within one file at 90%+) are the highest-value
targets — easiest to fix, clearest wins. Example: get_global_data_dir and
get_global_cache_dir share 95% structure, extracted into resolve_os_dir.
plan refactor options:
What remains after cleanup (stop signals):
- Platform stubs (
native.mbt/stub.mbt) — intentional platform branching - Type method similarity (
to_stringon different types) — different types, same pattern - CLI command boilerplate (
command()functions) — @argparse API pattern, not duplication - Semantic-but-different functions (
is_disqualifying_keywordvsis_skip_token) — different purpose
Phase 2: Extract cross-package shared code (plan solid)
After cleaning within each directory, find code that should be shared across packages.
plan solid differs from plan refactor:
plan solid options:
Workflow:
- Run
plan refactoron each directory individually first to clean internal duplication - Run
plan solid --from=dirA,dirBto find cross-directory extraction candidates - Extract shared code following the plan's recommendations
- Use
indexion grep "TypeIdent:SharedType"to verify all references are updated
Phase 3: Remove unnecessary wrappers (plan unwrap)
After consolidation, clean up trivial delegation functions that add indirection without value.
What gets detected: Functions whose body is a single function call with all arguments forwarded as simple identifiers — no control flow, no transforms.
plan unwrap modes:
plan unwrap options:
Review before removing:
- Platform wrappers (FFI,
@osenv_path) are abstraction layers, not accidental indirection - Public API wrappers used by external packages — removing them is a breaking change
- Always
--dry-runfirst
Phase 4: Detect concept-level duplication (explore + analysis)
This is the hardest level. Textual and structural tools won't find it because the code is different — but the concept is the same.
Files at 40-60% similarity without structural duplication are concept neighbors — they use the same terms because they deal with the same domain.
For each high-similarity pair, ask: "What concept do they share, and who owns it?"
Common patterns of concept leakage:
Phase 5: Consolidate into SoT
The module that defines the concept should be the only one that implements the logic.
Rules:
- One concept, one module, one function. If "extract text from archive" appears in
vfs.mbt,discover.mbt, andargs.mbt, it belongs invfs.mbtonly. - Callers receive results, not ingredients. Don't export
is_archive_spec+ArchiveSpec::from_spec+expand_archiveseparately. Exporttry_extract_archive_text(path, spec) -> String?. - Guards are symptoms, not fixes.
if is_virtual_path(x) { skip }means virtual paths shouldn't reach here at all. Fix the source, not the sink. - Re-reading from disk what's already in memory is a concept leak. If
SupportedFile.contentholds the text, no downstream code should call@fs.read_file_to_string(file.path).
Phase 6: Verify
After SoT consolidation:
- Textual similarity between the concept owner and its callers drops
- Callers become shorter (one API call instead of multi-step logic)
- The concept owner may grow, but it's the single place to change
Phase 7: Prove non-recurrence with tests
Write a test that structurally prevents the old pattern from recurring:
The test doesn't check behavior — it checks the SoT invariant.
Red Flags
"I need to add a guard here"
If you're adding if is_special_case(x) { skip } to a function that shouldn't receive
special cases, the problem is upstream. The function's caller should never pass that value.
"It works but prints errors to stderr"
Stderr messages from C runtime (opendir: No such file or directory) mean invalid data
reached a system call. catch absorbs the error, but perror() already printed.
The only fix is preventing invalid data from reaching the call.
"I'll fix it in each command separately"
If the same fix is needed in explore, search, grep, reconcile, plan documentation... the fix belongs in the shared pipeline, not in each command.
"The similarity is just shared vocabulary, not real duplication"
40-60% TF-IDF similarity between modules that aren't supposed to share concepts is a warning. The vocabulary match IS the signal.


