Architecture
An explanation. How Procoder is built, and the three contracts that shape every design decision in it.
The shape
Three layers, and the arrows only point the way they are drawn: the binary answers the agent, and never reaches back into the agent's files.
flowchart LR
subgraph AGENT["the agent — Claude Code, Cursor, Codex, any host"]
direction TB
SKILLS["skills / commands<br/><i>thin Markdown callers</i>"]
HOOKS["hooks<br/><i>PostToolUse on every write<br/>SessionStart</i>"]
end
subgraph BIN["the binary — one Go executable, no runtime dependencies"]
direction TB
CMD["cmd/procoder"]
DOM["internal/…<br/>gate · format · security · lint · docs<br/>ciops · infra · maintain · codeindex<br/>spec · plan · todo · debt · lessons"]
CMD --> DOM
end
subgraph STATE[".procoder/ — the repository's own files"]
direction TB
CFG["config.toml · PRINCIPLES.md<br/>rules files"]
WORK["specs/ · plans/ · todo/<br/>backlog/ · adr/"]
DERIVED["index/<br/><i>derived, gitignored</i>"]
end
AGENT -->|invokes| BIN
BIN -->|"findings, fixed content, verdicts<br/><b>never a write to your code</b>"| AGENT
BIN -->|"reads rules<br/>writes only its own state"| STATE
The binary is the whole engine. Skills are instructions about calling
it; hooks are fixed lifecycle points that call it; adapters for other
agents point at the same files. Cross-compiled per platform into
dist/, committed with the plugin: no npm, no network at hook time,
air-gapped installs included.
Contract 1 — P-CONTROL: the agent stays in control
Tools compute results and hand them over; nothing modifies code,
files, or state behind the agent's back. The write hook does not
format your file — it hands the agent the formatted content to review
and write. templates, agents, spec template, todo add all print
content for the agent to write. The two exceptions are Procoder's own
state (todo close flips a Status line; the index refreshes itself),
never your code.
Why: an agent that experiences its tools as collaborators uses them; an agent that gets silently overridden routes around its harness. And every change stays reviewable in one place — the agent's own actions.
Contract 2 — unchecked is never clean
A tool that is missing, times out, or returns unparseable output yields
NOT checked — counted by the gate as failing, never collapsed into
"no findings". This rule shows up everywhere: formatter verdicts are
clean/unformatted/unchecked (three, never two); a bare package.json
without a lockfile is an explicit unscannable gap; an unreadable rule
copy is UNREADABLE, not "missing"; the docs report says "offline checks
only" when it skipped the network. If a Procoder report says clean, the
check ran.
Contract 3 — D-OVERRIDE: the repo's files win
Every domain reads its rules from .procoder/ and the repo's version
beats the built-in default, wholesale: config policies, principles, docs
and security rules, review rubric, lessons ledger, templates. Procoder
imposes process, not opinions — a repo that wants different thresholds,
different badges, or entirely different principles writes them down and
the binary follows.
The mirrors-and-drift pattern
Several artifacts must exist in places Procoder does not control —
GitHub reads the PR template only from .github/, each agent host reads
its own rule path, each host manifest carries a version. Procoder's
pattern for all of them: one master, byte-pinned copies, drift blocks
the gate. mirrorSync (PR template), portability.Check (ten agent
rule files + six manifest versions), VersionSync (README, site index,
changelog entry per release). A copy is never edited directly; the
master changes and the copies are regenerated.
The gate is one code path
procoder check, procoder git, and CI all call the same Collect.
There is deliberately no way for the local gate and CI to disagree about
what the rules are — a green local gate that fails CI is defined as a
bug; when environment differences surface one (line-ending rewrites,
platform tool gaps), the fix lands in the shared path.
The write hook, end to end
PostToolUse on every Write/Edit: payload on stdin → format verdict
(fixed content included if unformatted) → lint findings for that file →
markdown/doc checks if prose → secrets scan → doc-drift notes → index
refresh. Answer in the same turn, file untouched. Cost in practice:
sub-second per write.
Testing philosophy
Every controller is pinned by refusal-path tests (the thing must say no, and name why), every canonical list by a both-directions pin (usage ↔ commands ↔ docs), every mirror by a drift test, and the instruments themselves carry known-good/known-bad fixtures — a checker that cannot catch its planted bad fixture is not trusted. Fixtures resembling scanner targets (secrets, debt markers) are assembled at runtime so the repository's own scans stay clean.