Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -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
52 changes: 0 additions & 52 deletions .github/workflows/ci.yml

This file was deleted.

28 changes: 0 additions & 28 deletions .github/workflows/deploy.yml

This file was deleted.

63 changes: 63 additions & 0 deletions .github/workflows/examples/ci.yml.example
Original file line number Diff line number Diff line change
@@ -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: <install command, e.g. pnpm install --frozen-lockfile>

# - name: Lint
# run: <lint all apps — root CLAUDE.md "Coding standards">

# - name: Typecheck
# run: <typecheck all apps; an explicit no-op in a plain-JS app — keep the step green>

# - name: Test
# run: <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: <production build of every app — root CLAUDE.md "Definition of Done">

# - name: i18n key parity
# run: <fail on any key missing from a locale — apps/frontend/CLAUDE.md "Internationalisation">

# - name: Migration gate
# run: <the gate named by the pack's db.md; base default is up, down, up on a scratch DB, failing on drift — db/CLAUDE.md>

# - name: Accessibility scan
# run: <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")
43 changes: 43 additions & 0 deletions .github/workflows/examples/deploy.yml.example
Original file line number Diff line number Diff line change
@@ -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: <your deploy command>
# # Widen `permissions` above only for what the deploy genuinely needs
# # (e.g. id-token: write for OIDC to your cloud provider).
118 changes: 118 additions & 0 deletions .github/workflows/template-integrity.yml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<pm>` (package manager) and every `TODO` below with real commands once it is, then delete this banner.

**Agent: if these are still `<pm>`/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 `<pm>` 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 `<pm>`/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 `<pm>` 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.

Expand Down Expand Up @@ -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.

- `<pm> lint`, `<pm> typecheck`, `<pm> test`, and `<pm> build` all pass for the touched apps.
- New or changed behaviour is covered by tests that assert behaviour, not implementation.
Expand Down Expand Up @@ -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.

Expand Down
Loading
Loading