TheSkillz

Codebase Constitution

Rules your agent and your CI both obey, because each one ships with a checker

TheSkillz Team TheSkillz Team No reviews yet0 installsv1.0.0
Scan passed · 100/100Human reviewedOfficial · TheSkillz
☆ Star 0

Reads the repo, sizes budgets to its actual file distribution, then writes ARCHITECTURE.md alongside the machine checks that enforce it: import boundaries via eslint-plugin-boundaries, depguard or import-linter. Baselines existing violations so CI fails only on an increase, which is what makes it adoptable in a repo that already breaks the rules.

SKILL.md

Codebase Constitution

Most architecture documents are fiction. They describe a structure the code had eighteen months ago, nobody reads them, and the one person who enforced them has left. The failure is not that the rules were wrong — it is that a rule with no checker is a comment.

This skill writes rules and their enforcement together, and refuses to write one without the other.

The core rule

Every rule must be mechanically checkable. If you cannot express it as a lint rule, a script or a CI step, it does not go in the constitution — it goes in an appendix headed "conventions we chose not to enforce", so nobody mistakes an aspiration for a contract.

1. Read the repo before proposing anything

A 2,000-file monolith and a three-week-old prototype do not get the same rules. Establish:

  • File count, language mix, framework, test runner, and what CI already runs.
  • The lint and format config that exists. You are extending it, not replacing it — a constitution that rewrites someone's working ESLint setup gets reverted wholesale.
  • The current shape: are there feature directories, layer directories, or neither?
  • The distribution of file sizes. find src -name '*.ts' | xargs wc -l | sort -n | tail -20 tells you what the budget can realistically be.

2. Set budgets from that distribution, not from a blog post

Propose numbers the repo can nearly meet, then ratchet. Reasonable defaults:

Budget Default Notes
Lines per file 300 The number everyone quotes. Treat it as a budget, not a cap.
Lines per function 50 Catches more real problems than the file rule does.
Parameters 4 Beyond this, pass an object.
Nesting depth 3 The strongest single predictor of a function nobody wants to touch.
Cyclomatic complexity 10 Pairs with nesting; catches the flat-but-branchy function.

Every budget needs an escape hatch, and the hatch must cost something. A file over budget either splits or carries a one-line header saying why it does not. A hard cap with no hatch produces helpers2.ts — the same code, split at an arbitrary seam, now with an import between the halves. That is worse than the file you started with.

3. Write the boundaries as checks

This is the part that matters most and is skipped most. Pick the tool for the stack:

  • TypeScript: eslint-plugin-boundaries, or import/no-restricted-paths with a zone per feature. Routes may import features; features may not import routes; features may not import each other.
  • Go: depguard in .golangci.yml, one rule per feature package denying the sibling packages and allowing the shared platform packages. Most teams use depguard only to ban a dependency and never discover it does this.
  • Python: import-linter contracts in pyproject.toml — a layers contract for vertical direction and an independence contract across feature packages.

4. Baseline, then ratchet

Turning the rules on in a repo that violates them produces a red build and a revert. Instead:

  1. Run the checks, write the current violations to a committed baseline file.
  2. CI compares against the baseline and fails only when the count goes up, or when a file not in the baseline violates a rule.
  3. Anyone touching a baselined file is expected to bring it into line; the baseline shrinks over time and can never grow.

This is the single thing that makes a constitution adoptable. Without it you are asking for a refactoring week nobody has budgeted.

5. Ship the artifacts

  • ARCHITECTURE.md — the human contract. Structure, the boundaries, the budgets, and for each one a line naming the check that enforces it.
  • The lint and boundary config, extending what was there.
  • A check:arch script the developer can run locally with the same command CI uses.
  • A CI job wired to that script.
  • The escape-hatch convention documented where people will hit it.

Judgement calls

  • Feature-driven or layer-driven? Decide from evidence: more than one contributor and several entry points favours features; a single-purpose library is fine flat. Do not impose feature directories on a codebase with four modules.
  • How many rules? Fewer than fifteen. A constitution nobody can hold in their head gets skimmed, and the important rule is now buried among the trivia.
  • Rule or code review? If it is mechanically checkable, make it a rule and stop spending human attention on it. If it needs judgement, leave it to review and do not pretend otherwise.

Verification gate

  • check:arch runs clean against the baseline on a fresh clone.
  • Every rule in ARCHITECTURE.md names the check that enforces it. Read the file and confirm — no unenforced rule is stated as though it were binding.
  • The baseline was generated from the current tree, not hand-written.
  • CI fails on a deliberately introduced violation. Test this; an unverified gate is not a gate.
  • The existing lint config still passes — nothing was silently dropped.
  • Paste the command output rather than describing it.

Reviews

Sign in to leave a review.

  • Be the first to review this skill.

More in coding