I just published engineering-skills, a personal pack of agent skills I now use across every project. It is built on top of Matt Pocock's mattpocock/skills. Most of the engineering doctrine is his. The packaging, the runtime, and a few opinions are mine.
Where it started
I had two things that worked well separately and refused to talk to each other.
On one side, an AFK loop runner I wrote, looper. Stateless, CLI agnostic, driven by a prompt file and a sentinel. I use it for unattended work: pick a ticket, implement it, validate, commit, repeat.
On the other side, an inconsistent collection of slash commands and one giant AGENTS.md per repo. Fine when I was the one driving. Painful when looper was driving, because the rules I cared about were spread across skill bodies, sidecar files, and prose in AGENTS.md. Same doctrine, three places, two voices.
Then I bumped into Matt Pocock's two repos. mattpocock/skills is a clean set of slash commands I trigger as a human. mattpocock/sandcastle is a containerised AFK loop. Same author, same engineering principles, but at first glance they looked contradictory: the tdd skill in skills/ is full of "confirm with the user" gates, while the implement-prompt.md in sandcastle/ is five lines of pure RGR. Either one of them was wrong, or I was missing something.
The unlock
I spent some time reading both repos side by side and writing a comparison file. The realisation, once it landed, was simple: HITL and AFK are two delivery surfaces for the same doctrine, not two different doctrines. The verbose tdd skill and the terse implement-prompt.md agree on the rules. They differ on whether a human is in the room.
The doctrine itself ("test through public interfaces", "mock at system boundaries only", "deep modules", "never refactor while RED") lives in sandcastle/.sandcastle/CODING_STANDARDS.md, near verbatim from the tdd skill's sidecars. Sandcastle's prompts pull it in with @CODING_STANDARDS.md. The skill bundles the same text as progressive disclosure. One source of truth, two delivery shapes.
That single observation reorganised everything for me. If doctrine is one thing, and skills are just delivery, then:
- There must be one canonical place for the doctrine in every repo.
- HITL skills should reference it, not duplicate it.
- AFK loops should read it directly, never invoke the HITL skill (which would block on user input).
- Anything HITL-only must be marked, so an AFK runner does not pick it up by description match and hang.
The whole project flowed from there.
The audit
Before writing anything new, I went skill by skill through mattpocock/skills and decided three things for each one: keep upstream, fork, or skip.
- Keep upstream, no changes:
diagnose,improve-codebase-architecture,zoom-out,grill-me,write-a-skill. They are generic and self-contained. - Fork and patch:
grill-with-docs,tdd,to-issues,to-prd,triage. Each one needs at least one small adaptation: GitHub-only hardcoding, a reference to my newCODING_STANDARDS.md, or a label vocabulary tweak. - Rebuild:
setup-matt-pocock-skills. Same shape (interview, write per-repo files), different output (my own doctrine layout instead ofdocs/agents/*). - Skip: a few that did not fit (Husky-based pre-commit setup, shoehorn migration, course scaffolding, Claude-only sub-agent guardrails).
Net result: 5 forks, 5 verbatim, plus my pre-existing repo-create, repo-setup-ci, repo-branch-protection on the GitHub bootstrap side, plus my own code-review and fix-code-review. Plus one new skill, setup-harness, to scaffold the per-repo files.
The architecture
The simple version, without internal jargon: there are skills you invoke (slash commands), files in your repo that those skills read or write, and an AFK prompt the looper runs in headless mode. The hard rule is that the AFK prompt never invokes a skill. It reads the same doctrine files the skills point to, and it does so directly.
Doctrine flows like this:
- A canonical source lives in one folder of the package, authored once.
- A sync script copies it into the relevant skill bundles (so the skill works offline if the repo has no
CODING_STANDARDS.md) and into the templates the bootstrapper writes. - The bootstrapper writes a single per-repo doctrine file (
CODING_STANDARDS.md) that overrides the bundled fallback when present.
One source at authoring time. Identical copies at distribution time. Per-repo override at runtime. No drift if the sync script runs in CI.
The bootstrapper
/setup-harness is the closest thing this package has to a hero. It runs once on a fresh repo, asks six questions, and writes:
CONTEXT.mdfor the domain glossary.CODING_STANDARDS.mdfor engineering doctrine.CODE_REVIEW.mdfor reviewer doctrine.AGENTS.md(with a## Agent skillsblock pointing at the above), and aCLAUDE.mdsymlink for tools that look for that name.docs/adr/0000-template.mdfor ADRs..looper/config.jsonand.looper/PROMPT_BUILD.md.scripts/run_silentfor output management.- Optional
prek.tomlfor pre-commit hooks. - A
.gitignorefragment.
Everything is parametrised over the answers (package manager, base branch, build commands). Re-running the skill is safe. It edits in place where files already exist and skips where they have content.
The AFK prompt
PROMPT_BUILD.md is the looper-side counterpart. It is the file that actually drives unattended work. Its life cycle:
- Set up a worktree under
.worktrees/feat/prd-<n>. - Find a ready sub-issue of the PRD via the
## Parentbody reference and theready-for-agentlabel, and only the ones whose## Blocked bylist is fully closed. - Read the sub-issue, the parent PRD,
AGENTS.md,CONTEXT.md, andCODING_STANDARDS.md. - Apply TDD per
CODING_STANDARDS.md. One acceptance criterion, one RED to GREEN cycle. - Run the four validators (build, lint, test, typecheck) wrapped in
run_silent. Three attempts, then stop and emit the sentinel. - Commit with a real message: a 2 to 3 sentence prose paragraph in domain terms, then a
Key changes:bullet list, then the trailers. Push. - Stop. Looper respawns for the next iteration.
- When all sub-issues are closed, open a PR with a body that summarises the whole PRD, not just the last commit.
Every variable that varied between repos got hoisted to a placeholder: base branch, package manager, the four validator commands, the sentinel. The interview answers fill them at scaffold time. Anything looper substitutes per iteration (the PRD number, iteration counter, session id) is left untouched by the bootstrapper.
Code review, separately
While building this, I realised the same shape applies to code review. I had been using a Claude sub-agent for code review, which was fine inside Claude but did not port to other CLIs (I am moving to pi for unattended work, and a post on that is coming soon). So I lifted the reviewer prompt out of the sub-agent file into a doctrine file, CODE_REVIEW.md, treated symmetrically with CODING_STANDARDS.md. The bootstrapper writes both. The /code-review skill reads the repo file (or falls back to the bundled copy), evaluates the PR against project doctrine and posts the review as a PR comment. The /fix-code-review skill fetches review comments, applies fixes, validates, commits, pushes. No manual gate. If a comment is genuinely ambiguous, it picks the conservative interpretation and notes it in the commit message.
The win here is the symmetry. CODING_STANDARDS.md describes how to write code in this repo. CODE_REVIEW.md describes what to look for when reviewing it. The reviewer is graded against the same rules the writer was. If a stack-specific concern matters (Prisma migration safety, Polar webhook replay protection, whatever), it gets appended to both files in the same commit.
Smoke testing on a real repo
Before promoting anything, I ran the bootstrapper on a real repo I had cloned for the test, an eniem-derived Next.js project. It surfaced two real issues:
.gitignoreappend needs to be per line, not all-or-nothing. The repo already had.worktrees/from old eniem days but not.looper/sessions/. Naive matching missed the second line.- Looper-runtime placeholders need an explicit "do not substitute" list in the bootstrapper. Without one, a future agent could trash them.
Both are now spelled out in the skill body.
The lessons
A few things I would tell myself at the start of the week:
- Read the upstream you depend on, all of it, before diverging. Half a day of reading saved me a week of bad decisions.
- One source of doctrine, multiple delivery surfaces. If a rule lives in two places, eventually they will disagree.
- HITL versus AFK is a delivery boundary, not a forking point. Same rules. Different gates.
- Mark HITL-only skills explicitly. Without the marker, AFK runners will pick them up via description match and hang on the first user-input gate.
- Bootstrappers should write everything they need, then optionally hand off to the next bootstrapper. Setup is a chain, not a monolith.
- A real smoke test surfaces things specs do not. No amount of design review beats writing into a real repo.
Want the speedrun version?
engineering-skills is the generic baseline. It gives any repo a coherent agent harness, but you still have to build the actual product on top.
If you want to skip ahead, eniem.dev is what I use. It is a production-ready Next.js codebase with the AI workflow described in this post wired in from day one, plus the things every SaaS needs already shipped: authentication that just works, payments without the headache (provider-agnostic), React Email templates, file uploads with CDN, rate limiting, Web3 wallet auth (SIWE), end-to-end type safety, and deploy-anywhere defaults. Same skills, same doctrine, plus the production code for the parts you would otherwise rebuild every time. More at docs.eniem.dev.
If you just want to read the code for the skills package, the repo is 0xtiby/engineering-skills.