agentleFS
anthony-chaudharyanthony-chaudhary/fak/.agents/skills/modularize/SKILL.md

modularize

Skill47 starsChanged 12 days agoApache-2.0View on GitHub

One 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 (hazard-check → plan with tools/godsplit_plan.py → extract → goimports/gofmt → prove no decl dropped with tools/refactor_verify.py → verify → commit by explicit path), and extracts long functions into named helpers. Use when this named workflow matches the task.

  • Installs packages

2 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

9 rules. About 0 are common, 2 are distinctive, and 7 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 7

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    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 …

    line 44
  2. Very specific:

    Read architecture's defects: each is god-file <path> (N lines > 1500) or god-function <path>:<fn> (N lines > 200).

    line 53
  3. Very specific:

    Run the snippets below with the Bash tool — they are POSIX sh (sed, the mk() function, goimports/gofmt), not PowerShell.

    line 96
  4. Very specific:

    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 …

    line 103
  5. Distinctive:

    Avoid param explosion: when a helper would take 8+ args, group them into a struct.

    line 136
  6. Very specific:

    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 …

    line 184
  7. Very specific:

    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 …

    line 191
  8. Very specific:

    When architecture is the heaviest KPI in /quality-score (it defers here on purpose).

    line 256
  9. Distinctive:

    After a feature lands a 1500+-line file or a 200+-line function in a critical package.

    line 258

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

/modularize — split the monoliths, provably, without changing behavior

What this does. /quality-score measures ten KPIs and retires the safe ones, but it marks architecture RISKY 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 the architecture KPI 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:

  1. per-file build tags (//go:build …) — moving a decl changes which file carries the constraint;
  2. func init() — init order is filename-alphabetical across a package, so moving an init() between files can silently reorder initialization;
  3. aliased imports (x "path") — goimports -w re-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 a sed -n range (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 honest refactor(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 --porcelain is 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 in git diff --cached — that's expected; your --path excludes them from the commit).
  • Honest subject. A split/extraction is refactor(<scope>): — never feat(/fix(. End every ship commit with a (fak <leaf>) trailer ((fak gateway), (fak model); a cmd/<dir> demo → (fak <dir>)).
  • Shared-trunk law. Stay on main (the OFF_TRUNK guard refuses a branch — this is why worktrees don't work, see below). Commit by explicit path via fak commit --path; if a peer's MERGE_HEAD is 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)

  1. 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.
  2. Never create a file-per-function. Over-splitting is its own debt; group related decls. refactor_verify.py flags this mechanically as OVER-SPLIT — a new file carrying a single top-level decl (per-OS build-tagged stubs and func main entrypoints excepted, since those are one-decl by design). If it fires, your seams are too fine: merge the fragments into one cohesive concern file.
  3. 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.
  4. Never touch the frozen ABI (internal/abi) and never add a dependency to move a number.
  5. goimports will not re-add a local import alias — copy it by hand, or the build breaks (undefined: <alias>); godsplit_plan.py lists the aliases to re-add.

When to run this

  • When architecture is 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 /loop cadence 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.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.