Skip to content

victusfate/scaffold

Repository files navigation

scaffold

Use this template

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.

Usage

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

Philosophy

  • Single source of truthAGENTS.md holds all agent instructions. CLAUDE.md imports it; GEMINI.md references it; .cursor/rules/agents.mdc points 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.

Workflow

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)
  1. grill-with-docs — Agent interviews you one question at a time, sharpens terminology, stress-tests against the codebase, and produces ./docs/<slug>/design.md with a canonical vocabulary and Mermaid/ASCII diagrams as structure becomes clear.
  2. to-prd — Synthesizes the conversation and codebase into ./docs/<slug>/prd.md automatically. No re-interviewing.
  3. tdd — Derives plan.md from 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.
  4. Review — Once the slices pass, code-quality-review brings 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.

Quality gate

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-review scores 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, and create-pr re-runs it as a gate.
  • /audit is the standalone counterpart — score any scope of files, ranked worst-first, with --fix to apply small fixes in place.

A mechanical pre-flight runs in CI on every PR via .github/workflows/quality.ymlscripts/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 support

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

Feature artifacts

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

Syncing updates to downstream repos

Bootstrap (one-time per repo — installs the sync script, prints next steps):

curl -fsSL https://raw.githubusercontent.com/victusfate/scaffold/main/bin/bootstrap.sh | bash

Bootstrap 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.sh

Flags:

  • --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.sh

Or 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-new for 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-keep file 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.md is shipped as a starting point. On first sync into a repo that has no CLAUDE.md, scaffold writes one. If you have already customized yours, add CLAUDE.md to .scaffold-keep — the sidecar or keep-list will protect it from then on.
  • LICENSE is never synced. The sync only writes files listed in .github/scaffold-files.txt, and LICENSE is not one of them. A repo created from the template starts with scaffold's MIT LICENSE (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.0

A 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.

What ships, and how we keep the manifest honest

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.tsphaseManifestCompleteness) 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 via git subtree (real git pull, history, conflict resolution, git subtree push upstream) 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; keeping sync-scaffold as-is.

Using scaffold skills as a dependency

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-project

After 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-project

List available capabilities:

node tools/hoist-skill/run --list

What 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-project

This 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.0

The 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 a curl against 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.

Skill engine

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 blocks

It 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 .githooks

New 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.

Global installation

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.

session-start hook

.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.

read-once hooks

.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

Branch Protection Setup

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:

  1. Enable Require status checks to pass
  2. Enable Require branches to be up to date before merging (under additional settings)
  3. Click + Add checks → type verify → select verify — GitHub Actions

verify not 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 CODEOWNERS protecting .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.

How releases work after setup

No manual steps needed day-to-day:

  1. Developer writes conventional commits (feat:, fix:, etc.) on the branch
  2. The create-pr skill computes the bump from the branch's commits (scripts/compute-bump.ts) and commits the new package.json version on the branch, before opening the PR
  3. verify job runs npm test (all tool/script suites, the skill-engine linter, the skill/rubric assertion suites, and the docs/skills.md freshness 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.
  4. PR merges to main with the version bump included
  5. release.yml reads the version from package.json on main and pushes the v<version> tag

Commit → version mapping

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

Credits

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.

License

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.

About

opinionated agent neutral scaffold for new projects

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors