SubZeroDev.GameEngine — the Game Engine. A deterministic, game-agnostic
narrative-game platform: its source (src/engine/) and its specs
(design/) in one repo. Human-facing documentation under docs/docs/ is generated from
that canonical design.
The model is core → kinds → campaigns: one shared deterministic core, game-type
logic (kinds, engine-owned code), and content (campaigns, data). v1 ships three kinds:
story-graph, simulation, and world-graph.
Companions:
- Game — SubZeroDev.GameOfLife:
Life in the Fast Lane, the flagship
simulation-kind game. Its docs are where much of this engine's core was first designed;games/…references throughout these specs point there.games/04-engine-specification.mdis the ancestor, not a second authority. It is a 104 KB engine spec that02-architectureand04-corewere derived from, and it is cited ~21 times across these docs — every citation is provenance. For anything the core owns, the docs here supersede it. It is no longer authoritative for anything. It was, for one thing: thesimulationkind's own content and resolution model (its §5, §7–§10, §12, §14), held upstream until a contract existed here against the Kind seam. That contract exists —10-simulation-kind.md, whole as of its Revision 2, expressed against the seam the way03-story-graph-kindis. What remains upstream is provisional balance, not contract: drift rates, scenario economics,demandBandthresholds, the housing-quality formula. Stated in04-core, Reused, not re-derived. - Game — SubZeroDev.SunTrap:
Sun Trap, the flagship
world-graph-kind game — a satirical resort-management sim. Design only, no code. Its kind contract is12-world-graph-kind.mdhere; the game's maps, scenarios, balance and client live there. Nothing in these specs depends on it. - Client — SubZeroDev.Adventures:
the browser play surface, extracted from this repository's
/play/route and the one going forward — the in-repositorysite/src/play/route has been retired (10-design.md, Succeeded by SubZeroDev.Adventures). It is a client repository: nodesign/, no pipeline, no contract of its own. It consumes this engine as a pinned git submodule and adds a hosted Fastify API, Postgres persistence and accounts — all without a reciprocal engine change, which is the dependency direction working. It is the first host to implement the ports outside a browser tab, and that turned up eight contract findings, indexed together in90-decisions.md§2, Found by the first downstream host. Two things to know before touching it: it depends onfromPortableand thePortable*types, which graduated from the spike and are contracted package-root exports (20-contract.md§19;90-decisions.md, 2026-08-11), so a submodule bump that breaks them is a contract break rather than a legitimate one; and its visual identity diverged from14-game-interface.mddeliberately — do not port it back. - Hosting / NEaaS — SubZeroDev.Platform: the deferred hosting / SaaS layer.
The build strategy is engine-first: prove the deterministic engine with automated tests and a plain text client before any UI.
The agent-kit files are canonical. Edit these, never their generated copies under
docs/docs/engine/:
| File | Owns |
|---|---|
design/00-brief.md |
Product vision, scope, non-goals, MVP, and definition of done |
design/10-design.md |
Architecture plus observability, extensibility, replay, capture, client, and content-pack design |
design/20-contract.md |
Core and all kind contracts, including exact types and invariants |
design/30-slices.md |
The W-numbered vertical delivery ledger; W ids are retained because repository history already cites them |
design/90-decisions.md |
Decision history, deferred items, and judgement calls to revisit |
Each file contains marked human-document blocks. build/ConvertTo-HumanDocumentation.ps1
extracts them deterministically into docs/docs/engine/; build/Test-Documentation.ps1
fails when a generated page differs or the generated developer guide is stale.
Read in order. These files are scoped deliberately and cross-reference by section number, but they are outputs, not editing surfaces.
| File | Holds |
|---|---|
01-vision.md |
Why the platform exists; the core/kind/campaign model |
02-architecture.md |
Every settled architecture decision with rationale. The contract (decisions) |
04-core.md |
The core as types: the Kind interface (the seam), GameState envelope, engine API, session store, projection, validation, reason codes, MCP schemas, determinism harness. The contract (types) |
03-story-graph-kind.md |
The flagship kind's content types: nodes, choices, typed variables, consequences, endings, achievements, turn/settle semantics + worked Bureaucracy-arc example. kindState plugs into 04's envelope |
11-content-packs.md |
Post-MVP: resolving an ordered pack set into the frozen registry. Campaigns replace wholesale, strings replace per key. The load-bearing part is identity — campaignVersion becomes a digest of the resolution, because two players on the same campaign version with different packs are playing different games and the envelope had no way to say so |
10-simulation-kind.md |
The second kind against the Kind seam — the seam only, not a port. Reconciles the upstream model with the envelope (seven fields it must not duplicate, and no persisted RngState), maps its richer verbs onto the one-action model (plan.add/remove/clear, end_week), and fixes projection, reason codes, events and terminal identity. §14 states what is still upstream and why |
12-world-graph-kind.md |
The third kind: a navigable world with autonomous inhabitants. A tick batch is the turn, and batch invariance — advance_ticks n reaches the same kindState as any split of it — is the load-bearing property, the one that forced KindContext.derive and the tick stream into 04. Win/loss is Kind.outcome, not a GameStatus. Not related to story-graph despite the suffix: a story graph is authored, a world graph is navigated |
09-clients.md |
The client contract, MVP scope: a client is a projection of the session store, never a participant — made testable as two clients, same inputs, byte-identical serialize(). Defines the API coverage checklist that MVP.md §5 and W16 both required and neither specified: ten store operations, ten MCP tools, one-to-one |
13-playable-web-demo.md |
The first public browser client: a static /play/ route over the Bureaucracy MVP, the browser-portability boundary, same-page checkpoint lifetime, client-parity proof, responsive/accessibility rules, and the explicit line between an engine demo and a finished game |
14-game-interface.md |
The presentation redesign for /play/: an original absurd adventure cabinet with a scene stage, tactile action deck, projected status console, controlled joke density, original-asset rules, and responsive/accessibility proof |
15-platform-static-host.md |
The first Platform consumer: a product-owned ASP.NET static host and immutable container image for the existing combined artifact, with the package-release gate, CI smoke contract, GHCR publication boundary, and the explicit line before a hosted engine |
08-session-capture.md |
Turning a played session into a ReplayFixture — post-MVP, gated on hosting. Mostly a privacy contract: no identity, only kind-declared params (ActionParams is arbitrary caller input), no timing; the seed is the sharp edge; promotion to the committed corpus is a reviewed one-way door |
07-replay.md |
The regression oracle, post-MVP: replaying {config, actionLog} fixtures across engine versions and comparing an Outcome built only from cross-version-stable vocabulary (GameStatus, ReasonCode, achievement ids). Distinct from 04 §14, which compares a build against itself. Fixtures are inputs, not state, so this sidesteps save migration |
06-extensibility.md |
Where the engine can be extended: the ports a host supplies (IdSource, stores, Emitter, Clock, ExperimentSource), the two composition roots, and the rule that decides — a host may supply anything that cannot change serialize() output, which makes the determinism boundary the trust boundary. Kinds stay engine-owned per architecture N2 |
05-observability.md |
Logging and tracing as a separate channel from StateChange: the clock-free EngineEvent, the per-resolution emitter handle, sinks, and the boundary that stamps time and trace ids. The invariant is that dropping every event changes nothing |
MVP.md |
The smallest slice that proves the platform, + Definition of Done (finalized) |
TODO.md |
The MVP as ordered units of work (W0–W19), each with contract refs, dependencies, and done-criteria; the MVP boundary is marked |
OPEN-QUESTIONS.md |
Living register of unknowns, gaps, deferred decisions. §1 is now a decision log — the eight MVP-blocking gaps are all resolved; start here for the reasoning behind a contract |
The engine npm package (package.json, tsconfig.json strict, eslint.config.js with
the determinism guard, README.md). Phase 1 core under src/engine/src/core/:
determinism/pcg32.ts (seeded PCG32, verified bit-identical to reference vectors),
persistence/canonical.ts (canonical serialization), each with a .test.ts alongside. The
contracts it builds against are the canonical core and kind documents; current work is
stabilization and release hardening, not Phase 1 bootstrap.
Numbering is positional. Inserting a doc between existing ones means renumbering
everything after it and rewriting every cross-link. Prefer appending. Reordering is now
free — reorder the list in docs/sidebar.ts — but inserting still is not.
03-story-graph-kind ↔ 04-core drift: 04 implements 03 as types. An edit to one
not mirrored in the other is this project's most common defect. When a type changes, update
the prose description, any example, the projection, and the validation/test list too.
Envelope-duplication recurs — five times now. A kind must not duplicate what the
GameState envelope, Campaign, or registry already own. The full ledger, in order:
StoryGraphKindStateduplicated envelope fields (03 §8.1)StoryGraphCampaignduplicatedid/version/titleKey/stringsbelonging toCampaign/ the registry (04-core §10.1)StoryGraphViewduplicated scene and status fields (03 §9 vs 04-core §6/§9)SimulationKindStatecarried seven envelope fields plus a persistedRngState(10 §2)ResortGameStatecarried six plus a persistedRngState(12 §3)
This ledger itself drifted — four documents carried four different counts, none of them complete, because each was written from memory rather than from the list. When updating it, add to the list and re-count; do not increment a number.
It recurs on the view side too, not just state and content (entry 3). Whenever a kind mirrors a core concept, check the identity fields live in exactly one place — the envelope, not the kind.
Determinism is enforced, not hoped for. The eslint guard bans Math.random, the
non-bit-stable Math.* functions, and Date.now in src/. The core must replay
byte-for-byte from a seed and inputs; the determinism harness (04-core §14) is the
acceptance test with teeth.
The specs are a Docusaurus site. docs/ is both the Docusaurus overlay and the Docker
build context:
design/— the canonical specs.docs/docs/engine/— their generated human-facing pages.docs/docs/guide/— generated and authored how-to pages for working on the code and the site. Sidebar order and sections are stated indocs/sidebar.ts, not inferred from filenames or front matter — that is how04-coresits before03-story-graph-kind, matching the stated reading order. A new page is invisible until it is listed there; an id that does not resolve fails the build.build/ConvertTo-HumanDocumentation.ps1— the design → human-doc generator. Run it after editing marked blocks indesign/; run/make-human-docsfordocs/docs/guide.md, then stamp the guide with./build/ConvertTo-HumanDocumentation.ps1 -StampGuide. Never edit a generated engine page directly../build/Test-Documentation.ps1runs the corresponding-Checkautomatically.docs/docusaurus.config.ts,docs/sidebar.ts— local overrides of the base image's defaults (a manual sidebar — grouping by folder would move files and change every URL; all three Docusaurus link checks are'throw', anchors included — they were'warn'only while a static file held the site root, andbuild/Test-Documentation.ps1is the second gate, covering the relative links and anchors Docusaurus resolves differently; seeagent.md, Two link checks). Spec docs namesrc/engine/code by its repository-root path in prose —src/engine/eslint.config.js— never by a relative traversal, and never as a markdown link.docs/is the whole Docker build context (COPY . .), sosrc/engine/is not in the image: a relative link out ofdocs/docs/engine/would resolve to nothing and fail the production build underonBrokenLinks: 'throw'. A relative path in prose is no better — it is clickable nowhere, meaningless on the published page (which lives at a URL, not a directory), and correct only while reading raw markdown in a checkout. To point a reader at the code, link the guide page (/docs/guide/engine-package), which is a real route.docs/src/pages/index.md— the site root, generated fromREADME.md. Do not edit; edit the README..config/DocumentationRules.psd1registers it for drift checking, and the gate fails the build if the two disagree. BecauserouteBasePathis'docs', the README becomes the root page rather than the docs index, and the generator appends a[View the documentation](/docs/)link to it.docs/docs/index.md— the/docs/landing page, listing the specs in reading order. Ordinary authored content: it used to be the generated copy of the README, and no longer is.docs/Dockerfile— extendsghcr.io/the-running-dev/docs-templateandCOPY . .overlays the above onto/template.
Run it with docs.ps1 (repo root; needs Docker Desktop running). It is installed by
Invoke-SetupDocs, not committed — the installer rewrites it for this project's paths and
tag, so it is generated rather than vendored. A hand-written copy predating the template used
to be committed here and was deleted; if the file is missing, run the installer. The installed
version also regenerates docs/src/pages/index.md from README.md on every run, which
retires doing that by hand:
| Command | Does |
|---|---|
./docs.ps1 |
Build the image, run it, serve http://localhost:3000/docs/engine/vision |
./docs.ps1 -Live |
Same, but bind-mounts docs/ so edits hot-reload without a rebuild |
./docs.ps1 -BuildOnly |
Build the image only |
Personal skill at ~/.claude/skills/graphify/. Turns the folder into a knowledge graph
with community detection. There is no current graph in this repo — run /graphify to
build one.
- On the code (
src/engine/) it is nearly free — TypeScript is extracted structurally via AST, no LLM. Worth it as the code grows. - On the prose specs it is expensive (~200K tokens for a full rebuild) and its
value is marginal on this small a corpus — reading the docs directly finds more. Don't
full-rebuild the specs casually; use
--cluster-only(free) orquery. - TRAP —
--updatedestroys cross-file edges.build_mergedrops every edge a re-extracted file owns, and chunked LLM extraction only recreates an edge when both endpoints are in the same chunk. When running--update, extract the changed files together with everything they cross-reference; verify cross-file edge counts against the backup afterward.
Plugin at ~/.claude/plugins/cache/thedotmack/claude-mem/. /claude-mem:learn-codebase
reads every file in full — run it after a long session of many small edits; editing from
diffs accumulates drift only a full read catches. /claude-mem:cloud-sync uploads to
cmem.ai Pro and needs a token the user places themselves (Claude never handles
credentials). Troubleshoot with npx claude-mem doctor.
- claude-mem — background worker on
localhost:37777, captures observations passively. - Claude's file memory — plain markdown under
~/.claude/projects/D--Dropbox-Projects-SubZeroDev-GameEngine/memory/, written deliberately, loaded each session viaMEMORY.md.
Independent. Neither affects the other.
Findings and review items are presented one at a time for sign-off, not applied in
bulk. When a suggestion is declined, record it in the affected document (or
OPEN-QUESTIONS.md) as a known-and-retained issue rather than dropping it silently.
A reconciliation ends in a decision, not a report. Any time you compare two things and
find they disagree — /reconcile, /install, /track drift, a spec checked against the
code, or any time the user says "reconcile" — the work is not finished at the findings.
Close by asking, one divergence at a time, each with a recommendation and what the
alternatives cost. A report the user has to turn into questions is half the job.
Recommend the resolution: what changes, in which file, and what reversing it costs. If
nothing diverged, say so plainly rather than manufacturing a fork.
/redteam is the one exception, and only partly: it must not propose fixes, since naming a
fix frames the problem. It still recommends a classification — defect, accepted risk,
brief conflict, or not sustained.
Lessons learned the hard way live in agent.md.
Before editing: git status --short --branch, git remote -v, and enough of a file listing
to know what is actually here. Read this file and the relevant sources completely — this
project's recurring defect is editing from memory rather than from the artifact. Preserve
unrelated work; never commit secrets, caches, or build output (artifacts/, dist/,
node_modules/). Work on a focused branch.
Match capability and reasoning effort to the task, not to the tool that reached it. Use the smallest model and lowest effort that still produces production-quality output; reserve the strongest model and highest effort for genuinely ambiguous or architectural work. Budget scales with complexity, not size — a one-line change to the determinism guard is architectural; a 500-line spec transcription against a settled seam is not.
| Tier | Work | Model here |
|---|---|---|
| Deep reasoning | Architecture, specs, API/seam design, root-cause analysis, multi-step planning, comparing implementation approaches, security and performance strategy | Opus, high effort |
| Implementation | Code against settled contracts, tests, refactors, bug fixes, CI/infra, docs tightly coupled to implementation | Sonnet — or Opus at standard effort for significant features, large PRs, or hard bugs |
| High volume | Summaries, changelogs, commit messages, PR descriptions, formatting, triage, log and tool-output summarization | Haiku, default effort |
Worked examples from this repository: the W20–W26 programme split and the
10-simulation-kind outcome() reconciliation were deep-reasoning; W24's execution — eight
enumerated edits against an approved plan — was implementation tier and did not need Opus.
Division of control. The user sets the session model with /model; Claude sets
subagent models via the Agent tool's model parameter and scales its own reasoning depth.
Claude cannot switch its own session model — if a task warrants a different tier, say so
rather than silently over- or under-spending.
Never use max effort unless the user asks for it by name. xhigh is for one
question, not one phase — running a whole design pass at xhigh is not rigour, it is a
substitute for asking a precise question. If the session is stronger than the task needs,
just proceed; do not interrupt to say so.
Budget discipline.
- Do not spend reasoning to manufacture findings, alternatives, or open questions. "None at this level" is a valid result; a padded answer is worse than a short one.
- Once a decision is signed off and recorded, do not relitigate it without new evidence. Name the evidence if you think there is some.
- Spend frontier-model reasoning on decisions that are expensive to reverse, not on producing more prose.
- Never recommend re-running a phase gate. The user decides when a phase repeats;
/redteamcarries its own stopping rule.
What should stop being model work. The tiers above decide which model does a job. This decides whether a model should be doing it at all.
| Work | Where it belongs | |
|---|---|---|
| 🟢 Necessary | Architecture, contracts, root-cause analysis, design tradeoffs, adjudicating findings | A model, at the tier above |
| 🟡 Maybe avoidable | Regenerating context already established, duplicate repository scans, rewriting boilerplate | A model, but the repetition is a signal — say so |
| 🔴 Definitely avoidable | Formatting, mechanical text transformation, arithmetic over files, counting, collecting metrics | Code. It should leave the model entirely |
A red item is a defect in the tooling, not in the run. Noticing one is worth a line; performing it
repeatedly and never saying so is the failure. When a red item recurs, add it to the open register
in design/90-decisions.md so /track can turn it into an issue — that is the existing path, and
there is no separate mechanism for this. This repository has already taken that route once:
build/ConvertTo-HumanDocumentation.ps1 exists because regenerating human pages by hand was red.
Two distinctions that are easy to get wrong:
- The mechanical half of a task is red; the judgement half is not. Opening an issue is an API call, but deciding what warrants one is not. Writing a PR description is a template, but which merge convention governs is not. Do not classify a whole command by its cheapest step.
- Do not report a cost you did not measure. A model is not given its own token counts or
elapsed time, so any figure it states about its own run is an estimate presented as a
measurement.
tools/Measure-Session.ps1reads the real per-call usage from the session transcript, and runs as aSessionEndhook. It measures Claude Code sessions only — Codex writes a different schema this has no reader for, and Copilot records no token usage at all. Use it, or say nothing — under either of those, say nothing is the whole instruction.
- Non-goals are binding. Anything listed as a non-goal in
01-vision.md§5 is out of scope even if it looks trivial, even if you are already touching that file. - One unit at a time. Do not start W<n+1> because you noticed something while doing
W. Write it to
90-decisions.md's open register instead. - No new dependencies without a decision-log entry naming the alternatives rejected and why.
- No new public interfaces that are not in
20-contract.md. If you need one, stop and ask for a contract amendment. - Ask instead of assuming. If two readings of a spec are both defensible, stop and present both. Do not pick one and proceed.
- Every unit ends runnable. No half-wired states committed.
The pipeline's normal loop keeps design/ live: a unit lands, /reconcile writes reality
back, /track resyncs the tracker. That is right while the design is still being settled
and wrong once implementation is the bottleneck, because each pass is generative rather
than merely checking — landing W rewrites W<n+1>'s specification, which desyncs the
tracker, which needs /track, which finds drift, which needs /reconcile. The loop has no
fixed point. Freezing is how it is escaped.
design/FROZEN.md is the marker, and its existence is the whole mechanism. It is
tracked, not ignored — a freeze is a statement to everyone working in the repository, not
local state. While it exists:
/reconcileand/trackdo not run. The tracker is deliberately allowed to go stale./design,/contractand/slicesrefuse. Authoring is gated too, so the docs cannot drift forward while the implementation is being checked against them.- Units implement against
20-contract.mdas a fixed artifact, at the SHA the marker names. - A contradiction found while implementing is stated in that unit's pull request and left
in the document. Do not fix it in
design/. The staleness is the point; recording it in the PR is what makes the eventual reconciliation cheap.
/freeze writes the marker; /unfreeze lifts it — deletes the file, then runs one
reconciliation pass, /reconcile then /track, in the same session. /unfreeze runs
unattended, without a confirmation prompt; the freeze itself is still the user's decision,
made when /freeze is invoked, and lifting it early is one command call away rather than
gated a second time. A unit that turns out to need a contract amendment still stops and
says so; that escalation is the user's to answer, and answering it may well be "thaw,
amend, re-freeze."
The marker's format, which the five gated commands read and must not restate:
# design/ is frozen
Frozen at: <sha>, <YYYY-MM-DD>
Frozen because: <what the freeze is escaping>
Lifts when: <the checkable condition — "tier one is code-complete", not "when we are ready">
To lift: run `/unfreeze`, or delete this file by hand and run `/reconcile`, then `/track`.A command that refuses reports Frozen because and Lifts when verbatim rather than
paraphrasing them — the point of a stated condition is that it can be checked against, and a
paraphrase is where it stops being checkable.
.claude/commands/ holds the
agent kit's pipeline commands.
design/00-brief.md, 10-design.md, 20-contract.md, 30-slices.md, and
90-decisions.md are now their real canonical inputs. This repository extends the generic kit
in two deliberate ways:
- Existing W identifiers remain the slice ids. Renumbering them to S would break plans, tests, issues, changelog entries, and merged history without changing the work.
- The design and contract files contain marked, independently generated human pages. Commands must read the complete canonical file, including every marked block relevant to the task.
plans/ remains implementation history and handoff material. It may explain how one slice was
executed, but it never overrides the five canonical design files.
Generation workflow. After a canonical edit:
- Run
./build/ConvertTo-HumanDocumentation.ps1to regenerate detailed engine pages. - Run
/make-human-docsto regeneratedocs/docs/guide.mdwhen the brief, design, or contract changed semantically. - Run
./build/ConvertTo-HumanDocumentation.ps1 -StampGuideafter regenerating the guide. - Run
./build/Test-Documentation.ps1; it checks both exact generated pages and guide digest.
Routing, when a command is run:
| Command | Tier |
|---|---|
/brief-check, /design, /contract, /slices |
Opus, high |
/redteam |
strongest model, different vendor from the design author; if it must be Claude, a fresh Opus session |
/slice |
Sonnet, medium — high for a large or difficult work unit |
/reconcile |
Opus, high to decide which side of a drift is correct; Sonnet, medium to apply the edits |
/install |
Sonnet, medium |
/install-all |
Sonnet, medium — escalate only to judge whether a per-repo hard stop is actually safe to resolve; never to resolve it unattended |
/make-human-docs |
Sonnet, medium — generate the developer guide from canonical design/, then stamp its digest |
/track |
Sonnet, medium — escalate only to judge whether a drifted work unit is a design change |
/verify |
Sonnet, medium — escalate to deep reasoning only to diagnose a failure, never to run the gates |
/pr |
Sonnet, medium |
/resolve |
Sonnet, medium — escalate to judge a contested finding, not to triage the obvious ones |
/refine |
Sonnet, medium — never escalates; an architectural ask is routed to the command that owns it, not refined |
/kit-help |
Haiku, low — orientation from file existence and a tracker listing; escalate only where the repository's state matches no stage |
/done |
Haiku, low — mechanical git housekeeping; escalate only to judge whether an unmerged-looking branch is actually safe to delete |
/freeze |
Sonnet, medium — Frozen because/Lifts when come from the user, never invented |
/unfreeze |
Sonnet, medium for the sequencing; runs /reconcile (Opus, high) and /track (Sonnet, medium) as its own phases |
/track reads design/30-slices.md. It opens one issue per unvisited W-numbered work unit;
W is this repository's retained slice prefix. New units carry stable per-criterion ids
(W42.1, W42.2, never reused or renumbered — the same discipline as the existing positional
W3a unit numbering, one level down), and issues open in the shape .claude/commands/track.md
defines: a human-first narrative, ### Done when checkboxes, and a fenced <!-- agent:start -->
block that /track regenerates but never overwrites outside the fence. Existing checked units
predate this and are not retrofitted or reopened.
Tracking work. Opening, labelling, closing, commenting on, and editing an issue — including
one opened by someone else — needs no per-instance approval in a repository the user owns;
issues are cheap and reversible. Creating a milestone or a project is carved out the same way;
deleting either is not, since that direction is not cheaply reversible. Writing to a repository
the user does not own is never carved out. Resolving or replying to a review thread is not
covered by this carve-out — a pull request's review threads are a different object and stay
authorized regardless, same as any other external write in Git and Pull Requests below.
/track owns every GitHub write it can make idempotent; closing an issue and ticking a
checkbox are the exceptions — the command that observes the work done (/track, /slice)
does those directly, in the same run, rather than waiting for a sync pass.
Session boundaries. Routing above says which model runs a command. This says when a session must end. A boundary exists wherever carrying context would corrupt the next step's judgement, or wherever the next step must read the tree rather than remember it. The artifact is the handoff, not the conversation — a stage that writes one has already handed over everything the next stage is entitled to.
| Boundary | Rule | Why |
|---|---|---|
/design → /redteam |
Fresh session, and a different vendor. | A model recognises its own output distribution and defends it. Fresh context on the same model is already the weak form; the same session is not a review at all. |
Any stage that writes a canonical design/ file → the next |
Fresh. | The next stage's input is the committed file. A session that also remembers the arguments behind it will design against the arguments. |
/slices → /slice |
Fresh, and one work unit per session. | A unit that does not fit one session without compaction is too large — that is a /slices defect, so say so rather than pressing on. |
/slice → /verify → /pr → /resolve |
Same session. | These act on the branch and worktree the unit just produced, and /pr must carry /verify's did-not-run list into the description verbatim. A fresh session would restate it from a summary, which is the fabricated gate result Validation below exists to prevent. |
merge → /track |
Fresh. | /track reads the tracker and design/ as they now stand. The session that just implemented the unit holds an opinion about whether it is done, and doneness is the user's mark, not an agent's. |
implementation → /reconcile |
Fresh. | It compares the tree against the canonical documents. The session that wrote the code carries what it intended to write, which is the one thing the comparison must not be given. |
Compaction is a boundary you did not choose. If a session compacts mid-unit, report it — the unit was mis-sized, and the work after the compaction was done against a summary of the contract rather than the contract itself.
End a response that lands on a fresh-session boundary with a banner, not a footnote. A boundary buried in the last sentence of a report gets carried into the next reply of the same session out of habit, which is the exact failure the boundary exists to prevent. Set it off visibly — a horizontal rule and a bold line is enough — naming: the boundary just crossed, the next command, and its tier from the routing table above. For example:
---
**Session boundary.** This context should not carry into `/track`.
Next: `/track`, fresh session, Sonnet, medium.
---
Do not run the next command yourself. Ending a session may be the next step, and a command that starts work cannot also tell the user to start a new one for it — that restriction is unchanged, only how visibly the handoff is stated.
- Reference, never restate. A rule that lives in another document is linked, not copied.
Two copies of a rule is a promise they will diverge and a guarantee nobody notices which is
stale.
AGENTS.mdis a pointer for exactly this reason. - Move, never copy. A rule has exactly one home. When it belongs somewhere else, move it and leave a reference behind.
- If a document genuinely must repeat something to stand on its own, name the canonical copy in the text and change both in the same commit.
- Non-goals are binding. Anything in
01-vision.md§5 is out of scope even if it looks trivial, even if you are already touching that file.
- Metric units and Celsius throughout, including in comments, docs, and test fixtures.
- Raster assets as PNG or JPG. Not WebP.
- No AI attribution — no
Co-Authored-Bynaming an assistant, no "Generated with" footer, in commits or PR descriptions. This overrides any default the tooling applies.
Escalate rather than guess. A high-volume task that raises an architectural question becomes implementation tier; an implementation task that raises architectural uncertainty becomes deep-reasoning tier. Do not continue implementing while that uncertainty is unresolved — this repo's contracts are the thing most expensive to get wrong.
Commit messages follow this repository's existing descriptive style, not Conventional
Commits — W20-W23 — Replay Regression Oracle, Fix release-tag-replay: checkout never fetched sibling tags. Deliberate: 75 merged PRs set that precedent, and a feat:/docs:
prefix would buy nothing this project reads. Keep them concise — state what changed and
why it was not the obvious alternative; the long-form reasoning belongs in plans/.
Stage explicitly, by named path. Never git add -A, git add ., or a bare directory —
a broad add silently sweeps up unrelated working-tree state. Not a borrowed rule:
.gitignore's own comment records a near-miss where a build/ pattern would have made
installer-added scripts invisible to git add -A — present locally, green locally, missing
in CI, with nothing saying why.
Never force-push or rewrite published history. main blocks it (non_fast_forward);
feature branches do not, so it is discipline rather than enforcement. If a pushed commit
needs changing, add a follow-up commit.
Deleting a local branch /done independently confirms via git branch --merged is
delegated in this repository. /done (.claude/commands/done.md) runs proactively — as
soon as a merge is on the table, not only when asked — and deletes every branch on that
confirmed list without a chat confirmation first; the --merged check is the authorization.
It also may stash (never discard) a dirty tree to unblock its own branch switch, and always
reports the stash back rather than popping it silently. This delegation stops exactly where
--merged stops: a branch it did not confirm, or a -d refusal on one it did, still needs a
separate ask before anything stronger (-D) is even considered.
/slice may push the branch it creates and open its PR as a draft, without asking —
carved out of the authorization rule the same as an issue: a draft blocks no one and requests
no review, so opening one carries the same reversibility argument. Marking that PR ready for
review, and merging it, are not carved out and stay /pr's and the user's respectively.
Do not enable auto-merge. Open the PR, report the check outcomes, and leave the merge to
the user. (Auto-merge is enabled at the repository level and required_approving_review_count
is 0, so it would work — this is a deliberate workflow choice, not a limitation.)
Push every commit before announcing a PR is ready. Announcing invites an immediate merge, and a commit pushed after that lands on a branch nobody merges — PR #77's second commit was squashed out exactly this way and needed recovering as PR #78. With auto-merge off, this discipline is the only thing preventing it.
Three required checks on main — engine, Documentation links and terminology,
Verify Documentation Build. The deploy job is not required (it runs only on main, so
requiring it would leave every PR pending).
required_review_thread_resolution is on. Automated PR review runs via the
anthropics/claude-code-action GitHub Action (Qodo is retired and no longer in use).
Automated review comments can leave conversation threads that do not appear in
gh pr view --json reviewRequests,latestReviews; query threads directly via the GraphQL
reviewThreads field. Resolve a thread only when a validated fix satisfies it; leave
ambiguous findings open and report them.
Run what applies, and do not claim a gate passed that did not run:
./build/Test-Documentation.ps1 # links, terminology, generated-doc drift
cd src/engine; npm run typecheck; npm run lint; npm test # the `engine` job's own three
git diff --check
git status --short --branch./docs.ps1 -BuildOnly is the production Docusaurus build (onBrokenLinks: 'throw', the only
gate that resolves routes and heading anchors). It needs Docker and an installed
docs.ps1. When either is missing, say so plainly and verify the Verify Documentation Build
check on the PR instead — never report it as locally passing.
Never state or imply a published URL until the deploy workflow for that exact merge commit reports success. A merged PR is not a deployed site; poll the run rather than estimating.
A regression test is verified by reverting the fix and confirming it fails. A test that passes with and without the fix guards nothing.
A schema or validator change is not done until it has rejected something. Positive and negative cases both, with the counts stated. A validator that has never failed is not known to constrain anything.
The conventions above were reconciled from The-Running-Dev's blog-repository guidelines.
Recorded here so the same reconciliation is not redone, and so the non-transferring rules are
not re-imported by someone reading that document next to this one.
Two of its rules are inverted here and must not be adopted:
- "Do not restore
build/ConvertTo-DocumentationHomepage.ps1ordocs/src/pages/index.md" — correct there, because its blog owns/. Both are load-bearing here:routeBasePathis'docs', so the README is the site root, generated intodocs/src/pages/index.mdand drift-checked by.config/DocumentationRules.psd1. - "Required PR checks are" — it lists two. This repo requires three;
enginehas no equivalent there and is this repository's most valuable gate.
Absent here, so the rules referencing them do not apply: tools/blog-mcp/ (and every
blog_* tool), docs/blog/, docs/src/pages/blog/, MILESTONES.md, .config/blog.json,
build/Test-DocumentationArtifact.ps1, .agents/workflows/. There is no blog, no
front-matter/tag/slug contract, and no /welcome/ route — this repository publishes specs
under /docs/.