diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..23d1cce --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# The Cavalry-Collective org has no teams, so ownership is the single maintainer. +# Replace with a team handle (e.g. @Cavalry-Collective/platform) once one exists. +* @DeyangChan diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index c7149ad..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: CI - -# Enforcement point for the Definition of Done (root CLAUDE.md). Every gate below is a -# convention already stated in the CLAUDE.md files; CI is where it stops being prose. -# Fill in each TODO with your stack's command. RULE: a failing check FAILS the build — -# never `|| true`, never "warn". Wire the gates in the order listed; cheapest first. - -on: - pull_request: - push: - branches: [main] - -jobs: - ci: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - # - name: Install dependencies - # run: # TODO: replace — install command (e.g. pnpm install --frozen-lockfile) - - # - name: Lint - # run: # TODO: replace — lint all apps — root CLAUDE.md "Coding standards" - - # - name: Typecheck - # run: # TODO: replace — typecheck all apps (an explicit no-op in a plain-JS app — keep the step green; root CLAUDE.md "Common commands") - - # - name: Test - # run: # TODO: replace — run all test suites — root CLAUDE.md "Testing". - # # Most coverage lives in the fast inner rings; this gate must be red on any failure. - - # - name: Build - # run: # TODO: replace — production build of every app — root CLAUDE.md "Definition of Done" - - # - name: i18n key parity - # run: # TODO: replace — fail on any key missing from a locale — apps/frontend/CLAUDE.md "Internationalisation" - - # - name: Migration gate - # run: # TODO: replace — the pack's db.md names the gate (base default: up → down → up on a scratch DB, fail on drift) — db/CLAUDE.md - - # - name: Accessibility scan - # run: # TODO: replace — run the a11y scanner against built pages — apps/frontend/CLAUDE.md "Accessibility baseline" - - # Optional structural checks — wire only if cheaply scriptable in your stack - # (closer to custom lint rules than CI one-liners; candidates, not mandates): - # - no hardcoded colour/spacing literals outside the token source - # - no network calls outside services/ - # - route-registry completeness (no page without a route entry) - # - token-scale conformance (spacing/type values on the guide's scales) - # - component duplication audit (apps/frontend/CLAUDE.md "Component structure") - - name: Placeholder - run: 'echo "TODO: replace this job''s steps with the gates commented above"' diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index a825c11..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Deploy - -# Deploy must NOT run unless CI is green. This fires only after the CI workflow -# completes successfully on a push to main; keep that dependency when filling in the TODO. -# (If you later merge CI and deploy into one workflow, replace this trigger with a -# deploy job that declares `needs: ci`.) - -on: - workflow_run: - workflows: [CI] - types: [completed] - -jobs: - deploy: - runs-on: ubuntu-latest - # Only on a successful CI run, and only for main. - if: >- - github.event.workflow_run.conclusion == 'success' && - github.event.workflow_run.head_branch == 'main' - steps: - # When filling in: check out the exact commit CI tested — a `workflow_run` - # checkout defaults to the latest main, which can deploy a commit CI never saw. - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.workflow_run.head_sha }} - - - name: Deploy - run: 'echo "TODO: replace with your deploy command"' diff --git a/.github/workflows/examples/ci.yml.example b/.github/workflows/examples/ci.yml.example new file mode 100644 index 0000000..b9ef4c2 --- /dev/null +++ b/.github/workflows/examples/ci.yml.example @@ -0,0 +1,63 @@ +# EXAMPLE — not an active workflow. +# +# Copy to .github/workflows/ci.yml once a stack is chosen, then replace every +# placeholder with your stack's command. A stack pack's README carries a CI block +# that fills most of this in. +# +# Enforcement point for the Definition of Done (root CLAUDE.md). Every gate below is a +# convention already stated in the CLAUDE.md files; CI is where it stops being prose. +# RULE: a failing check FAILS the build — never mute it, never downgrade it to a warning. +# Wire the gates in the order listed; cheapest first. +# +# Every step is commented out on purpose. A green run that checked nothing is worse than +# no run at all, so this file must not become an active workflow until at least install, +# lint, test, and build are real commands. + +name: CI + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + ci: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # - name: Install dependencies + # run: + + # - name: Lint + # run: + + # - name: Typecheck + # run: + + # - name: Test + # run: + # # Most coverage lives in the fast inner rings; this gate must be red on any failure. + + # - name: Build + # run: + + # - name: i18n key parity + # run: + + # - name: Migration gate + # run: + + # - name: Accessibility scan + # run: + + # Optional structural checks — wire only if cheaply scriptable in your stack + # (closer to custom lint rules than CI one-liners; candidates, not mandates): + # - no hardcoded colour/spacing literals outside the token source + # - no network calls outside services/ + # - route-registry completeness (no page without a route entry) + # - token-scale conformance (spacing/type values on the guide's scales) + # - component duplication audit (apps/frontend/CLAUDE.md "Component structure") diff --git a/.github/workflows/examples/deploy.yml.example b/.github/workflows/examples/deploy.yml.example new file mode 100644 index 0000000..9475cfb --- /dev/null +++ b/.github/workflows/examples/deploy.yml.example @@ -0,0 +1,43 @@ +# EXAMPLE — not an active workflow. +# +# Copy to .github/workflows/deploy.yml only after a deployment target is chosen and its +# secrets and configuration are present. An active deploy workflow with no real deploy +# step reports successful deployments that never happened. +# +# Some packs delete this file instead of filling it in — a platform whose own Git +# integration deploys every accepted push does not want a second path racing it. Check the +# adopted pack's conflict register before copying (e.g. vercel-csr, vercel-ssr). +# +# Deploy must NOT run unless CI is green. This fires only after the CI workflow completes +# successfully on a push to main; keep that dependency when filling in the deploy step. +# (If you later merge CI and deploy into one workflow, replace this trigger with a deploy +# job that declares `needs: ci`.) + +name: Deploy + +on: + workflow_run: + workflows: [CI] + types: [completed] + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-latest + # Only on a successful CI run, and only for main. + if: >- + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.head_branch == 'main' + steps: + # Check out the exact commit CI tested — a `workflow_run` checkout defaults to the + # latest main, which can deploy a commit CI never saw. + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha }} + + # - name: Deploy + # run: + # # Widen `permissions` above only for what the deploy genuinely needs + # # (e.g. id-token: write for OIDC to your cloud provider). diff --git a/.github/workflows/template-integrity.yml b/.github/workflows/template-integrity.yml new file mode 100644 index 0000000..7f8b4ed --- /dev/null +++ b/.github/workflows/template-integrity.yml @@ -0,0 +1,118 @@ +name: Template integrity + +# Guards this template repository, not the projects made from it. Every check below +# validates something that actually exists here: documents, links, and the pack and +# add-on contracts stated in stacks/README.md and add-ons/README.md. +# +# Instantiating the template? Delete this workflow and copy the scaffolds in +# .github/workflows/examples/ instead (README.md → Day-1 checklist, step 7). + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + integrity: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Workflows perform real checks + run: | + set -euo pipefail + # Assembled at runtime so this check does not match its own error message. + marker="$(printf 'TO%s' 'DO')" + failed=0 + for wf in .github/workflows/*.yml .github/workflows/*.yaml; do + [ -e "$wf" ] || continue + if grep -nE '\|\| *true' "$wf"; then + echo "::error file=$wf::a check is muted, so a failure would still report green" + failed=1 + fi + if grep -nE "^[^#]*$marker" "$wf"; then + echo "::error file=$wf::an executable workflow still carries a placeholder step" + failed=1 + fi + done + exit $failed + + - name: Document and contract checks + run: | + python3 - <<'PY' + import os, re, subprocess, sys, urllib.parse + + PRECEDENCE = ( + "> Rides on top of the base contract; this file only adds stack bindings and " + "resolves conflicts. Where this appendix and a base file disagree, the conflict " + "register below wins — for this stack only." + ) + BINARY_EXT = {".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico", + ".woff", ".woff2", ".ttf", ".pdf"} + LINK = re.compile(r"\[[^\]]*\]\(([^)\s]+)(?:\s+\"[^\"]*\")?\)") + + tracked = [p for p in subprocess.run( + ["git", "ls-files", "-z"], capture_output=True, text=True, check=True + ).stdout.split("\0") if p] + + errors = [] + + # Whitespace invariants: LF endings, a final newline, no trailing blanks. + for path in tracked: + if os.path.splitext(path)[1].lower() in BINARY_EXT: + continue + raw = open(path, "rb").read() + if not raw: + continue + if b"\r\n" in raw: + errors.append(f"{path}: CRLF line endings") + if not raw.endswith(b"\n"): + errors.append(f"{path}: no final newline") + for n, line in enumerate(raw.split(b"\n"), 1): + if line != line.rstrip(): + errors.append(f"{path}:{n}: trailing whitespace") + + # Every relative Markdown link resolves to a file in the tree. + for path in (p for p in tracked if p.endswith(".md")): + for n, line in enumerate(open(path, encoding="utf-8"), 1): + for target in LINK.findall(line): + if (re.match(r"^[a-z][a-z0-9+.-]*:", target) + or target.startswith(("#", "//"))): + continue + rel = urllib.parse.unquote(target.split("#", 1)[0]) + if not rel: + continue + resolved = os.path.normpath( + os.path.join(os.path.dirname(path), rel)) + if not os.path.exists(resolved): + errors.append(f"{path}:{n}: broken relative link -> {target}") + + # Stack pack contract — stacks/README.md "Required files" and "Appendix rules". + for pack in sorted(d for d in os.listdir("stacks") + if os.path.isdir(os.path.join("stacks", d))): + base = os.path.join("stacks", pack) + for required in ("README.md", "backend.md", "frontend.md", "db.md"): + if not os.path.isfile(os.path.join(base, required)): + errors.append(f"{base}: missing required {required}") + for name in sorted(n for n in os.listdir(base) + if n.endswith(".md") and n != "README.md"): + text = open(os.path.join(base, name), encoding="utf-8").read() + if PRECEDENCE not in text: + errors.append(f"{base}/{name}: missing the verbatim precedence line") + if "## Conflict register" not in text: + errors.append(f"{base}/{name}: missing Conflict register") + + # Add-on contract — every kept directory is adopted, so each needs its README. + for addon in sorted(d for d in os.listdir("add-ons") + if os.path.isdir(os.path.join("add-ons", d))): + if not os.path.isfile(os.path.join("add-ons", addon, "README.md")): + errors.append(f"add-ons/{addon}: missing README.md") + + for error in errors: + print(f"::error::{error}") + print(f"{len(errors)} problem(s)") + sys.exit(1 if errors else 0) + PY diff --git a/.gitignore b/.gitignore index 653e5ee..3fa51cc 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ yarn-error.log* # Claude Code — agent worktrees live here (root CLAUDE.md "Working in a git worktree"); never commit them .claude/worktrees/ +# Local runtime state written by the agent, not project content +.claude/scheduled_tasks.lock diff --git a/CLAUDE.md b/CLAUDE.md index c7df70a..5dcb76c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,7 +24,7 @@ When instruction files disagree: the area file (`apps/*/CLAUDE.md`, `db/CLAUDE.m > ⚠️ **PLACEHOLDER — NOT YET FILLED IN.** No toolchain has been chosen. Replace `` (package manager) and every `TODO` below with real commands once it is, then delete this banner. -**Agent: if these are still ``/TODO when you need to run one** — detect the real command from the repo (lockfile, manifest / `package.json` scripts, Makefile, CI workflow) and use that. If you cannot determine it, STOP and ask the user — never run the literal `` and never guess a package manager. Once you learn the real commands, offer to fill in this block and `.github/workflows/ci.yml` as part of your change. +**Agent: if these are still ``/TODO when you need to run one** — detect the real command from the repo (lockfile, manifest / `package.json` scripts, Makefile, CI workflow) and use that. If you cannot determine it, STOP and ask the user — never run the literal `` and never guess a package manager. Once you learn the real commands, offer to fill in this block and the project's CI workflow as part of your change (scaffold: `.github/workflows/examples/ci.yml.example`). > Instantiating this template? Work through the **Day-1 checklist** in `README.md` before feature work — it enumerates every placeholder site. @@ -104,7 +104,7 @@ Load-bearing engineering rules; honor them on every change. They are stack- and ## Definition of Done -The concrete bar for *Goal-driven execution*: do not report work as done until all of the following hold. If a step cannot be run (e.g. the toolchain TODOs in *Common commands* are still unfilled), say so explicitly rather than skipping it silently. This is a hard self-check the agent runs before claiming completion — while `ci.yml` is still a stub, the gate is not delegated. +The concrete bar for *Goal-driven execution*: do not report work as done until all of the following hold. If a step cannot be run (e.g. the toolchain TODOs in *Common commands* are still unfilled), say so explicitly rather than skipping it silently. This is a hard self-check the agent runs before claiming completion — until a real `ci.yml` exists, the gate is not delegated. - ` lint`, ` typecheck`, ` test`, and ` build` all pass for the touched apps. - New or changed behaviour is covered by tests that assert behaviour, not implementation. @@ -153,7 +153,7 @@ Worktrees are the **default** here — most work runs in parallel with Claude ac 3. **Fast-forward merge** into the default branch (the rebase makes this a clean ff, preserving linear history). 4. **Stop** any dev servers / test instances started for the work. 5. **Delete** the worktree (`git worktree remove`) and its merged branch. -6. **Push** the default branch only after confirming. By default this template's `.github/workflows/deploy.yml` runs after a green CI run on `main` (a `workflow_run` trigger), so once its deploy step is filled in a push to the default branch ships to the configured target — confirm with the user before pushing, and check `deploy.yml` if the trigger has been changed. +6. **Push** the default branch only after confirming. Once `.github/workflows/deploy.yml` exists it runs after a green CI run on `main` (a `workflow_run` trigger), so a push to the default branch ships to the configured target — confirm with the user before pushing, and read `deploy.yml` to see what its trigger actually is. Where `main` is protected (Day-1 step 11) or the work is spec-backed (`specs/README.md`: open a PR that links the spec), steps 3 and 6 run through the platform instead: push the rebased branch, open or update the PR, let CI go green, and merge with a fast-forward/rebase merge — never a merge commit. The local ff-merge + push path applies only to an unprotected repo. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..7a53c82 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,84 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at conduct@cavalry.sg. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7c1ecd6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,40 @@ +# Contributing + +This repository is a template made of documents. Changes are almost always to a contract, so treat a wording change as seriously as a code change: an ambiguous rule misleads every project cloned afterwards. + +## Before you open a PR + +- Read [`CLAUDE.md`](CLAUDE.md). It governs this repository too — in particular **Documentation style** and **Development workflow**. +- Keep each rule in the one document that owns it, and link from everywhere else. +- Work on a short-lived branch off `main`. History is linear here: rebase, never merge-commit. +- Use Conventional Commits (`docs:`, `feat:`, `fix:`, `chore:`) with an imperative subject and one logical change per commit. + +## What CI checks + +`.github/workflows/template-integrity.yml` runs on every pull request. It verifies: + +- whitespace invariants — LF endings, a final newline, no trailing whitespace; +- every relative Markdown link resolves; +- every stack pack has its four required files, and every appendix carries the verbatim precedence line and a conflict register; +- every add-on directory has a `README.md`; +- no active workflow mutes a check or ships a placeholder step. + +Run the equivalent locally before pushing: `git diff --check` catches whitespace, and a broken link is usually a renamed file. + +## Adding a stack pack or add-on + +Follow the recipe in the index rather than copying a neighbour wholesale: + +- stack packs — [`stacks/README.md`](stacks/README.md) → *Add a pack*; +- add-ons — [`add-ons/README.md`](add-ons/README.md). + +A new pack must resolve its disagreements with the base contract in its conflict register. A silent contradiction is a defect. + +## Version claims + +Any pin — a framework major, a Node LTS, a managed runtime, a database engine — must be checked against the vendor's current documentation in the change that introduces it, and the reference linked. An end-of-life default is a bug, not a detail. + +## Reporting problems + +- A wrong, unclear, or contradictory rule: open an issue. +- A security concern: follow [`SECURITY.md`](SECURITY.md) instead. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9cd72f4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Cavalry Collective + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index dfe9446..9727bbd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,20 @@ # vstack-template-base -**An opinionated template for spinning up production-ready, full-stack projects — fast, and without re-litigating a single engineering decision.** +**An opinionated project template and a set of production conventions for full-stack work — so a new codebase starts with its engineering decisions already made.** -Every new Cavalry Collective project starts here. Clone it, run the Day-1 checklist once, and start shipping features the same day — with the architecture, quality gates, and conventions of a mature codebase already in force. +Every new Cavalry Collective project starts here. Clone it, run the Day-1 checklist once, and start shipping features the same day, with the architecture, quality gates, and conventions of a mature codebase already in force. + +The template ships production-oriented defaults and contracts. It does not ship a production system: a project becomes production-ready when you have completed the Day-1 checklist and configured and verified your own CI, infrastructure, secrets, and deployment. Those are stack-specific by design, which is exactly why the template leaves them to you. + +## Who it's for + +Teams and solo developers starting a new full-stack web project who want a house style enforced from the first commit rather than retrofitted. It assumes you are comfortable choosing your own framework, package manager, and cloud provider — the template tells you where code goes and what "done" means, not which library to import. + +It is equally aimed at AI coding agents. The contracts are written so an agent working in any area picks up that area's rules automatically. + +## Status + +Stable and in active use across Cavalry Collective projects. The contracts change when we learn something; the directory shape and the Day-1 checklist are settled. Breaking changes to a contract are called out in the commit that makes them. There is no release cadence — take `main`. ## Why this exists @@ -16,9 +28,9 @@ The contracts are written for humans **and** for AI agents. An agent working in - **Simplicity first.** The minimum code that solves the problem; every added abstraction must defeat the simpler alternative on the record. If 200 lines could be 50, it's 50. - **Quality is a gate, not a vibe.** Nothing is "done" until it's been run and observed: four data states exercised, endpoints hit, migrations round-tripped, screens checked at 320 px. The design guide (`design/`) locks the visual system *before* the first screen is built. - **Spec-first, independently shippable slices.** Non-trivial work starts as a short written spec under `specs/`; P1 stories alone form a viable MVP. Trunk stays releasable, history stays linear. -- **Instructions over machinery.** The template carries no build scripts, hooks, or generated artifacts — just precise instructions in the files agents and humans already read. What you see is the whole mechanism. +- **Instructions over machinery.** The template carries no build scripts, hooks, or generated artifacts — just precise instructions in the files agents and humans already read, plus one workflow that checks those documents stay consistent. What you see is the whole mechanism. -**The end state:** a template you can instantiate in an afternoon and trust for years — every project born production-ready, every convention already decided, every agent already briefed. +**The end state:** a template you can instantiate in an afternoon and trust for years — every convention already decided, every agent already briefed, and a clear path from first commit to production. ## How to use it @@ -41,7 +53,7 @@ That's it. There is nothing to install and no generator to run — the template | `specs/` | Feature specs — written before implementation | | `stacks/` | Optional stack packs — appendix docs binding the agnostic contracts to one concrete stack; one chosen at instantiation, the rest deleted. See [`stacks/README.md`](stacks/README.md) | | `add-ons/` | Optional capability add-ons — agnostic patterns you opt into at Day-1 (test mode, OTP login, LLM calls, …); the concrete stack wiring is derived at adoption from each add-on's seam list plus the active pack's appendices. See [`add-ons/README.md`](add-ons/README.md) | -| `.github/workflows/` | CI and deploy stubs — fill in your toolchain commands | +| `.github/workflows/` | `template-integrity.yml` guards this template; `examples/` holds the CI and deploy scaffolds you copy in once a stack is chosen | | `project.code-workspace` | VS Code workspace (hides agent worktrees from search and watchers) | ## What's not included @@ -60,7 +72,7 @@ Or choose a stack pack under `stacks/` (e.g. `enterprise`) for a vetted set of t ## Day-1 checklist -Run this once, top to bottom, the first time you instantiate the template. Each step names the file and the marker to replace. The placeholders are grep-able: `` in the root `CLAUDE.md` command block, `FILL IN ON SETUP` in `apps/frontend/CLAUDE.md`, and `TODO: replace` in the `.github/workflows/` stubs. Step 13 checks they are all gone. +Run this once, top to bottom, the first time you instantiate the template. Each step names the file and the marker to replace. The placeholders are grep-able: `` in the root `CLAUDE.md` command block, `FILL IN ON SETUP` in `apps/frontend/CLAUDE.md`, and the `<...>` command placeholders in the `.github/workflows/examples/` scaffolds. Step 13 checks they are all gone. 1. **Create the repo.** Click **Use this template** → **Create a new repository** on GitHub. 2. **Clone** your new repo. @@ -74,14 +86,15 @@ Run this once, top to bottom, the first time you instantiate the template. Each 5. **Choose a stack pack — or stay agnostic.** - **Pack path (fast):** pick the pack under `stacks/` matching your stack (e.g. `enterprise`), then: - `rm -rf` every other `stacks/*` directory — the one pack left is the adopted one; each area's `CLAUDE.md` already points agents at its appendices (mechanism: `stacks/README.md` *How packs work*). - - Copy the pack README **dev** command block into the root `CLAUDE.md` "Common commands" placeholder (delete the banner); copy its **CI** block into `.github/workflows/ci.yml`. They are different blocks — never paste a dev-only migration command into CI. + - Copy the pack README **dev** command block into the root `CLAUDE.md` "Common commands" placeholder (delete the banner); copy its **CI** block into your new `.github/workflows/ci.yml` (step 7). They are different blocks — never paste a dev-only migration command into CI. - Record the choice in root `CLAUDE.md` **Learnings**: `Stack: ; appendices under stacks//`. - **Agnostic path:** keep `stacks/` for reference (or delete it) and fill in the toolchain yourself — see step 6. 6. **Choose your add-ons.** Under `add-ons/`, keep the optional capabilities you want and **delete the directories you don't** — every directory kept is adopted, and the root `CLAUDE.md` points agents at each kept add-on's README. The full inventory is the **Current add-ons** table in [`add-ons/README.md`](add-ons/README.md). Check each chosen add-on's **Prerequisites** against your pack and other add-ons before keeping it; the concrete stack wiring is derived at implementation time from its *Binds to a stack* seam list plus the active pack's appendices. 7. **Fill the toolchain placeholders.** On the pack path, step 5 already filled the first two bullets; **both paths** still do the last two: - Root `CLAUDE.md` "Common commands" — replace the seven ``/`TODO` commands and delete the PLACEHOLDER banner. - - `.github/workflows/ci.yml` — replace every commented gate (**the file is the canonical gate list**): install/lint/typecheck/test/build, the i18n key-parity check, the migration gate, and the a11y scan. A pack's CI block covers the toolchain gates; still wire the remaining `ci.yml` gates (migration gates per the pack's `db.md`, the a11y scan). - - `.github/workflows/deploy.yml` — replace the TODO step (or, on a pack whose register deletes the stub, e.g. `vercel-csr`/`vercel-ssr`, delete it per that register). + - **Wire CI.** Copy `.github/workflows/examples/ci.yml.example` to `.github/workflows/ci.yml`, then uncomment and fill every gate (**the example is the canonical gate list**): install/lint/typecheck/test/build, the i18n key-parity check, the migration gate, and the a11y scan. A pack's CI block covers the toolchain gates; still wire the rest (migration gates per the pack's `db.md`, the a11y scan). Uncomment a step only once its command is real — a green run that checked nothing is worse than no run. + - **Delete `.github/workflows/template-integrity.yml`.** It guards the template repository's own documents, not your project. + - **Leave deploy until last.** Copy `.github/workflows/examples/deploy.yml.example` to `.github/workflows/deploy.yml` only once a deployment target is chosen and its secrets are in place. On a pack whose register rules it out (`vercel-csr`, `vercel-ssr`), never copy it — the platform's Git integration is the pipeline. - Add a real `.env.example` (already whitelisted in `.gitignore`). 8. **Declare the primary form factor.** In `apps/frontend/CLAUDE.md`, fill in the form-factor line: ```markdown @@ -91,8 +104,26 @@ Run this once, top to bottom, the first time you instantiate the template. Each 10. **Copy runtime config.** Copy any gitignored runtime config (`.env`, secrets) into your local checkout — it is not carried over from the template. 11. **Protect `main`.** Add a branch protection rule / ruleset requiring the CI workflow to pass before merge. Install the rule **after step 13's first green push** (or run steps 5–10 on a branch and merge them via a PR) — a required-status rule rejects a direct push whose CI has never run. Trunk must stay releasable — and on packs whose pipeline ships whatever lands on `main` (e.g. `vercel-csr`), green-CI-before-merge *is* the deploy gate. 12. **Stand up staging (if your pack defines one).** Bring up the persistent preview/staging environment your stack pack specifies before feature work — for the `vercel-csr` and `vercel-ssr` packs that is the `develop` branch plus its dedicated Neon branch (the pack's `infra.md` → *Staging*), migrated with the same manual runbook as prod (the pack's `db.md` → *Operations*). -13. **Confirm green.** Push and watch the first CI run pass. Then confirm no placeholder survives — both must return nothing: `grep -rn 'FILL IN ON SETUP\|TODO:' . --exclude-dir=stacks --exclude-dir=specs --exclude-dir=.git | grep -v '^\./README\.md:'` and `grep -n '^ ' CLAUDE.md`. (Only this root README — whose checklist names the markers — is filtered out; delete it once instantiation is done if you prefer a clean tree.) +13. **Confirm green.** Push and watch the first CI run pass. Then confirm no placeholder survives — all three must return nothing: + - `grep -rn 'FILL IN ON SETUP\|TODO:' . --exclude-dir=stacks --exclude-dir=specs --exclude-dir=.git | grep -v '^\./README\.md:'` + - `grep -n '^ ' CLAUDE.md` + - `grep -rn '^ *# *- name:' .github/workflows/` — a gate still commented out in an active workflow is a gate that isn't running. + + (Only this root README — whose checklist names the markers — is filtered out of the first grep; delete it once instantiation is done if you prefer a clean tree.) > If you chose a server-first Next.js pack (`enterprise` or `vercel-ssr`), soften the SPA framing the base ships agnostic: root `CLAUDE.md` "the single-page app" → "the web frontend", the opening line of `apps/frontend/CLAUDE.md` ("how the single-page app is structured") likewise, and the **What's included** "Frontend SPA" row above → "Frontend (server-first Next.js)". (`vercel-ssr`'s one-app restructure step covers this and more — see its README.) > > **The `vercel-csr` pack is not one of them** — it is a client-rendered SPA with no SSR, so the base framing above is already correct for it and every one of those files stays exactly as shipped. Don't soften anything. + +## Support + +- A rule that is wrong, unclear, or contradictory: open an issue. +- A change you want to make: read [`CONTRIBUTING.md`](CONTRIBUTING.md). +- A security concern: email **security@cavalry.sg**. See [`SECURITY.md`](SECURITY.md) — do not open a public issue. +- Conduct in this project's spaces is governed by [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md). + +Projects created from this template are supported by whoever owns them, not by this repository. + +## License + +[MIT](LICENSE). Projects generated from this template carry no obligation to retain it — replace `LICENSE` with whatever your project needs. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..f393c10 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security policy + +## Reporting a vulnerability + +Email **security@cavalry.sg** with the affected file or rule, the risk, and a reproduction if there is one. Do not open a public issue for a security report. + +Expect an acknowledgement within five working days. + +## Scope + +This repository contains documentation and one CI workflow. It ships no application code and no runtime dependencies, so the realistic risks are: + +- guidance that leads a project into an insecure default; +- a credential or private hostname committed by mistake; +- a workflow change that widens permissions or lets untrusted input reach a privileged step. + +All three are in scope. A vulnerability in a project *generated* from this template is that project's to fix, unless a rule here caused it — in which case report it. + +## Supported versions + +`main` only. Fixes land there; there are no maintained release branches. diff --git a/design/design-guide.html b/design/design-guide.html index 2de7e65..e3dbdb0 100644 --- a/design/design-guide.html +++ b/design/design-guide.html @@ -6,7 +6,7 @@ Keystone — Design Guide - +