Skip to content

The ten domains

Reference. Procoder organises senior-developer work into ten domains. Each follows the same architecture: the binary computes findings, the write hook hands them to the agent in the same turn, the gate carries them at commit time, a skill packages the workflow, and every rule is repo-overridable (D-OVERRIDE). This page is the detailed reference: what each domain checks, with what, what blocks, and where its knobs live.

Reading the tables: blocks fails the gate and CI; reports informs the agent's judgment; NOT-checked means the tool was missing or failed — counted as failing, never as clean.

1. Security

Runs in the write hook (changed file), the gate (changed set), and procoder security --deep (whole repository).

Check Tool Verdict
Secrets in any written/changed file gitleaks (with a legacy-CLI fallback for old installs) blocks — names rule and location, never the value, and orders removal AND rotation
SAST over the repository (--deep) semgrep, community rulesets ERROR severity blocks; WARNING reports
Dependency vulnerabilities (--deep) osv-scanner over explicitly named lockfiles CVSS ≥ 7.0 blocks; below reports
Scanner missing or output unreadable blocks as NOT-checked

Details worth knowing: manifests are enumerated explicitly (osv's own walker trusts git metadata and comes back empty in worktrees); a package.json that declares dependencies with no lockfile is an explicit unscannable gap; false positives are handled by gitleaks:allow trailing comments or .gitleaksignore, each a reviewed decision — the flow is in .procoder/security/RULES.md, which the repo owns.

2. Best practices (lint)

The canonical linter per ecosystem, under the project's own config — Procoder imposes nothing where the repo has spoken.

Ecosystem Tool Baseline when the repo has no config
Go golangci-lint curated set: standard + gosec, gocritic, errorlint, unparam, copyloopvar, nilerr
Python ruff check ruff's defaults
Shell shellcheck shellcheck's defaults
JS/TS eslint plain JS gets eslint's built-in core rules; configless TypeScript is out of scope (a parser would have to be imposed)
Rust cargo clippy clippy's defaults (needs a Cargo workspace; findings filtered to the changed files)
Kotlin ktlint ktlint's defaults
Swift swiftlint swiftlint's defaults
Ruby rubocop rubocop's defaults
Java checkstyle the bundled google_checks; a repo checkstyle.xml wins

Report by default; [lint] policy = "block" in config.toml makes findings block. Lint is judgment where formatting was not — the findings arrive in-turn on every write, and the agent is expected to fix what is real and say why for what is not.

lint --types closes the compile gap: Go and Rust arrive compiled (their linters build what they lint), TypeScript and Python do not — so the flag adds tsc --noEmit (under the project's own tsconfig) and pyright. The agent reaches for it after refactors and renames, where type fallout is exactly what the linters cannot see.

3. Maintainability

procoder maintain — informed judgment, never blocking.

Check Source Notes
Dead-code candidates the index's precise (SCIP) tier exported API is marked — a public surface is legitimately unreferenced from inside
Cyclomatic complexity golangci (gocyclo) / ruff (mccabe), isolated config threshold [maintain] gocyclo, default 15
Function length funlen [maintain] funlen_lines / funlen_statements, defaults 80/50

Deliberate corner-cuts are the sibling discipline: mark them with the debt: comment convention (marker configurable via [debt]) naming the ceiling and revisit condition; procoder debt harvests the ledger and flags no-trigger entries as rot.

Complexity is Go and Python only — gocyclo rides golangci and mccabe rides ruff, and no other ecosystem has a linter Procoder can isolate the metric out of. Length and complexity are reported separately even when they land on the same function: golangci keeps only the first issue per line by default, and since a long function is usually a branchy one, that default hid every length finding until 0.32.6. The dead-code sweep is limited to the index's precise tier, so it answers for the languages a SCIP indexer covers and stays silent elsewhere rather than guessing.

Dependencies age too, and procoder deps is the freshness half of the same judgment — report-only, never blocking:

Check Tool Scope
Outdated direct dependencies go list -u -m, npm outdated, cargo-outdated, pip — where installed the ecosystems whose manifests exist; capped and summarized
Licenses go-licenses Go only — every other ecosystem answers NOT checked
An optional tool missing information, not failure; a tool that errored is a failure

NOT checked is reserved for a license surface that exists and was not read. A repository whose manifest declares no third-party dependencies has nothing to check, and says so — otherwise the reader learns to skim the line in the repositories where it means something. Where Procoder cannot tell (a manifest it cannot parse, or a Python project whose dependencies live in requirements.txt, a Pipfile, or a setup.py that computes them at runtime) it says NOT checked rather than guess "none".

Nothing here decides for you: a major version behind is a fact, whether to take it is a judgment with context Procoder does not have.

4. Performance

/procoder:perf encodes measure-first: baseline before touching, profile before guessing, re-measure after, report the delta with the command that produced it. A fix without a benchmark is a hope.

procoder bench is the measurement infrastructure that discipline used to lack. It runs the repository's benchmarks (go test -bench . -benchmem) and compares each against the committed baseline in .procoder/bench/baseline.txt: ns/op and B/op with a percentage delta, regressions beyond [bench] threshold (default 10) marked and exiting 1, new and vanished benchmarks listed as such. --save records a new baseline — explicit, because a baseline is a decision, not a side effect.

Go only in this version, and the output says so: other ecosystems answer NOT run rather than letting the scope look wider than it is. Results are single-run and machine-local; a baseline recorded on a different GOOS/GOARCH still compares, with a warning attached. Numbers arrive with the conditions that produced them.

5. Documentation

Documentation is a product: correct, presentable, delivered, and complete — completeness has its own blocking checks, because presence checks alone let documentation rot silently.

Check Verdict
Broken relative references, non-compiling Mermaid diagrams blocks (hook + gate)
Version-tracked pages missing the current version; changelog without an entry for the release blocks
A shipped command the docs never mention blocks
A declared feature family the README's narrative stops telling (## README must mention, whole-word, badges/links stripped) blocks
Doc drift (a doc mentions a file you changed), missing API doc comments, badges, README first screen reports
External links (lychee), Pages serving the latest build --external and CI

Rules live in .procoder/docs/RULES.md; this site is built and deployed by the harness's own CI job.

Decisions are documentation too, and they rot differently: prose can be corrected, but a decision rewritten after the fact loses the reason it was taken. procoder adr keeps them under .procoder/adr/ as numbered, immutable records — Context, Decision, Consequences, and a date — where a changed mind writes a new record that supersedes the old one rather than editing it. adr check refuses hollow records, unknown statuses, duplicated numbers, and supersede references pointing at nothing; the audit sweep carries those findings.

6. Clean code (formatting)

Every write is checked against the ecosystem's canonical formatter — gofmt, ruff format, prettier (JS/TS/JSON/CSS/HTML/Markdown/YAML), rustfmt, clang-format (config required — Procoder has no style opinion of its own), shfmt, google-java-format, ktfmt (Kotlin), swiftformat, rubocop (Ruby), dart format, and csharpier (C#). Three verdicts, never collapsed: clean, unformatted (the agent receives the formatted result in-turn and writes it itself), unchecked (tool missing or failed — fails the gate). The file is never touched behind the agent's back; that is P-CONTROL's original case.

7. Testing

procoder test runs the repository's actual suite — not a proxy for it, and not a claim about it. Each detected ecosystem's canonical runner runs and reports separately:

Ecosystem Runner Coverage
Go go test ./... native (-cover)
Rust cargo test not measured
JS/TS the package.json test script, via the lockfile's package manager not measured
Python pytest (where a pytest config or a tests directory exists) native with pytest-cov
Java ./gradlew test or mvn -q test, where the build files exist not measured

Three verdicts, and the third is the point: PASS with counts where the output allows, FAIL with the failing tests named, and NOT run when no runner or test script is present. NOT run is never green — a repository with no suite is told it has no suite, not congratulated. Exit 0 when everything passed, 1 when anything failed, 2 when nothing could run at all.

--coverage reports the percentage where the runner measures it natively. It is reported and never enforced: a threshold turns coverage into a number to farm, and Procoder has no opinion worth blocking on about which lines matter.

The suite reaches the rest of the chain through one knob. With [test] policy = "block" in config.toml, todo close and backlog close story run procoder test and refuse while it is red — or while it cannot be verified at all, because unknown is never done. Left at the default, the verdict informs and nothing refuses.

8. CI/CD/CT

procoder ci and the same checks inside the gate:

Check Verdict
Actions pinned to mutable refs (a tag can be silently repointed) reports; [ci] pin_actions_policy = "block" to block
Missing per-job timeout-minutes reports — a hung job otherwise burns the whole runner allowance
Missing concurrency cancellation reports
Pipelines with no test step reports
Workflow files unreadable blocks as NOT-checked

actionlint runs on every workflow file the agent writes, in-turn.

9. DevOps / IaaS / CaaS

procoder infra — inventory-driven: each tool runs only where its files exist, so a repo without infrastructure pays nothing.

Files Tool Verdict
Dockerfiles hadolint reports
Terraform terraform fmt (reports) · terraform validate (blocks when initialised; says NOT-validated when .terraform is absent rather than failing on providers) · tflint (reports)
Kubernetes manifests kubeconform reports
Helm charts helm lint reports

10. GitOps / GitHub

The finishing discipline — most of it rides the gate:

Check Verdict
Conflict markers in changed files blocks, names file and line
Junk/caches staged (.DS_Store, *.orig, node_modules, …) blocks
Oversized files ([git] max_file_mb, default 5) blocks
AI-attribution lines in commits (procoder scrub for drafts) blocks — the work is the author's
PR-template mirror drift (.github/ vs the .procoder/github/ master) blocks
Agent-layer drift (rule copies vs AGENTS.md, manifest versions) blocks
Commit subject shape (≤72, blank line before body); working on the default branch reports ([git] default_branch_policy can block)

Around the checks, the skills encode the workflow: a worktree per feature (a git practice the skills prescribe — Procoder creates and removes none of them itself), /procoder:pr (docs-impact question, pre-PR self-review, scrubbed template), /procoder:merge (watch-only polling, every review thread answered, the reflection step for anything that escaped, then merge and full cleanup).

Tagging is the last step and has its own controller. procoder release verifies in one pass that every file in [release] files carries the version, that CHANGELOG.md has the matching entry, that the tree is clean including untracked files, that the gate is clean, and that the suite is green under [test] policy — every failure listed together rather than one per attempt. On success it prints the git tag command and stops: the tag is the human's to run, as P-CONTROL requires. Without [release] files the version-sync leg says out loud that it verified nothing.

Beneath them: the code index

Two tiers — universal-ctags for breadth, SCIP for precision — with thirteen queries from find to the call graph, kept current by the hook, consumed by the agent and the domains alike (maintainability's dead-code sweep and the gate's impact lines both read it).

The language matrix:

  • Broad tier (find/search/outline/textual refs/impact): everything universal-ctags parses — 160+ languages including C/C++/C#, Java, Kotlin, Ruby, Rust, PHP — plus Procoder-supplied regex parsers for the two it lacks, Swift and Dart (top-level symbols, approximate by nature).
  • Precise tier (exact refs/impls/callers/graph): where a SCIP indexer exists and is wired — Go (scip-go), TypeScript (scip-typescript), Python (scip-python), Rust (rust-analyzer), and Java/Kotlin/Scala builds (scip-java). A polyglot repository runs every indexer its layout calls for and the results merge into one index; an ecosystem whose indexer is missing or failing stays textual and the build says so per indexer — a textual ref is labeled, never passed off as precise.
  • Rename (index rename): the one write-shaped operation, and it still writes nothing — the language's own engine computes the cross-file rename (Go via gopls) and Procoder prints it as a unified diff for the agent to review and apply. A language without an engine gets the reference worksheet, not a half-right rewrite.

Above them: the quality chain

The domains judge code that exists; the quality chain governs whether the right thing gets built at all — spec, plan, the backlog's milestones/epics/stories worked in sprints, the standalone todo list, and the lessons loop, each with its own refusing controller.