Part of the OpenCrane agent guidance. See
AGENTS.mdfor the index.
Three files track work, each with a distinct role β keep them from drifting:
plan.mdβ the open backlog: phases/tracks not yet complete. Items carry status; completed tracks leave a one-lineβ COMPLETE (see plan-done.md)pointer.plan-done.mdβ the historical record of finished tracks (large; the detailed "what shipped and how" lives here, e.g. multi-instance Track MI, ClusterTenant CT.*).CHANGELOG.mdβ capability log; sections map to git tags,## [Unreleased]holds in-flight work. Written in functional terms, not commit restatement.
- Keep
plan.mdupdated as implementation progresses. - Read
versioning.mdbefore a wave that touches the database schema or the release manifest: pre-1.0 the only version evidence is the current manifest's baseline digest β update it in the same wave as the schema change. - When a roadmap item changes state due to code, validation, or a discovered blocker, update
plan.mdin the same work cycle. - Do not leave completed or partially implemented backlog items stale in
plan.mdafter landing the corresponding code. - When a track or phase is fully complete, move it out of
plan.mdintoplan-done.md(the historical record) and leave a one-lineβ COMPLETE (see plan-done.md)pointer in its place. - When a phase or track completes, update
CHANGELOG.mdin the same work cycle, version by version (sections map to git tags; in-progress work goes under## [Unreleased]). Never let a release/tag land without a corresponding changelog entry.- Write entries in functional, capability-first terms β what an operator/tenant/integrator can now do, or do differently, that they couldn't before β never a restatement of commits. Name a mechanism (flag or endpoint) only when it helps the reader use the feature. Collapse many commits into the single capability they deliver.
- Delegate this to the
changelogagent (.claude/agents/changelog.md, runs on Sonnet), which encodes this style; or follow that file's rules if writing the entry inline.
Before creating, refreshing, rebasing, or reporting a pull request, inspect the live PR list and the candidate's actual commit/diff ancestry. Identify every earlier PR whose branch, commits, or functional diff the candidate builds on. Resolve each predecessor in exactly one of these ways:
- Stack it: set the candidate PR's base to the predecessor's head branch. The candidate diff must then contain only its incremental change. Name the review order in the PR body and status report.
- Absorb it: include the predecessor completely in the candidate, close the superseded PR, and retarget any dependants. The surviving PR becomes the only place where that combined diff is reviewed.
Never leave a PR based on the integration branch while its diff repeats an open predecessor. Never
ask for review until base...head has been checked against the chosen live base and contains no
already-reviewed predecessor material except an explicitly documented, inseparable absorption.
When neither stacking nor absorption is safe, stop and resolve the ancestry before publishing.
The hand-off must state the resulting stack order, absorbed/closed PRs, and intentionally independent PRs. A passing check, remembered branch relationship, or similar title is not evidence of ancestry; re-read the current PR bases, heads, commits, and changed files.
Run npm run check:pr-stack-integrity -- --current-branch "$(git branch --show-current)" at every
PR checkpoint. The checker takes two live GitHub snapshots around exact ref fetches, rejects a graph
that changed during inspection, and binds its evidence to every open PR's base/head SHA, incremental
diff digest, stable patch id, parent edge, and topological review level. It fails when an open child
still targets a merged/closed feature branch, when a parent head was rewritten without restacking its
children, or when one open PR contains another outside its declared base chain. Patch ids detect
replayed whole patches; semantic overlap after squashing still needs reviewer judgment.
Re-read the live graph and regenerate SHA-bound evidence after every event that can invalidate an earlier conclusion, and at least once per hour while a task remains active:
| Event | Required checkpoint |
|---|---|
| Slice/wave commit or review-fix commit | Record the new HEAD; review the explicit WAVE_BASE...HEAD range plus staged, unstaged, and untracked overlays. |
| Parent push, force-update, or rebase | Re-fetch the parent SHA and restack every descendant before unrelated work continues. |
| Parent PR merged | Before merging its child, retarget that child to the integration branch and prove the merged parent head is now ancestral there. Never merge a child into an already-merged feature branch. |
| Authorized push | Confirm the remote head equals the reviewed local HEAD; stale local evidence is invalid. |
| PR open, refresh, edit, or base change | Run the live stack checker and record the PR number, base ref/SHA, head ref/SHA, and review order. |
| Pre-handoff or pre-merge report | Validate both the incremental base...head diff and the cumulative integration-base-to-stack-tip range. If integration is not ancestral to the tip, require a clean git merge-tree --write-tree <integration-sha> <tip-sha> simulation. |
Treat committed, staged, unstaged, and untracked files as four separate overlays. Never use
git diff HEAD as proof of a complete change set: staged and unstaged changes can cancel in that
view, and committed work disappears from it. Evidence from an earlier SHA, base, remote head, or
overlay manifest is stale and must not be reported as current.
At the first wave checkpoint, persist the immutable local review base with
git config "branch.$(git branch --show-current).opencraneWaveBase" "$(git rev-parse HEAD)". The
Stop gate fails closed on a committed pre-PR branch when neither live PR evidence nor this recorded
base exists. Update it only when deliberately starting a new reviewed wave, never to silence a gate.
- Always end each work cycle with a suggested commit message.
- Every commit subject must start with an emoji that matches the primary intent of the change.
Use the table below β it is derived from this repository's own commit history, so following it
keeps
git logconsistent with the convention already established here. - Use imperative mood for the subject line (e.g.
add,fix,update, notaddedoradding). - Keep the subject line under 72 characters.
- If the change touches multiple concerns, list them as bullet points in the body. When a secondary
concern is significant, you may append a second emoji after the first (history does this β e.g.
π±β¨,π π§,π§ π₯); lead with the emoji for the primary intent. - Do not add a Claude / AI co-author trailer (no
Co-Authored-By: Claude β¦). Commits are authored solely by the configured git user.
Emoji convention (derived from commit history; the count is how often it already appears):
| Intent | Emoji | Notes / what it has marked here |
|---|---|---|
| Configuration / tooling / infra wiring (most common) | π§ | Helm scaffold, cluster/scope config, deploy plumbing (45Γ) |
| New feature / capability | β¨ | A new subsystem or API surface (19Γ) |
| Enhancement / extend an existing capability | β‘ | Increment to a shipped capability β metrics, versioning, bindings (5Γ) |
| Bug fix / typing fix / address review findings | π | (7Γ) |
| Refactor β no behaviour change | β»οΈ | Move shared code, align typing/signals (7Γ) |
| Move / rename / restructure files | π | Split src/ into packages, relocate tests (4Γ) |
| Remove code, files, or infra | π₯ | Delete dead infra (e.g. remove Crossplane) (13Γ) |
| Security / auth / RBAC / TLS / NetworkPolicy | ποΈ | IAM-first changes (13Γ) |
| Documentation | π | Docs + plan.md updates (14Γ) |
| Notes / progress / readmes | π | Lighter-weight notes & progress (6Γ) |
| Architecture / plan / design updates | π‘ | High-level design & phase planning (6Γ) |
| Agent / prompt / AI-loop / meta-config | π± | AGENTS.md, agent defs, prompt/loop tuning (5Γ) |
| Cosmetic / UI polish | π¨ | Visual-only tweaks (10Γ) |
| Deploy / launch | π | Launch scripts, local deploy fixes (2Γ) |
| Tests | π§ͺ | Test-only additions |
| Work in progress | π§ | Incomplete checkpoint |
When an intent isn't covered above, pick the closest gitmoji and prefer reusing an emoji already in this table over introducing a new one.
Before adding substantial production code in any language, run:
npm run check:module-growthFor a branch or PR range, use npm run check:module-growth -- --diff <base-ref>. The checker is
diff-scoped and excludes tests, generated code, dependencies, fixtures, and build output.
- A review candidate requires a responsibility inventory and the maintainability review dimension.
- A module that crosses or grows beyond the configured hard maximum must be split or receive a temporary exact-path exception with an owner, reason, and expiry.
- Size is only the trigger. Architecture and review findings must demonstrate a concrete cohesion, ownership, dependency, ordering, or core-path testing problem.
See maintainability.md for the cross-language policy and exception
contract.
The self-review compliance table is a self-check and
is not sufficient on its own. A policy-driven Stop gate decides β per change β whether an
independent review is required before the turn can end. When the gate asks for review you must:
- Delegate one integrated
@reviewpass against the explicit change scope, or use/review-loopto coordinate independent risk areas and verify disputed findings. Both satisfy the same review requirement. - Resolve every Critical and High finding it returns β fix it, or justify in your response why it is not applicable.
- Document the change in its owning code and package README. Use
@commentsafter code settles for consequential contracts, unresolved documentation findings, or explicit documentation work. - Finish the coherent slice once the relevant checks and required review pass.
Default to one integrated independent reviewer when the risk policy requires it. Use architecture pre/post for new or moved deployables/libraries, trust or responsibility changes, or a demonstrated module-cohesion problem. Use reaper pre/post when replacing a mechanism requires a survivor/drop classification. Ordinary residue and maintainability belong in the integrated review. Fan out review dimensions only when independent risk areas justify it; use a separate verifier for disputed or uncertain consequential findings rather than every suggestion.
The implementer owns ordinary documentation. A comments specialist works from the complete diff, including removals, plus the controlling issue or decision. Every explanation needs evidence; an unresolved reason is reported rather than invented. Documentation depth follows consequence. After proving comments-only edits, reuse existing build/test evidence and run only syntax, lint, doctest, or generated-document checks that can be affected.
Update a package README in the same slice when its public surface, boundary, invariant, models, or
configuration changes. See package-docs.md. Pass reviewers the exact source
scope and already completed validation; rerun checks only after relevant source changes or failures.
Run scripts/agent-style-check.sh, npm run check:prisma-boundaries -- --diff <base-ref>,
npm run check:module-growth, npm run check:release-versioning, and
npm run check:pr-stack-integrity -- --current-branch <branch>
when relevant to the changed responsibility, before delegating. Reuse recorded results for the same
source scope. The first checks TypeScript mechanics and invokes
the same diff-scoped Prisma ownership floor; the explicit Prisma command is useful when reporting
that gate separately; the final command produces language-neutral architecture candidates.
The Prisma gate authorizes exact adapter class/path/contract tuples, delegate ownership, transaction
ownership, and transaction-scoped repository construction. All four raw Prisma methods are
forbidden in production TypeScript, including inside an authorized repository, and exemptions
cannot enable them. A policy-only rename or stale construction declaration is therefore a failing
change, not an implicit exemption. Construction also proves the target constructor and exact
callback binding are transaction-scoped; a root client substitution fails even when the class and
import still match policy.
Mechanical errors are cheaper to fix before review, while module-growth warnings give the reviewer
the exact files that need a responsibility inventory.
When a change touches a workload, app composition, agent-domain project metadata, TypeScript alias, or dependency boundary, run its current boundary guard before review:
npm run check:workload-ownership-app-compositionand its negative test for workload ownership, rendered profiles, runtime-created Jobs, and thin app roots;npm run check:agent-domain-boundaryand its negative test for personal, shared execution, and operator domains.
How the gate decides:
.claude/hooks/require-review.shβ a free shell pre-filter. It skips the obvious cases (no supported production-source change, trivial size, test/type-only/generated files, already-reviewed) and escalates the rest. Its fingerprint includes the live PR-stack evidence, committed base range,HEAD, staged diff, unstaged diff, and untracked source bodies. It writes.claude/.review-context.mdfor the judge.- In Claude Code, a Haiku agent hook reads that context plus
.claude/review-policy.mdin parallel with the pre-filter. It judges whether the change carries real risk (auth, secrets, network, IAM, money, or non-trivial production control flow) and blocks only when warranted. - In Codex,
.codex/hooks/require-review.shruns the same pre-filter. Codex has no separate judge model, soJUDGEbecomes aStopcontinuation that makes the model judge its own change against the same judgment guidance, and run the independent review agent when that guidance says the change carries real risk. An explicitSKIPlets the turn end; a checker crash, missing context, or unknown verdict blocks closed and requires the full review.
.claude/review-policy.md is the single tunable surface. If review fires too often
and burns tokens β or misses something β edit that file (threshold, always-review
keywords, never-review-paths, or the judgment guidance) and record it in its tuning log.
The gate blocks at most once per stop sequence (loop-safety via stop_hook_active),
so it can never trap a turn β but skipping the review when it fires defeats the purpose.
Treat a block as a hard requirement, not a suggestion.