PHPackages                             jessegall/code-commandments - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Testing &amp; Quality](/categories/testing)
4. /
5. jessegall/code-commandments

ActiveLibrary[Testing &amp; Quality](/categories/testing)

jessegall/code-commandments
===========================

An architecture compiler for PHP — single-concern discipline prophets that judge a codebase against a configurable style and architecture spec

v4.212.0(2w ago)01.8k[14 issues](https://github.com/jessegall/code-commandments/issues)MITPHPPHP ^8.4

Since Jan 20Pushed 2w agoCompare

[ Source](https://github.com/jessegall/code-commandments)[ Packagist](https://packagist.org/packages/jessegall/code-commandments)[ RSS](/packages/jessegall-code-commandments/feed)WikiDiscussions main Synced 2w ago

READMEChangelogDependencies (28)Versions (970)Used By (0)

Code Commandments
=================

[](#code-commandments)

> An architecture linter for PHP &amp; Vue, built to drive AI coding agents.

**code-commandments** judges a PHP and Vue codebase against a set of architectural disciplines. Every violation (a "sin") is reported as a `file:line`, grouped under the **skill** that teaches the fix.

It's built for AI coding agents: point your agent at a codebase and it reads the skill each sin names, fixes at the source, and re-runs until clean. You can drive it by hand too.

A linter tells you a line is too long. code-commandments tells you *this array should be a value object, and here's the discipline that explains why*.

Contents
--------

[](#contents)

- [How it works](#how-it-works)
- [Install](#install)
- [Usage](#usage)
- [Configuration](#configuration)
- [Freezing a file](#freezing-a-file)
- [Hooks](#hooks)
- [How detectors are tested](#how-detectors-are-tested)
- [Skills](#skills)
- [Sins &amp; detectors](#sins--detectors)
- [Auto-fixing](#auto-fixing)
- [Scaffolding](#scaffolding)
- [Developing detectors](#developing-detectors)
- [License](#license)

How it works
------------

[](#how-it-works)

The loop is simple:

1. **Judge**: `commandments judge` prints every sin as a `file:line`, grouped by skill.
2. **Learn**: each sin names a **skill**; you (or your agent) read it.
3. **Fix**: fix at the source, or let `commandments repent` [auto-fix](#auto-fixing).
4. **Repeat**: re-run until clean (exit code `0`).

One pass of that loop, with an agent driving:

 [![An agent running one pass of the loop in a terminal: judge finds four sins across three skills, repent auto-fixes two, the agent fixes feature envy and an array bag at the source, judge comes back clean.](docs/agent-loop-session.svg)](docs/agent-loop-session.svg)

Under the hood there are two layers:

- **Skills**: the teaching layer. One doc per discipline, the source of truth for what "good" looks like.
- **Sin detectors**: small finders that read the syntax tree. Each finds one kind of sin and names the skill that fixes it. Detectors *find*, skills *teach*, scribes *[auto-fix](#auto-fixing)*.

**You don't need the packages a rule is about.** Detectors match on real *types*, so a rule for a package your project doesn't use never fires. Nothing to install or configure to keep it quiet.

Install
-------

[](#install)

```
composer require --dev jessegall/code-commandments
vendor/bin/commandments install
```

Usage
-----

[](#usage)

```
# scan. With no path, judge reads the source roots from .commandments/config.php
# (auto-detected from composer.json on the first run)
vendor/bin/commandments judge
vendor/bin/commandments judge src                  # or point it at a path

# scope to one skill (group) or one sin
vendor/bin/commandments judge src --skill=exceptions
vendor/bin/commandments judge src --sin=swallow-catch

# scope to what you changed
vendor/bin/commandments judge src --branch         # branch vs main (--branch=BASE to override)
vendor/bin/commandments judge src --changes        # uncommitted working-tree changes

# detectors run across 8 workers by default (capped at CPU cores); --parallel=1 disables
vendor/bin/commandments judge src --parallel=4

# skip paths (comma-separated fragments); list everything
vendor/bin/commandments judge src --exclude=Generated,Legacy
vendor/bin/commandments judge --list

# read the dependency stack you already have, and propose the layer declaration for it
vendor/bin/commandments layers                # print it
vendor/bin/commandments layers --write        # add it to .commandments/config.php
vendor/bin/commandments layers --floor        # only the namespaces nothing of yours sits below

# and as the codebase grows, edit the declared stack in place
vendor/bin/commandments layers add 'App\Parts' --may-use='App\Ui\Tokens'
vendor/bin/commandments layers allow 'App\Ui\Pages' 'App\Parts'   # one arrow
vendor/bin/commandments layers --write --refresh                   # regenerate the block

# executing an approved plan (see Hooks below)
vendor/bin/commandments checks start          # run the project's start / phase / complete checks
vendor/bin/commandments checks phase
vendor/bin/commandments checks complete       # full gate: your checks, then `judge --branch`
vendor/bin/commandments plan status           # is a plan active? (`plan done` ends it)

# hold every stop until a condition you set holds (no plan needed — see Hooks below)
vendor/bin/commandments until "the full test suite passes"
vendor/bin/commandments until met 1           # verified it — strike it off (`until list` shows them)
```

Exit code is non-zero when sins are found.

### Every command

[](#every-command)

CommandPurpose`commandments judge [path]`Scan a codebase and report its sins, grouped by the skill that fixes each. Exit code 1 when sins are found.`commandments make `Scaffold a commandment of your own — a skill, a sin and a detector in `.commandments/custom/`, registered in your config, with the rest of the process printed for you.`commandments checks [start|phase|complete]`Run the project's planExecution() checks for one moment of a plan.`commandments hints [path]`Auto-fix the Spatie Data magic surface — rename non-`from…` object factories to `from`, rewrite their call sites to `::from(...)`, and regenerate the `@method from(...)`/`collect(...)` docblock hints.`commandments repent [path]`Auto-fix sins — run every Scribe: the maintenance rewriters (Spatie Data hints) and each Repentable detector's own fix, backend and frontend.`commandments scaffold`Generate the reusable helper a sin's fix uses — written into your source root with its namespace injected. Idempotent: an existing file is skipped.`commandments report --reason="…" --ref=PATH:LINE`File a GitHub issue about code-commandments itself (via `gh`) — a false positive, a wrong rule, or a bug.`commandments feature-request --title="…" --reason="…"`File a \[feature-request\] GitHub issue (via `gh`) proposing a new or changed rule.`commandments freeze `Mark a file intentionally immutable, or lift the mark. A frozen file is still scanned (so cross-file rules stay correct) but never flagged and never rewritten.`commandments sync`Refresh this project's code-commandments integration — publish the skills, refresh the CLAUDE.md briefing and the config surface, and wire the Claude Code hooks.`commandments install`Wire a consumer project up once — the composer sync hook, the Claude Code hook suite (cardinal-rule reminder, judge nudge, plan-execution hooks) and .gitignore — then sync.`commandments remind`PostToolUse hook that counts tool uses and surfaces the cardinal rule once every INTERVAL—a steady heartbeat keeping 'trace to the source' present.`commandments judge-reminder`A "did you judge?" nudge wired to `Stop` and `PreToolUse` hooks; reminds when judged files are touched but unchecked, deduped per changed-file set.`commandments plan-reminder`The plan-execution Hook wired to `PostToolUse/ExitPlanMode` and `Stop`.`commandments plan status`The handle on the ACTIVE PLAN marker the keep-going Stop hook reads — scoped to this worktree.`commandments constraints list`The plan's architectural invariants — the rules the whole branch must still hold at the end.`commandments testing show`The plan's testing methodology — the working style the user chose at approval, in force for this run.`commandments until ""`The user's STOP GATE — record what must hold before you may stop, and every stop is held until you have VERIFIED it. Needs no plan and no config.`commandments hooks`The wired hook entry point — reads one hook payload from stdin, runs every registered handler, and merges their responses into one.`commandments hook `Run ONE hook class directly — the form every wired hook is written as, built-in or a consumer's own $config-&gt;hook(...).`commandments disable `Toggle a rule in the project's .commandments/config.php — edited through the AST, so the file stays valid PHP and your own lines are untouched.`commandments config`Inspect and manage .commandments/config.php — what is configured, and what is actually running.`commandments layers [path]`Read the dependency stack this project ALREADY has and propose the layer declaration for it — the rule is inert until one is declared, and nobody writes that from a blank file.`commandments exemptions`List the exemption tags — what a package registers to quiet a general rule on its own boundary types.Run `commandments  --help` for a command's forms, options and notes — every help screen is projected from the command itself, so it is never out of date.

Configuration
-------------

[](#configuration)

**You don't have to configure anything.** Every detector is enabled out of the box. Configuration is opt-out: silence a rule, tune a threshold, or add a detector of your own.

A commented `.commandments/config.php` is scaffolded on install. It returns a closure given a `Config`; no framework required, the CLI loads the file itself:

```
