Twenty-two skills that carry a project from a brain-dump to a merged PR — and beyond it, to the release notes — turning every code review into a permanent convention along the way. Extracted from a real production codebase, dogfooded one phase at a time, and refusing to forget what it has already learned.
Sysop is a self-improving development workflow for Claude Code. Its core idea is simple: when a code review keeps catching the same mistake, that finding stops being one-off feedback and becomes a written convention the workflow consults automatically on every future task. To be precise about the term: promoting a finding to a convention is a human judgment call — what's self-improving is the enforcement, which from that point on is automatic and permanent. Reviews get cheaper over time because the workflow stops re-finding what it already knows. So far that loop has distilled nearly 3,300 review findings into 78 enforceable conventions across two codebases — and § IV shows what the archive measures, and, just as carefully, what it can't.
I built it to solve a problem. I was shipping a production app solo, generating far more code than I could review line by line. So instead of inspecting every line, I built a system where the review is structural: deterministic gates plus adversarial plan review. Agents, like staff, make mistakes — so you need a process that catches them when the code is written and again before it deploys. Adversarial plan review is handing a colleague your spec and asking them to tear apart your assumptions — a review rigorous enough to come back with the line numbers that need to change. Recurring defects get rarer by design, not because I'm eyeballing every line.
The instinct comes from running a team that shipped real systems: analysts and economists building SQL and front-end tools, plus contract developers. You can't read every line your people write, so you put a process in place that lets you ship anyway.
There's a quieter design constraint underneath all of it: the context window. It turned out to be the most precious resource in this whole system. Even when you stay comfortably inside the limits, an instruction given early in a session has to compete with everything generated since. The model predicts one token at a time, and the more context that has piled up between that instruction and the work happening now, the less it weighs on what comes next. The rule is still in the window; it's just too far from the work to carry much signal. Piling every rule into one standing document makes this worse, not better.
So the workflow is built to nurture the window rather than fill it. Conventions live in glob-keyed maps, and only the entries matching the files a task actually touches get pulled in, at the two moments they matter: during planning and after implementation. Skills load when invoked, not as permanent preamble. Hooks inject their guidance at the instant of the event they handle, not a thousand turns earlier. The principle throughout: don't tell the model everything up front and hope it remembers. Hand it the applicable instruction when and where it applies, so it's actually applied.
Most teams treat code review as a checkpoint — gate-keep the PR, ship it, move on. Sysop treats every review as a data point. When the same finding recurs across PRs, it gets promoted from "feedback on this change" to a written convention that future planning and implementation steps consult automatically.
A planning front door — /intake (Phase 60) — turns a brain-dump into a populated, validated
tasks/index.yml. From there, four lifecycle skills — /next-task, /claim-task,
/document-work, /review-close — carry a single task from queued to merged. Two quality skills
(/codebase-review, /security-audit) generate work back into the queue; a third
(/test-audit) assesses the test suite's standing health. One orchestrator
(/auto-build) fans the same lifecycle across N tasks in parallel. A read-side pair answers a
project's two standing questions — /roadmap (what's left, in what order) and
/daily-summary (what happened) — and /release bundles the merges since the last
tag into a changelog entry, a semver bump, and an annotated tag. Two hooks
(PermissionDenied, SubagentStop) handle the boring mechanics so the skills can stay short.
Per-pack convention_map.md files are the persistent memory of the workflow. Each entry traces back to
the review round where the pattern was first observed. /claim-task consults them before planning and
after implementation; /codebase-review uses them as the rubric. Promotion into the map is a human
decision; everything after it is enforcement. And the loop reaches past one repo:
/contribute-convention takes a locally-promoted convention, strips your project's fingerprints
down to placeholder vocabulary, and files it upstream as a pack proposal — the give-back half of the same loop.
Sysop isn't a productivity skin you sprinkle on any repo. It pays for itself in three specific situations, and asks more than it's worth in a fourth. Name yours before going further.
You, shipping a real product alone. The case the workflow was built for. You're a competent engineer producing more code than you can review line by line, and you can't add reviewers. The review has to become structural — deterministic gates, written conventions, adversarial plan critique — instead of eyeball-by-eyeball. The compounding payoff (§ IV) is the reason a solo builder can hold the line on quality at team velocity.
A team shipping via agentic tools. Agents generate diffs faster than reviewers can read them.
The workflow puts gates between generation and merge — convention checks, plan critique, batched cleanup — so
autonomous code doesn't reach main unread. The value here is risk posture as much as throughput: same speed,
fewer of the predictable failure modes (schema drift, naive auth, silent data loss, half-applied fixes) sneaking
through. One boundary to name up front: the coordination Sysop provides is between parallel sessions —
locks and worktrees keep concurrent agent work from colliding — under a single human reviewer. Two people
sharing one queue (assignment, review handoff, who runs /review-close) is out of scope today: a
deliberate non-goal, not an oversight.
A builder growing into the judgment the workflow assumes. Sysop's gates ask for calls —
approve this plan, promote this convention, accept this risk — that presume review experience. Guided
mode (Phase 76; opt-in via one section in your project's CLAUDE.md) re-shapes those gates
for someone still building that judgment: the skill states the decision plainly, stress-tests its own
recommendation, and hands you only the calls that are genuinely yours. An honest tradeoff gets pros and cons;
a false choice — a security hole dressed as an option — gets collapsed, not offered as a peer; a call you
couldn't fairly weigh gets a conservative default and a note explaining why. The review bar doesn't move —
the deterministic gates are unchanged. What changes is how much of the reasoning you get to see.
Not a fit: anything you can hold in your head. Side projects, quick scripts, prototypes, weekend tooling — anything where reviewing your own diff costs less than running the process. Sysop assumes the codebase is big enough that structural review pays for itself. If it's small enough to keep in your head, you don't need this; the overhead won't earn its keep.
Everything in this document — the lifecycle, the parallel orchestrator, the convention loop — assumes a populated
tasks/index.yml. For most of Sysop's life you filed that by hand. Phase 60 added
/intake: a planning front door that runs the brain-dump-to-queue loop with you, writes the
tasks, and validates them — while leaving every judgment call that matters to you.
Ask it more than you think. The strongest move most people miss with these tools is using them
upstream of code: as a sounding board for scope, business model, tech stack, schema, pricing — the whole shape
of the thing before any of it gets built. The trick is how you ask. Propose an idea and tell the LLM
to tear it apart. Read the critique, keep what's real, drop what's reflexive, decide. The LLM can pressure-test
the structure of an idea; it can't tell you whether the idea is worth building. That judgment is yours, and the
workflow doesn't pretend otherwise — /intake is built around exactly this boundary: it argues its
own decomposition calls, but it will not adjudicate whether the venture is worth pursuing. The same adversarial
primitive the workflow applies to plans (§ I) you apply to the premise, one layer up.
What /intake does. A few rich turns, not a wizard. You brain-dump; it plays the idea
back so you can catch where it misheard you; it acts as a sounding board, arguing the weak spots; then it proposes
phases, sorts them by priority, and emits a phase-one slice into tasks/index.yml with body
files alongside. Decomposition follows a calibrated rubric (_shared/decomposition-rubric.md, Phase 60b)
— one-sentence done-conditions, effort kept independent of blast_radius,
depends_on reserved for physical impossibility. The skill is re-enterable: later phases stay coarse
until you come back to sharpen them.
Two legs of done. The floor is mechanical — validate_tasks.py passes in pre-commit,
so a malformed queue can't land. The ceiling is human — you read the slice and confirm it's the work you meant.
Intent that should outlive a single session lives in two optional, consumer-owned files at the tasks/
root: vision.md (what this is) and decisions.md (what you've ratified and why), so the
next /intake pass re-checks itself against them instead of relitigating settled calls.
And when the project predates Sysop entirely — code shipped, a ROADMAP.md or an issue tracker full
of backlog, no intent layer — /onboard (Phase 88) runs the adoption: with consent it reads the
repo's own evidence and drafts vision.md + decisions.md for you to confirm
(observations recorded as fact; inferred rationales explicitly marked, never asserted), and/or imports the
existing backlog — a roadmap file or open GitHub issues — into the queue, each task provenance-marked surfaced_by: [imported] so
downstream batching treats its effort numbers as the archaeological guesses they are — then hands off to
/intake for everything after.
Between planning sessions, single tasks arrive constantly — a bug spotted in a screenshot, an idea mid-review.
/add-task (Phase 89) is the thirty-second capture for that moment: one thought in, a validated,
deduplicated queue entry out, uncommitted for your sign-off. It never creates phases or touches the intent
layer — a phase-shaped thought gets routed back to /intake.
You don't use Sysop once. You use it across the arc of a project — plan, execute, maintain — and the value compounds across all three.
convention_map.md (§ IV) before and after every implementation, so the gates aren't friction — they're how the workflow ships clean without a human reading every line./codebase-review and /security-audit generate batches in review_tasks.md. /auto-fix handles mechanical fixes in parallel (default 4 agents); /auto-judge handles flagged batches with Opus judgment. Both accept a --merge flag that runs overlapping batches sequentially — same-file work waits its turn so the second batch sees the first batch's fixes already in place. /pr-dependabot (§ III) handles dependency upkeep on the same cadence, and /test-audit assesses the standing health of the test suite itself — coverage the diff-scoped gates never look at, and existing tests worth retiring./daily-summary reads the git log and the queue and answers what happened — yesterday in detail, the week in shape. /roadmap answers what's left and in what order, computing 1–3 orderings from the queue's own signals. When enough has merged, /release bundles the commits since the last tag into a proposed semver bump, a changelog entry, and an annotated tag — dry-run by default, person-gated like every merge.
The happy path for a developer working on a single feature or fix. Each skill hands off to the next via the filesystem
(tasks/index.yml, .locks/, .pending-docs/) so any step can be re-run independently.
/document-work) inherit the session model.
current_focus: true, sorted by effort low→high..pending-docs/<branch>.md for senior review.## Test decision and confirms it against the actual diff; the manual-smoke gate; merge to main; archive the task; clean up branches.
Two skills that sit next to the lifecycle rather than inside it — useful for ad-hoc planning and for surveying state
when multiple branches are in flight (typically after an /auto-build batch — § II).
/claim-task Step 7 (_shared/adversarial-review.md); surfaces findings, presents a revised plan for approval before any execution. Phase 58a added a source-verification dimension: external SDK or framework calls with no in-repo precedent get flagged unverified rather than trusted on faith.Doc-Work: git trailer that lets it classify documented vs undocumented branches without grepping prose. Phase 54 made the read-only guarantee structural — a disallowed-tools guard (Claude Code 2.1.152) on both /sitrep and /next-task so neither can mutate state even when asked.
When the queue holds several independent tasks, /auto-build runs the same lifecycle in batched parallelism.
Architectural tasks solo, migrations solo, cross-module capped, complexity-sum ceiling, and a heavy-solo invariant
(any single task with weight > K = 6 runs alone, never alongside siblings — Phase 42a). Default N = 4.
/codebase-review and /security-audit are the workflow's task generators. Their output isn't a comment
thread or a checklist — it's structured entries in review_tasks.md. /triage classifies each batch
as auto or flag; /auto-fix and /auto-judge drain the two pools in parallel.
Findings have to survive the same gates as feature work.
Neither generator opens with the model. A review's first pass is deterministic — the shared check
registry (checks.yml): grep rules for the mechanical convention violations, a
language-server pass (pyright/tsc diagnostics), Semgrep AST rules for the
patterns grep can't express, lint and dependency-audit stages riding the same plumbing. One finding
shape, one baseline, one blocking gate — those findings file themselves, no model in the loop. The
model reviews on top of that floor, and the language server arms it a second time on the way out:
each novel finding is amplified across the codebase, symbol-anchored ones through
find-references — which resolves imports, aliases, and renames — so a promoted convention
lands as a task at every call site, not just the one place an agent happened to look. Judgment gets
spent where tools can't see; enumeration goes to the tools.
review_tasks.md; /triage splits the pile into auto and flag; /auto-fix and /auto-judge drain the two pools in parallel (or sequentially via --merge when batches share files).
convention_map.md, files findings as batched tasks (Batch 1 / Batch 2 / …) with effort + blast_radius pre-populated. Drained by /auto-fix.security_map.md. Pack-aware (python / postgres / nextjs-react / llm) so findings cite the right CWE and remediation pattern./intake; it never writes a test or claims a task itself./auto-fix: read each pending batch, classify as auto (fully mechanical) or flag (needs judgment), persist the verdict back into review_tasks.md. Idempotent, so it's safe to re-run. Prerequisite for the two drainers below.--merge handles file-overlapping batches sequentially so the second batch sees the first's fixes.--merge shape, but each sub-agent is Opus and can FIX, DROP, or FAIL a task after adversarial re-reading. The two drainers run concurrently against disjoint pools.Phase 53 added the workflow's first GitHub-touching skill — the exception to this section's title, scoped to bot-authored dependency PRs only.
--execute to act. A client-side replacement for native auto-merge, which Free-plan private repos don't get. Classifier validated against ~50 real PRs.
This is the thing. Every other section is plumbing for this one. When a finding from /review-close recurs across
two review rounds, it gets promoted into the relevant pack's convention_map.md. From that moment on,
/claim-task consults it during planning and after implementation, and /codebase-review
uses it as a rubric on future passes. The workflow forgets nothing.
Each entry is a glob pattern, a one-line rule, and an attribution: the review round where the pattern was first surfaced. The round attribution is load-bearing. When an entry later turns out to be wrong, you can find the review that promoted it and learn what changed.
**/*.py · Use yaml.safe_load, never yaml.load without a Loader (Round 12) · Subprocess calls go through shared_cli.run(), not raw subprocess.run (Round 23) · No bare except: — catch a specific exception or re-raise (Round 7) tests/**/*.py · Integration tests use a real database, never mocks (Round 4 — burned by mock/prod divergence)
The promotion history is the workflow's audit trail.
The flip side of forgetting nothing: entries can become wrong. A convention that was right at round 12 can be stale by round 60 — the codebase moved, the library fixed the footgun, the rule outlived its reason. The round attribution is the first concession to that: a bad entry traces back to the review that promoted it, so it can be retired deliberately rather than worked around forever. The second is a backward sweep that runs every review round, alongside the forward coverage audit: it flags the statically detectable staleness — a map section whose glob no longer matches any file (a removed category), and a bullet citing a helper that has since been renamed or deleted (a dead reference) — at the moment the code moves out from under the rule. What stays manual is the decision: both halves surface candidates, a human adjudicates them. And the harder, judgment-driven demotion — a blocking rule that keeps firing false positives long after it stopped catching real ones — is now built too: each false positive a reviewer triages away is recorded as a stale-verdict in a per-rule ledger, and once a rule accrues them across two or more rounds, the review session prompts to retire it, demote it to advisory, tighten it, or keep it. Anyone who has maintained a lint config for five years knows how that debt accrues; the map differs in keeping the receipts and, now, in both noticing out loud when an entry's referent disappears and retiring the rules that have quietly gone wrong.
Measured, not asserted — 71 review rounds and 3,298 findings from the upstream project's review archive, February through July 2026. The honest version first: raw findings per round go up, not down. The rubric grows with every promoted convention, and later rounds file one task per call site where early rounds bundled. Many findings are process-induced — total findings track the rubric's size, not the code's health.
What falls is severity — and then it stays down. The cleanest metrics need no denominator at all: the critical share of findings fell from 21% in February to 5% in April — the last full month before Round 70's retro-application sweep — and then, that sweep aside, held in the low single digits through partial July (17% in May during the sweep, 0% in June, 5% in July) while the rubric kept growing. It is not a monotonic decline; the point of the claim is that the severe share dropped from roughly one-in-five to the low single digits and has stayed there. The share of low-severity findings mirrors it, rising from 22% in February to a 75% peak in June, 65% in July. Activity-normalized rates agree on direction: criticals per 10,000 effective lines changed fell roughly 3× from the March peak to May, lower still through June and partial July. Reviews don't get smaller; they stop finding things that matter.
Two confounds were checked rather than assumed. Commit size shrank ~5× across the interior of the period (median 65 → 13 lines per commit by May), which inflates any per-commit rate — hence the headline rests on denominator-free shares, corroborated per-line. And severity labels are the reviewer's own; the rubric is impact-categorical (security, data integrity, crash potential), and the late rounds labeled map-enforcement findings critical rather than discounting them as known — but the direction of any labeling drift can't be fully pinned, so treat the shares as the primary evidence and the rates as the check.
And the limits, plainly — stated because they bound how much the chart can carry. This is one project, and the loop's author ran it, ran the reviews, and adjudicated every severity — n = 1 and self-graded, which no re-analysis of the same archive can fix. The labels have moved at least once: June's lone critical was reclassified out during a later archive consolidation, so June's 0% is one bookkeeping decision away from ~1.5%. The tail is thin — 68 findings in June, 37 in partial July — so a single bad round would move the late shares by double digits. And the strongest rival explanation is ordinary maturation: young codebases surface their worst findings early and harden over time with or without a convention loop, so some of the 21% → 5% fall was coming anyway. Nothing in Fig. 7 separates the two hypotheses.
What would separate them is specified in the method notes (docs/analysis/REPORT.md) and has not
been run: partition findings into classes covered by an already-promoted convention at the time of the round
versus classes nothing covered, separate new-code findings from retro-application backfill (Round 70 files
covered-class findings by design), and track both per line of diff. Maturation depresses both
classes together; the loop selectively suppresses the covered ones. That analysis needs the upstream
per-finding archive, which is private — the round-level dataset that ships here cannot answer it. Until it
runs, the claim this section makes is the narrower, defensible one: the severe share fell early and stayed
low while the rubric grew, and where a convention is mechanized, its machine check makes silent recurrence
of that finding class structurally harder — a prose-only convention's gate is the reviewer re-armed with the
map. What would falsify it: covered classes recurring in new code at the uncovered
classes' rate, or a fresh project that starts with the map pre-loaded and still produces February's
one-in-five critical share.
docs/analysis/REPORT.md. Mined from the upstream project's review_tasks archive (71 rounds, 3,298 findings) and its convention history (78 entries, all dated). The mined dataset ships alongside (docs/analysis/gdp_review_metrics.json), and the miner that produced it is included for provenance — re-mining needs the upstream repo's private archive, so verify against the dataset.
Claude Code 2.1.x added two hook points that Sysop leans on heavily. The hooks let skill prose stay short — instead of writing escape instructions inline for every possible permission denial, the hook injects them automatically. Neither hook can authorize anything. The real gate is the human invoking the skill and, for protected operations, typing the escape — the hooks only remove the re-explaining.
!-prefix is a user-side control the model cannot emit.
additionalContext showing the exact !-shell-escape for the human to type at the next prompt. It grants nothing; the person typing the escape remains the actor — and is expected to read the command before typing it; the relay is a request, not an instruction. Scoped to three matchers (protected-branch push, --delete push, protected-branch commit), each one a command already in the settings.json allow-list that the classifier overrides anyway; unmatched denials pass through untouched. Collapsed four paragraphs of /review-close escape prose into pointers.TASK: / STATUS: / WORKTREE: / …) into JSON so the parent skill doesn't have to grep the return text. Phase 54 added an agent_transcript_path fallback and message_source provenance, retiring the earlier timing caveat — the parser no longer depends on winning a race against the sub-agent's final message.
/plugin marketplace add getsysop/sysop then
/plugin install sysop@sysop plus any packs. Delivers namespaced slash commands like
/sysop:claim-task.
bash install.sh <target> --packs python,postgres works with any agent (or none) —
or --packs auto to detect the stack from the target's own markers (Phase 75). Writes scripts, git hooks,
permission settings, and per-pack convention maps. Updates via bash scripts/sysop-update.sh from the consumer
side once $SYSOP_SRC is set.
Since Phase 69, skills that pin a model declare a role — reasoning, mechanical, or
quick — rather than a model id (skills without a pin simply inherit the session model). Install resolves roles through
.claude/served_models.yml (default mapping: opus · sonnet · haiku); a consumer remaps any role in
served_models.local.yml, which is never overwritten and layered on top — so an upstream model
sunset and a local preference both survive the next update. The default served: allowlist
also pre-lists fable — nothing maps to it by default, but opting the deep-reasoning tier into
Claude Fable 5 is a one-key local override (reasoning: fable).
consumer-repo/ ├── tasks/ # queue lives here │ ├── index.yml # source of truth · validated on every commit │ ├── vision.md # optional intent layer · /intake-authored, consumer-owned (Phase 60a) │ ├── decisions.md # ratified product decisions · re-checked on re-intake (Phase 60a) │ ├── open/ # task body files for status: open │ └── archive/ # completed task bodies (git mv from open/) ├── .claude/ │ ├── settings.json # permission allow-list + hooks │ ├── convention_map.md # core + per-pack content concatenated │ ├── security_map.md # OWASP map · pack-extended │ ├── checks.yml # concat of pack check fragments │ ├── served_models.yml # role → model mapping · override via served_models.local.yml (Phase 69) │ └── *.project.{md,yml} # consumer-authored extensions (Phase 24a) · promotion writes land here (Phase 78) ├── scripts/ │ ├── claim_task.sh # lock + worktree + branch creation │ ├── close_batch.sh # review_tasks.md batch closure │ ├── validate_tasks.py # schema enforcement · runs in pre-commit │ ├── permission_denied_hook.py # Phase 36 · classifier escape injection │ ├── parse_subagent_envelope.py # Phase 37 · structured envelope parsing │ ├── run_checks/ # Phase 49 split · Phase 61 coverage.py crown-jewel gate │ └── sysop-update.sh # the consumer-side update entry point ├── SYSOP_ISSUES.md # friction log · seeded once, project-owned · /report-issues files upstream ├── .locks/ # lives in main repo · git-common-dir resolved (Phase 32) ├── .pending-docs/ # /document-work writes here · /review-close consumes └── .subagent-envelopes/ # Phase 37 · SubagentStop hook writes JSON here