/modularize — split the monoliths, provably, without changing behavior
SkillFiles & storageOne focused, repeatable pass that retires the code-quality scorecard's `architecture` debt — the god-files (>1500 lines) and god-functions (>200 lines) that /quality-score flags as RISKY and explicitly defers to \"a focused pass\". Splits a monolith along REAL concern seams via behavior-preserving code motion (the goimports recipe: hazard-check → plan boundaries with tools/godsplit_plan.py → sed-extract → goimports -w → gofmt → prove-no-decl-dropped with tools/refactor_verify.py → verify → prove → commit by explicit path), and extracts long functions into named helpers (linear. Use when this named workflow matches the task.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the /modularize — split the monoliths, provably, without changing behavior skill
What this skill tells your AI
The instructions your AI receives, as published by anthony-chaudhary/fak in .agents/skills/modularize/SKILL.md and read by ahel’s review.
What this does.
/quality-scoremeasures ten KPIs and retires the safe ones, but it marksarchitectureRISKY and defers it: "do it in ONE lane, keep behavior identical… if you can't do it safely this pass, leave it for a focused pass — do NOT half-do a refactor." This is that focused pass. It takes the god-files and god-functions that drag thearchitectureKPI to the floor and splits them along real concern seams — as behavior-preserving code motion, verified by the toolchain, proven by the scorecard, committed by explicit path on the shared trunk. In one run it took fak from code-debt 12 → 0, score 86.2 (B) → 95.9 (A).
The shape: find the heaviest architecture defect → plan the cut (boundaries + hazards) → split (file = code motion, function = extract) → verify (build/vet/test + behaviour review) → prove the drop → commit ONLY your packages by explicit path.
The one insight that makes this safe
Moving a top-level declaration to another file in the same package is a semantic
no-op in Go — package, not file, is the scope. So a god-file split can't change
behavior, and go build + go vet + go test is a near-complete gate. The residual
go build cannot cover — a top-level decl dropped by an incomplete cut that happens
to be unreferenced in-module (so no undefined:) — is what closes it from near to
complete: tools/refactor_verify.py proves the declaration set was preserved (Step 4).
There are exactly three exceptions that make code motion NOT a no-op;
tools/godsplit_plan.py flags all three:
- per-file build tags (
//go:build …) — moving a decl changes which file carries the constraint; func init()— init order is filename-alphabetical across a package, so moving aninit()between files can silently reorder initialization;- aliased imports (
x "path") —goimports -wre-derives plain imports after a move but does NOT re-infer a local alias; you must copy it to the new file by hand.
If a file has a build tag or an init(), keep those decls where they are (or in a file
whose name preserves init order) — don't move them blind.
Step 1 — Find the target (the scorecard builds the work-list)
python tools/code_quality_scorecard.py --no-toolchain --json # fast; corpus.kpis architecture.defects = your work-list
Read architecture's defects: each is god-file <path> (N lines > 1500) or
god-function <path>:<fn> (N lines > 200). Attack the heaviest first, and bias to
the most critical packages (the kernel — adjudicator, gateway, model,
ggufload — and cmd/fak, the entry point — over demos/benchmarks). Record the
baseline: architecture defects = K, code_debt = N, score = S.
Step 2 — Plan the cut (boundaries + hazards, computed not guessed)
The error-prone part of a clean split is cutting at the right line: a decl's doc comment
sits ABOVE its keyword, so a naive cut at func/type orphans the comment. Let the
planner compute the doc-comment-aware ranges and the three hazards:
python tools/godsplit_plan.py internal/model/kv.go # human table
python tools/godsplit_plan.py internal/model/kv.go --json # machine payload
Each decl row is block_start..block_end <kind> <name> — block_start..block_end is the
exact sed -n 'A,Bp' range that carries the decl with its doc comment (func [NL] sizes
are advisory; the Step 1 scorecard is the authority on what's a god-function). Group adjacent
decls into cohesive concerns (e.g. kv.go → kvcache.go for the KVCache type +
eviction, rope.go for rotary embedding, leaving the Session/forward core). Splitting on
real seams is the whole point — see the anti-gaming laws.
Sanity-check the plan before trusting it — godsplit_plan.py is a LINE parser, not a Go
tokenizer. Fall back to a hand cut if ANY of these hold: a decl row prints !! INVERTED RANGE; the raw_strings hazard is > 0 (a multi-line backtick string can hide or shift a
column-0 decl — eyeball those ranges); or the plan's decl count disagrees with the truth:
grep -cE '^(func|type|var|const) ' <file> # must equal the number of rows the plan printed
goimports is the engine of the file split and is not preinstalled — install it and
confirm it resolved before you cut anything:
go install golang.org/x/tools/cmd/goimports@latest
command -v goimports || { echo "goimports missing — $(go env GOPATH)/bin not on PATH"; exit 1; }
Step 3 — Split
Run the snippets below with the Bash tool — they are POSIX sh (sed, the mk()
function, goimports/gofmt), not PowerShell. Precondition: gofmt -w <file> BEFORE you
plan, so the package clause and import block are blank-separated and no extract range can
start at the package line.
A. God-FILE → concern files (pure code motion)
Create each new file from its block range, then delete the moved ranges bottom-up (so
earlier deletes don't shift later line numbers), then let goimports fix every import. The
package name and target dir are read from the file so the snippet is copy-paste-safe in any
package:
F=internal/model/kv.go
D=$(dirname "$F")
PKG=$(python tools/godsplit_plan.py "$F" --json | python -c 'import sys,json;print(json.load(sys.stdin)["package"])')
mk(){ { echo "package $PKG"; echo; sed -n "$2,$3p" "$F"; } > "$D/$1"; }
mk kvcache.go 10 272 # ranges = block_start..block_end from godsplit_plan.py
mk rope.go 389 577
sed -i '389,577d' "$F"; sed -i '10,272d' "$F" # BOTTOM-UP: highest range first
goimports -w "$F" "$D/kvcache.go" "$D/rope.go" # auto-fix imports
gofmt -w "$F" "$D/kvcache.go" "$D/rope.go"
Then re-add any aliased import by hand to the new file (godsplit_plan.py lists them —
goimports won't re-derive a local alias, and the build fails undefined: <alias> without
it). If the source file leads with a license/SPDX header above package, copy it to each new
file too. The sed -n 'A,Bp' > newfile and sed -i here are the right tool for line-range
code motion; redirect only to in-tree paths — the repo-guard PreToolUse hook
(tools/repo_guard.py; AGENTS.md OUT_OF_TREE_WRITE) refuses a > /dev/null or ../ escape.
B. God-FUNCTION → helpers
- Linear flow (e.g. a CLI
cmdServe/cmdGuard): extract coherent phases into helpers. Return a small struct and unpack it to the SAME local names the caller already uses (up, base, key := us.provider, us.baseURL, us.apiKey) so the rest of the function is untouched — no cascading edits, smallest possible diff. - Closure-soup (a function whose closures share ~15 mutable locals, e.g. an SSE relay): make the shared state a named struct and turn the closures into methods on it. That's genuine modularity — named, individually-readable parts — not line-shuffling.
- Avoid param explosion: when a helper would take 8+ args, group them into a struct. A helper with 11 positional params is worse code than the god-function you started with.
Step 4 — Verify (behaviour is preserved — don't assume it, prove it)
4.0 — Prove no definition was dropped (the check go build cannot do)
A pure code-motion split preserves the package's top-level declaration set exactly.
go build catches a referenced symbol that went missing (undefined:) and a duplicated
one (redeclared) — but a decl that was dropped and happens to be unreferenced in-module
(dead-but-real, or an exported symbol whose only consumers are out-of-tree or a JSON wire
contract) compiles clean and vanishes silently. That is the "split a god-file, then a
definition is missing" failure. Close it mechanically first — it is fast and catches what
the toolchain structurally cannot:
python tools/refactor_verify.py --expect-motion <orig.go> <your new files> # exit 1 on any drop
It diffs the package's decl multiset before (HEAD) vs after (working tree). For a
pure in-package split the correct verdict is clean; --expect-motion makes any decl
that left the package a hard failure. Read each finding:
DROPPED— a definition that exists nowhere after the cut. Either you missed ased -nrange (the split is INCOMPLETE — go add it) or it is a genuine deletion, which does not belong in a commit labelled "pure code motion" — split it into its own honestrefactor(x): remove dead <name>commit with the reason in the body.RELOCATED— a decl moved to another package (a cross-package consolidation, not an in-package split). Drop--expect-motion, include the destination package in scope, and confirm each move is intended.OVER-SPLIT— a new file carrying a single decl (see anti-gaming law #2).
(Scope note: it is DECL-level — it proves no whole decl was dropped, not that a struct's
FIELDS survived a type Local = pkg.Remote alias consolidation. For that, read the aliased
type's fields by hand.)
4.1 — Build, vet, test
go build and go vet run natively; run the tests under WSL — per AGENTS.md, native
go test is blocked by an OS Application-Control policy on freshly-compiled test binaries on
this checkout (an OS quirk, not a code failure), and /quality-score validates the same way:
go build ./... # whole-tree compile (catches a lost/duplicated symbol)
go vet ./<pkg>/... # typecheck incl. _test.go
wsl -e bash -lc 'cd /mnt/c/work/fak && go test ./<pkg>/... -count=1' # the real gate
If native go test happens to run on your host, you may use it — but treat an
Application-Control block on a test binary as the documented ENVIRONMENT quirk, never as a
behaviour regression in your split. For internal/model, run the full (non--short)
suite so the bit-exact oracle witnesses fire. If you DELEGATED a split to subagents, have an
independent reviewer confirm behaviour-preservation (no changed strings/flags/control-flow/
exit-codes) before committing — read the diff in the reviewer, not in your own context.
Verify formatting via the scorecard's format KPI (full run), not a naive gofmt -l .:
this checkout is CRLF in the working tree, so a local gofmt -l . flags hundreds of files as
a line-ending artifact (see tools/gofmt_debt_audit.py for the blob method).
Step 5 — Prove the drop
python tools/code_quality_scorecard.py --no-toolchain --json # architecture.defects should shrink
State it plainly: architecture-debt K → K', code-debt N → M, score S → S'. The bar for a
pass is the heaviest architecture defect retired with behaviour proven green.
Step 6 — Commit ONLY your packages, by explicit path
fak sync reconcile --apply
git status --porcelain # see which files peers are editing RIGHT NOW
fak commit \
--path <orig.go> --path <your new files> \
-m "refactor(<leaf>): split <file> by concern" \
-m "Pure code motion, no behavior change. <file> N->M lines; extracted <a.go>, <b.go>. Verified build/vet/test green." \
[--push]
fak sync push
- Route around peer WIP. A file showing in
git status --porcelainis being edited by a peer — do NOT split it this pass; pick a clean target. A path-scoped commit leaves peer-staged files alone (you'll see them ingit diff --cached— that's expected; your--pathexcludes them from the commit). - Honest subject. A split/extraction is
refactor(<scope>):— neverfeat(/fix(. End every ship commit with a(fak <leaf>)trailer ((fak gateway),(fak model); acmd/<dir>demo →(fak <dir>)). - Shared-trunk law. Stay on
main(theOFF_TRUNKguard refuses a branch — this is why worktrees don't work, see below). Commit by explicit path viafak commit --path; if a peer'sMERGE_HEADis set, wait for it to clear; never force-push.
Parallelizing across many targets (when there are several god-files)
The Workflow tool's isolation: 'worktree' fails in this repo: git worktree add creates a
branch and the trunk guard refuses it (OFF_TRUNK). To fan out: give each agent one disjoint
leaf package (nothing two agents both touch), tell each to edit in place, run only scoped
builds (go build ./<pkg>/..., never go build ./... which races peers), and run no git — then
the orchestrator verifies and commits each package by explicit path. Have an independent reviewer
check the delegated diffs for behaviour-preservation before committing.
Anti-gaming laws (the measure is only as honest as the pass)
- Split on REAL concern seams, never arbitrary line counts. Each new file is one cohesive concern (the loader, the rotary embedding, the eviction path) — not "lines 1–700 / 701–1400". A split that just chops a file in half to beat 1500 is gaming, not modularity.
- Never create a file-per-function. Over-splitting is its own debt; group related decls.
refactor_verify.pyflags this mechanically asOVER-SPLIT— a new file carrying a single top-level decl (per-OS build-tagged stubs andfunc mainentrypoints excepted, since those are one-decl by design). If it fires, your seams are too fine: merge the fragments into one cohesive concern file. - Behaviour MUST be preserved — verify, don't assume. Pure code motion is safe only once the three hazards (build tags, init order, aliases) are cleared; an extraction can silently change a captured variable. Build + vet + test (+ review for delegated work) is non-negotiable.
- Never touch the frozen ABI (
internal/abi) and never add a dependency to move a number. goimportswill not re-add a local import alias — copy it by hand, or the build breaks (undefined: <alias>);godsplit_plan.pylists the aliases to re-add.
When to run this
- When
architectureis the heaviest KPI in/quality-score(it defers here on purpose). - To drive code-debt the last mile to 0 after the safe classes are retired.
- After a feature lands a 1500+-line file or a 200+-line function in a critical package.
- On a
/loopcadence to keep the kernel from re-accreting monoliths between releases.
The scorecard, godsplit_plan.py, and refactor_verify.py are read-only; this skill's only
writes are your genuine splits and the new concern files. It never edits the frozen ABI, never
games a seam, and never commits a behaviour change it didn't verify — or a split it didn't prove
declaration-complete.
Signals
- GitHub stars
- 38
- Forks
- 15
- Last commit
- Sep 2026
ahel review
K1binfo
installs-packages
Automated review, not a security audit. Ruleset v1+k2.
Advanced
- Catalog kind
- skill
- Gateway key
modularize- Source
- github.com/anthony-chaudhary/fak