A minimal, cross-harness project scaffold for AI-assisted development. Drop it into any new project to get a consistent design → PRD → TDD workflow whether you're using Claude Code, Cursor, or Gemini CLI.
Browser / mobile: hit "Use this template" above → create new repository → open in Claude Code.
CLI:
gh repo create my-new-project --template victusfate/scaffold --private
gh repo clone victusfate/my-new-project- Single source of truth —
AGENTS.mdholds all agent instructions.CLAUDE.mdimports it;GEMINI.mdreferences it;.cursor/rules/agents.mdcpoints to it. No duplication. - Careful before fast — features start with a structured design Q&A (
grill-with-docs) that locks in vocabulary and decisions before any code is written. - Automatic flow — once Q&A is done, the chain runs to completion (PRD → TDD → review gate) without manual handoffs.
- Minimal viable diff — agents are instructed to make the smallest change that achieves the goal, no opportunistic refactors.
- Quality is scored, not vibed — every file is graded on a four-dimension rubric with cited, weighted deductions; the chain won't open a PR until each changed file scores 10/10.
Start a feature by describing what you want to build. The feature-chain skill fires automatically and runs four phases:
grill-with-docs → to-prd → tdd → review
(Q&A) (auto) (auto) (you)
- grill-with-docs — Agent interviews you one question at a time, sharpens terminology, stress-tests against the codebase, and produces
./docs/<slug>/design.mdwith a canonical vocabulary and Mermaid/ASCII diagrams as structure becomes clear. - to-prd — Synthesizes the conversation and codebase into
./docs/<slug>/prd.mdautomatically. No re-interviewing. - tdd — Derives
plan.mdfrom the PRD, then executes RED → GREEN → REFACTOR one vertical slice at a time, scoring each slice against the quality rubric as it goes. Commits per slice. - Review — Once the slices pass,
code-quality-reviewbrings every changed file to 10/10 on all four rubric dimensions, then the chain presents a summary of what was built, tests passing, and any plan deviations. Prompts you to review before merging.
Skills can also be invoked individually. See docs/skills.md for the full skill list and repo layout.
Every feature is scored against a four-dimension rubric — Quality,
Readability, Encapsulation, Clarity — defined once in
lib/code-quality-rubric.md and shared by every
skill that scores code. Each dimension starts at 10 and every deduction must cite
a filename:line (Score = 10 − Σ(violation weights)), so a score is never a
vibe.
The rubric is woven through the chain, not bolted on at the end:
- tdd loads the rubric as the generative voice — code that would violate it is never written, and REFACTOR is a confirmation pass.
/code-quality-reviewscores the changed files once the slices pass (auto-fix in the chain, review mode standalone). All files must reach 10/10 before a PR is opened, andcreate-prre-runs it as a gate./auditis the standalone counterpart — score any scope of files, ranked worst-first, with--fixto apply small fixes in place.
A mechanical pre-flight runs in CI on every PR via
.github/workflows/quality.yml →
scripts/check-quality-mechanical.sh: file length, magic literals, and
commented-out code, each emitting filename:line citations and failing the build
on any hit.
Overrides (model-driven criteria only). A genuinely irreducible violation can be exempted two ways — both require a reason and appear in the report at zero score weight:
- PR body:
quality-override: <file> — <criterion> — <reason> - Inline (colocated):
// quality-override: <criterion> — <reason>on the line immediately above the offending line (or the first non-blank, non-shebang line for a whole-file criterion). The rationale lives next to the code and survives merge.
Mechanical criteria can never be overridden by either form — the code must be
fixed. A malformed pragma (unknown criterion, blank reason, or missing separator)
is itself a [Clarity/minor] violation.
| Harness | How it reads instructions |
|---|---|
| Claude Code | CLAUDE.md → imports AGENTS.md; /skill-name invokes skills |
| Cursor | .cursor/rules/*.mdc — description-driven activation |
| Google Antigravity | GEMINI.md + AGENTS.md; .agents/skills/ (lazy-loaded) + .agent/workflows/ (slash commands) |
| Gemini CLI | GEMINI.md → references AGENTS.md |
| OpenAI Codex | AGENTS.md directly |
Each feature gets its own folder under ./docs/:
./docs/<feature-slug>/
design.md # Q&A, decisions, canonical vocabulary, diagrams
prd.md # problem, solution, user stories, implementation decisions
plan.md # vertical slices
tdd-log.md # per-slice TDD status
Bootstrap (one-time per repo — installs the sync script, prints next steps):
curl -fsSL https://raw.githubusercontent.com/victusfate/scaffold/main/bin/bootstrap.sh | bashBootstrap does not run the sync automatically and does not change any tracked files. Run the first sync yourself when ready:
bash bin/sync-from-scaffold.shFlags:
--run— also run the sync immediately after install (old behavior).--with-workflow— also install.github/workflows/sync-scaffold.yml.
Update (run whenever you want to pull in scaffold changes, like a dependency bump):
bash bin/sync-from-scaffold.shOr ask the agent — /sync-scaffold detects which path is needed (bootstrap vs sync) and runs it automatically.
The sync script uses git (no curl after bootstrap), compares blob SHAs, and three-way merges files that both you and scaffold have changed. Files with uncommitted local edits are skipped with a warning. The last-synced SHA is stored in .github/scaffold-sync-sha so future merges have a proper base.
Clobber-safe behaviors:
- No silent first-sync overwrite. When no base SHA exists and a target file already exists and differs, the incoming version is written to
<file>.scaffold-newfor deliberate review. The original is left untouched. Diff and merge when ready; re-run the sync to save the SHA once resolved. .scaffold-keep. Add an optional.scaffold-keepfile at your repo root (one path or glob per line;#comments and blank lines ignored). Any matching path is always skipped — even on first sync — and reported as "Kept (consumer-owned)". Use this to protect any file you own locally from being overwritten.CLAUDE.mdis shipped as a starting point. On first sync into a repo that has noCLAUDE.md, scaffold writes one. If you have already customized yours, addCLAUDE.mdto.scaffold-keep— the sidecar or keep-list will protect it from then on.LICENSEis never synced. The sync only writes files listed in.github/scaffold-files.txt, andLICENSEis not one of them. A repo created from the template starts with scaffold's MITLICENSE(a one-time copy from "Use this template") — replace it, delete it, or go closed-source/private as you like. Scaffold will never re-add or overwrite it.
Pinning to a release tag (recommended for reproducible pulls):
# Pin SCAFFOLD_REF in your update script or set the remote to a tag
git fetch scaffold refs/tags/v1.0:refs/tags/scaffold-v1.0A GitHub Actions workflow can be installed at .github/workflows/sync-scaffold.yml — trigger it manually from the Actions tab for a PR-based update flow. Install it with --with-workflow during bootstrap, or copy it manually.
Two lists classify every tracked file in this repo:
.github/scaffold-files.txt— the manifest of files that sync to consumers..github/scaffold-internal.txt— files deliberately held back (this repo's own CI, dev/test scripts, feature artifacts, maintainer tooling).
CI (scripts/check-resolvable.ts → phaseManifestCompleteness) fails if a tracked file is in neither list, or if the manifest lists a file that no longer exists. This makes "ship it unless explicitly held back" the default, so adding a skill's runtime (e.g. tools/<x>/, lib/<x>/) without also shipping it can't silently slip through. The check is keyed on scaffold-internal.txt, which never syncs — so consumer repos (which lack it) skip it rather than flagging their own files.
Future direction — git-native sync. Today's flow is a hand-rolled selective vendoring tool: it re-implements merge (3-way against the stored SHA) and ignore (
.scaffold-keep) over a curated subset that scatters into ~13 shared dirs. That keeps the destination repo simple (files live natively where each harness reads them, no install step) at the cost of a bespoke sync tool plus a manifest that must be kept complete (the guard above). A possible future swap: relocate scaffold's payload under a single owned prefix (e.g.vendor/scaffold/) consumed viagit subtree(realgit pull, history, conflict resolution,git subtree pushupstream) plus an install step that places files into harness paths. That trades manifest-maintenance for an install/symlink layer in every consumer — worth it once we want real history and bidirectional contribution badly enough to pay that cost. Not adopting it now; keepingsync-scaffoldas-is.
The sync flow above is bulk — it pulls every file in the manifest. If you only want specific skills in a specific harness, use hoist-skill instead. scaffold owns the emit so consumers do not need to know the internal canonical+generated layout.
Emit specific skills into a repo:
# From inside a scaffold clone, target another repo
node tools/hoist-skill/run \
--names feature-chain,grill-with-docs,tdd \
--harness claude \
--into ../my-projectAfter a successful emit, the tool automatically writes (or updates) ../my-project/.sync/hoisted — a tab-separated registration manifest recording each (name, harness, ref) triple. Commit this file so the selection survives across sessions and devices.
Emit everything for all harnesses:
node tools/hoist-skill/run --names all --harness all --into ../my-projectList available capabilities:
node tools/hoist-skill/run --listWhat gets written per harness:
| Harness | Files written |
|---|---|
claude |
skills/<name>.md + .claude/skills/<name>/SKILL.md |
cursor |
skills/<name>.md + .cursor/rules/<name>.mdc |
antigravity |
skills/<name>.md + .agents/skills/<name>/SKILL.md + .agent/workflows/<name>.md |
The same clobber-safe contract applies as with the sync script: .scaffold-keep paths are never touched, differing files become <file>.scaffold-new sidecars, and --force overrides that.
The tool emits a JSON manifest on stdout listing every file written, skipped, or sidecarred — pipe it to jq or capture it for CI.
Refresh registered skills (replay on sync):
Once .sync/hoisted exists, re-emit all registered skills from scaffold's current ref in one call — no need to retype the names list:
node tools/hoist-skill/run --from-manifest --into ../my-projectThis is the call a consumer's /pull-scaffold makes to keep hoisted skills up to date. Clobber-safe: locally edited files become sidecars; .scaffold-keep paths are skipped.
Pin to a specific ref:
node tools/hoist-skill/run \
--names feature-chain,tdd \
--harness claude \
--into ../my-project \
--ref v1.2.0The ref is stamped in .sync/hoisted per entry so replay is reproducible.
Plan mode (for curl-only consumers):
Pull-only consumers that have no scaffold clone can fetch just the files they need. --plan prints the exact repo-relative source paths to curl — without writing anything:
node tools/hoist-skill/run \
--names tdd,feature-chain \
--harness claude \
--plan{
"ref": "main",
"harness": "claude",
"sources": [
{ "path": "tools/hoist-skill/run", "required": true },
{ "path": ".claude/skills/RESOLVER.md", "required": true },
{ "path": "skills/tdd.md", "required": true, "ref": "main" },
{ "path": ".claude/skills/tdd/SKILL.md", "required": false, "ref": "main" },
{ "path": "skills/feature-chain.md", "required": true, "ref": "main" },
{ "path": ".claude/skills/feature-chain/SKILL.md", "required": false, "ref": "main" }
]
}required: false sources are generated by the emitter when absent (a curl 404
on them is non-fatal).
Combine with --from-manifest to get the source list for all registered skills at once. Use --no-record to suppress manifest writes when testing or doing dry-run checks.
Note for pull-only consumers: per-harness wrapper forms (
.claude/skills/<n>/SKILL.md,.cursor/rules/<n>.mdc, etc.) are generated by the emitter when no upstream file exists. They may not be present in scaffold's repo as committed files, so acurlagainst a planned source path can 404. Treat any 404 on a planned source as non-fatal — the emitter generates the wrapper from the skill body and RESOLVER metadata instead.
Skills are indexed in a central routing table, .claude/skills/RESOLVER.md,
which maps each skill to its invocation regex and path. A linter keeps the
table and the skills on disk in sync:
node scripts/check-resolvable.ts # errors block, DRY duplication warns
node scripts/check-resolvable.ts --strict # DRY duplication also blocksIt enforces nine phases: Reachability (no skill orphaned from the table),
Ambiguity (no two skills share a slash-command route), DRY (duplicated
prose blocks should move to lib/), MECE (no two skills with overlapping
purpose — merge via args), Wrapper integrity (each harness wrapper
@-includes the canonical skill body instead of duplicating it), Cursor
parity (every skill has a .cursor/rules/<slug>.mdc mirror), Antigravity
parity (every skill has a .agents/skills/<slug>/SKILL.md +
.agent/workflows/<slug>.md pair for Google Antigravity), Frontmatter parity
(every wrapper's description matches the Claude form), and Scaffold-sync
(every skill is registered in .github/scaffold-files.txt so it propagates
downstream).
Enable it as a pre-commit gate:
git config core.hooksPath .githooksNew skills are created with /skillify, which reconstructs the session,
interviews briefly, generates a SKILL.md, Cursor mirror, and Antigravity
wrappers, registers it in RESOLVER, validates, and opens a PR back to scaffold
so all repos inherit it.
To make the skills available in every project (not just this one):
mkdir -p ~/.claude/skills
cp -r .claude/skills/* ~/.claude/skills/Project-level skills override global ones when names match.
.claude/session-start/hook.sh runs at every session start. It fetches origin/main silently and warns Claude if the current branch is behind — prompting a rebase or pull before new feature work begins. Always exits 0 so a network failure never blocks a session.
.claude/read-once/ contains a pair of hooks that prevent Claude from re-reading files it already has in context, saving tokens on large sessions. Controlled via env vars:
| Var | Default | Effect |
|---|---|---|
READ_ONCE_MODE |
warn |
warn allows re-read with advisory; deny blocks it |
READ_ONCE_TTL |
1200 |
Seconds before a cached read expires |
READ_ONCE_DIFF |
0 |
Set to 1 to show only diffs on changed files |
READ_ONCE_DISABLED |
0 |
Set to 1 to disable entirely |
One-time manual step required after the CI workflows are in place.
→ Open Ruleset Settings for victusfate/scaffold
Go to Settings → Rules → Rulesets, edit (or create) the ruleset targeting main, and enable:
Restrict pushes:
- Block force pushes ✓
- Restrict deletions ✓
Require pull requests:
- Require a pull request before merging ✓
- Required approvals:
0 - Dismiss stale reviews when new commits are pushed ✓
Require status checks:
- Enable Require status checks to pass
- Enable Require branches to be up to date before merging (under additional settings)
- Click + Add checks → type
verify→ select verify — GitHub Actions
verifynot in the dropdown? It only registers after at least one PR has run.github/workflows/ci.yml. Open a draft PR, let CI run, close it, then add the check here.
Bypass list: Add Repository admin → Always allow (for emergency hotfixes).
Or just run /protect-branch in Claude Code — it opens this page and walks you through it.
Why required approvals: 0? GitHub rulesets hard-block PR authors from approving their own PRs when required approvals > 0. Since agents run as your GitHub account, setting approvals to 1 locks you out of approving agent PRs — and there's no override in rulesets. The real gates are CI (tests must pass) and
CODEOWNERSprotecting.github/workflows/.
Long-term fix — create a dedicated bot GitHub account for the agent. Set Required approvals back to 1 and re-enable "Require review from Code Owners". Agents open PRs as the bot; you approve as yourself; neither party is locked out.
Known limitation — agents share your GitHub token. Agents in this workflow run as your GitHub account, so GitHub can't distinguish an agent approval from a human one. "Required approvals: 1" can technically be satisfied by the agent approving its own PR. The practical guards are: CI must pass, workflow changes always need your sign-off via CODEOWNERS, and an agent would need to be explicitly told to self-approve. The clean long-term fix is a dedicated bot GitHub account for the agent — that separates author from reviewer cleanly and lets you keep "prevent authors from approving their own PRs" without locking yourself out.
No manual steps needed day-to-day:
- Developer writes conventional commits (
feat:,fix:, etc.) on the branch - The
create-prskill computes the bump from the branch's commits (scripts/compute-bump.ts) and commits the newpackage.jsonversion on the branch, before opening the PR verifyjob runsnpm test(all tool/script suites, the skill-engine linter, the skill/rubric assertion suites, and thedocs/skills.mdfreshness check) on the bump commit — must pass before merge is allowed. A separate Quality Gate workflow (quality.yml) runs the mechanical checks on every PR.- PR merges to
mainwith the version bump included release.ymlreads the version frompackage.jsononmainand pushes thev<version>tag
| Prefix | Bump |
|---|---|
fix: |
patch (1.0.0 → 1.0.1) |
feat: |
minor (1.0.0 → 1.1.0) |
feat!: or BREAKING CHANGE in body |
major (1.0.0 → 2.0.0) |
chore:, docs:, refactor:, etc. |
no release |
The grill-with-docs, to-prd, and tdd skills are adapted from Matt Pocock's skills repo. The core workflow — careful design Q&A → PRD → vertical-slice TDD — is his.
scaffold itself is MIT (see LICENSE).
Repos generated from this template are not bound by it. LICENSE is never
synced, so you are free to relicense — pick any license, remove LICENSE
entirely, or keep the repo closed-source/private. Scaffold never re-adds or
overwrites it on sync.