Skip to content

Claude context architecture — the operating manual and the always-loaded context ​

Assessed 2026-09-23. The question: the repo-root CLAUDE.md has grown past 3,600 lines and the IDE's context counter shows it at 121.3K tokens on every session. How should the project's knowledge be organised so that Claude carries a small, always-correct constitution and reads the detailed knowledge when a task needs it, without losing any of the hard-won rules, and without the root file growing back?

This page is the assessment and the proposal. Every number on it was produced by a command or read off the IDE counter; the method is stated beside each one.

STATUS — ASSESSMENT ONLY. THE MIGRATION IS NOT APPROVED.

Owner decision 2026-09-23: do not proceed yet. The owner reviews this page in full first. Nothing in CLAUDE.md, .claude/, the project-state record or the memory index has been changed. Sections marked PROPOSED describe what would be built; the tracker row is QRS-1288.

THE FINDING THAT REFRAMES THE QUESTION

CLAUDE.md was ~93% of what actually reached the model before the first user turn (~130K tokens). The SessionStart hook printed the 1,375-line project-state record at every startup, resume and compaction, but Claude Code injects only a 2 KB preview of hook output above ~10,000 characters, so the record never arrived (measured 2026-09-24, QRS-1289); the memory index adds ~8K. The state record's problem is therefore delivery, not cost: the hand-off a reset session depends on was never handed off.

Recommendation in one line: CLAUDE.md becomes a ~300-line constitution plus a routing layer (~11K tokens); detailed knowledge lives in this portal (already the gated SSOT); .claude/rules/ with path globs plus three small hooks make the "read this before changing that" step fire by mechanism, and for the money path, migrations and releases, an edit is refused until the mandatory pages were opened. Nothing is deleted; every moved paragraph gets a named home first.

1 · Current state ​

1.1 Size and cost ​

PayloadLinesCharsTokensHow measured
CLAUDE.md3,677329,956121,300IDE context counter (2026-09-23); implies 2.72 chars/token
dev-tracker/project-state.md, printed by the SessionStart hook1,375132,745~750 delivered⚠ a 2 KB preview (QRS-1289); ~48,800 had it arrived
~/.claude/…/memory/MEMORY.md, the auto-loaded memory index10422,478~8,100chars ÷ 2.72 (IDE shows 8.2K)
Fixed overhead before the first prompt~130,000sum of what was delivered

Why the file tokenises at 2.72 characters per token rather than the usual ~4: 4,458 backticks, 2,711 bold markers, 763 em dashes, 378 middle dots, 260 ⚠ glyphs and a very high density of identifiers, paths and numbers. Dense technical markdown is expensive per character, which is why the proposed ceiling is stated in characters as well as lines.

1.2 Where the tokens are ​

Measured fence-aware per top-level section (the Commands section is one 1,037-line fenced block):

SectionLines~TokensShare
## Commands — 24 scripts, each with its incident narrative1,037~36,00030%
## Architecture — 13 sub-sections968~31,70026%
## Non-negotiable engineering standards433~15,90013%
## Environments, CI/CD & promotion189~6,7006%
## Dev portal & tracker discipline159~5,5005%
Six owner rules + two product principles (lines 71–649)579~17,00014%
Everything else312~8,4007%

Two sections hold 56% of the file. The Commands block alone is five times the official recommended size of an entire CLAUDE.md.

1.3 Growth rate and shape ​

  • 172 commits have touched CLAUDE.md since 2026-07-10. In the last 30 days: 67 commits, +1,086 / −263 lines, net +823 — about 27 lines a day.
  • 811 lines (30 Jul) → 2,172 (13 Aug) → 2,756 (18 Aug) → 3,314 (4 Sep) → 3,677 (23 Sep). Linear, no plateau.
  • 66 lines carry a dated self-correction ("… until 2026-08-28"); 68 lines refer to the file itself ("this file said", "this bullet"). This is the growth mechanism: a correction is appended as a narrative beside the wrong sentence instead of replacing it, so the file accumulates its own errata. Each correction is valuable as a lesson and expensive as always-loaded text.
  • 239 ⚠ markers. When one line in fifteen is a warning, warnings stop working as emphasis.

1.4 What is genuinely always-needed today ​

Tested against "would Claude make a rule violation on an arbitrary task without this line":

  • Project identity, the two stacks, the three user categories as a rule (not the 87-line essay).
  • The six owner rules and "verified, never assumed" as rules — each is 40–110 lines today; the rule itself is 3–8 lines.
  • The Definition of Done.
  • About fifteen one-line hard invariants: the supabase.from() ban, zero hard-coded colours, no em dash in copy, the required idempotencyKey, payment_counts_as_collected(), never assume a Razorpay fee, the npm audit fix --force ban, the D: drive rule, reading a gate four ways, Dev as the source of truth, never git add -A here, proving the device runs your bundle.
  • The repo map, the ~12 daily commands, the Supabase project ref table, the authority order, and where to read next.

Everything else fails that test and is Tier 1 or lower (see §3).

1.5 Content that is too detailed, duplicated or historical for the root ​

  • Per-gate incident narratives in the Commands block (check:state 40 lines, check:db-health 55, check:sql 40, check:ef-drift 50, check:i18n-keys 30, check:arch-proposal 50, check:docs 35 …). Each tool's own header already carries the same story (verified for check-doc-claims.js, check-docs-vocabulary.js, session-state.mjs, and by the overlap sweep in §17 for eleven more). The block is a third copy of what the script header and the tracker row already say.
  • Architecture sub-sections that restate portal pages: payments (the heading itself names payments/index.md as the SSOT), release management (releases/hld.md, lld.md, process.md), data access (architecture/data-access.md), tiers (architecture/tiers.md), naming (guides/coding-standards.md already carries the five SQL families and the table-naming rule verbatim), Edge Functions (EDGE_FUNCTION_GUIDELINES.md is at the repo root; CLAUDE.md cites it under supabase/functions/, a path that does not resolve).
  • Dated state: the auth OTP SMTP diagnosis of 2026-08-01, the "two blockers now CLOSED" money-path block, the "this heading has been wrong four times" narrative, the delivery-log count saga, the design project table with two rounds of re-measurement, the SonarQube burn-down closed on 2026-07-30.
  • Duplicated inside the file: parity is stated under standards, under testing and under CI; "a standard with no gate decays" appears six times; QRS-246 is cited more than twenty times for the same lesson; the /:slug route correction appears three times; the design-project table appears twice.

1.6 Content whose only home is CLAUDE.md ​

The overlap sweep (§17) confirmed sixteen topics with no other home. Each has a named destination in §17.1; a migration creates the home before moving the text.

1.7 Dependencies that break if content moves ​

Measured, not inferred:

DependencyWhat bindsNaive move breaks…Handling
tools/check-doc-claims.js rule C3Every check: / test: / deploy: script name (31) must appear in CLAUDE.md proseMoving the Commands block fails check:claims at pre-push and CIRetarget C3 at the new gates catalogue page (the function already takes docPath); keep a names-only table in CLAUDE.md
check-doc-claims.js rules C1/C2The generated MEASURED-INVENTORY blockNothing, if the block staysKeep it: 19 lines, ~1.5K tokens, the best anti-hallucination anchor in the file
tools/check-docs-vocabulary.jsROOT_DOCS = ['CLAUDE.md','README.md'], baseline 39 mentions for CLAUDE.md, fails on a stale allowanceRemoving corrections drops the count → fails as "stale allowance"; the moved text lands in pages with no baseline → fails as new mentionsLower the root baseline and add per-page allowances in the same commit as each move; wrap warnings in ::: containers, which are exempt by design
check:docs scan rootsPortal + the two root docsAnything under .claude/ is ungated for retired vocabularyExtend ROOT_DOCS to .claude/rules/**/*.md and .claude/skills/**/SKILL.md
check:portal-navEvery portal page must be in config.mjsNew pages fail pre-commit until registeredRegister in the same commit
Twelve tools cite CLAUDE.md sections by name in comments (check-naming.js, check-design-parity.js, check-env-drift.js, check-function-config.js …)Comments, not assertionsDangling prose pointersUpdate to rule ids in the reference-validation phase
Portal index.md authority clause"Where this portal and CLAUDE.md disagree, CLAUDE.md wins"Inverts once detail moves hereRewrite to the authority order in §8
27 of 103 memory files cite CLAUDE.mdMostly by file nameStale section pointersOpportunistic update; not blocking

1.8 The two other always-loaded payloads ​

  • project-state.md has become a second CLAUDE.md. Its §1 "Where the work is right now" is 895 of 1,375 lines and holds full thread narratives, phase tables and superseded task lists. The hook printed the whole body at every startup|resume|compact; check:state gates recency only, never size. Above ~10,000 characters the harness injects a 2 KB preview, so none of it past the first ~2 KB was delivered.
  • MEMORY.md is at 22.5 KB against a documented 25 KB hard cap, past which content is silently dropped on load. It is one or two entries from truncating.

1.9 The loading mechanisms Claude Code actually provides ​

Verified against the current documentation (CLI 2.1.252):

MechanismLoads whenCostRole in the proposal
Root CLAUDE.mdEvery session, in full, stays in contextAlways paidConstitution + routing
@path imports in CLAUDE.mdEagerly at launch (4 hops; code fences ignored)Always paidNot used for detail — it is CLAUDE.md by another name
Nested <dir>/CLAUDE.mdLazily, when Claude reads a file in that directoryOn touchArea conventions for apps/mobile, apps/web, supabase, packages
.claude/rules/*.md with paths: globsLazily, when Claude reads a matching fileOn touchThe primary "before modifying X, read Y" layer
.claude/rules/*.md without paths:At launchAlways paidAvoided; gated
Skills (SKILL.md)Name + description always; body on invocation (manual or auto by description)Near zero until usedProcedures only, never mandatory reads
SessionStart / PreCompact hook stdoutInjected in fullAlways paidKept, capped
UserPromptSubmit hookCan read the prompt and inject conditionallyWhen it firesA pointer router for tasks that touch no file
Auto-memory MEMORY.mdIndex only; capped 200 lines / 25 KBAlways paidTrimmed
Official size guidance"Keep CLAUDE.md under 200 lines; longer files reduce adherence"—Sets the order of magnitude

ONE MECHANISM IS UNDOCUMENTED AND MUST BE TESTED FIRST

The docs say a path-scoped rule loads "when Claude reads files matching the pattern". They do not say whether a read through Bash (cat, sed -n, grep) counts, and this repo's sessions prefer Bash. Phase 0 of the proposal runs a sentinel test (five cases: Bash read · Read tool · longhand vs brace glob · Windows path · inside a subagent · after compaction). If two or more fail, the hooks in §16 are the primary control and path rules are documentation.

2 · Target budget for the root CLAUDE.md ​

Target: 280–320 lines, ≤ 30,000 characters, ≈ 10–12K tokens. Gate ceiling: 350 lines / 35,000 characters. Total fixed overhead target (CLAUDE.md + unconditional rules + session hook + memory index): ≤ 35K tokens, down from ~130K.

Why this range rather than "under 200 lines":

  • The official guidance is under 200 lines because adherence measurably degrades with length. This repo's constitution is denser than a typical project's: six owner-codified rules with gates, plus a three-user-category threat model that changes what authenticated means. Below 200 lines the why goes, and the why is the value.
  • Tokens are the cost, and here they run at 2.72 chars/token. 300 lines at ~95 chars is ~28.5K chars ≈ 10.5K tokens. The character ceiling matters more than the line ceiling because the observed failure is lines growing past 120 characters of dense markdown.
  • The marginal-value curve. The first ~10K tokens prevent whole classes of error on every task. The next 110K prevent errors on specific tasks and are indistinguishable from a page Claude is told to open when that task arrives. Loading them always charges every task to protect some.
  • Below ~250 lines the routing table does not fit, and routing is the one thing that must be always loaded for lazy loading to be safe.
  • Headroom is deliberately small. 350 is the ceiling, 300 the target, so a correction cannot be appended as a paragraph; it has to displace something or go to its home.

3 · Information hierarchy ​

Each tier is defined by what loads it, not by how important it feels — "important" is how every line got into CLAUDE.md.

TierDefinitionMechanismBudgetExamples
0 · ConstitutionNeeded on nearly every task; a mistake without it is a rule violationRoot CLAUDE.md, always loaded≤ 320 linesIdentity · three user categories · eight rules · DoD · hard invariants · repo map · daily commands · routing table · placement policy · inventory block · Supabase refs
1a · RouteWhich Tier-2/3 document is mandatory for a task shapeRouting table in CLAUDE.md + "Read before changing" at the top of each domain index~20–30 rows"Change the money path → payments/index.md"
1b · Touch rulesOne code area's invariants + mandatory-read list; fires without recall.claude/rules/<area>.md with paths:; nested <dir>/CLAUDE.md≤ 60 lines each, ≤ 12 filespayments.md, edge-functions.md, migrations.md, ui-systemic.md
2 · Domain knowledgeEverything needed to change one subsystemPortal domain index + pagesUnbounded, indexedpayments/*, architecture/platform-model.md, consumer/*
3 · ReferenceContracts, schemas, runbooks, gate catalogue, ADRs, tool headersPortal, EDGE_FUNCTION_GUIDELINES.md, PROMOTION_RUNBOOK.md, tools/* headers, parity contractsUnboundedguides/quality-gates.md (new), ADR-0001..0032
4 · HistoryWhy a decision was taken; what a sentence used to sayTracker rows, delivery log, ADR bodies, a corrections log (new), the documentation/ archiveNever auto-loadedThe 66 dated corrections, the OTP diagnosis

Procedures are orthogonal to the tiers. Step-by-step workflows (build both natives, promote to prod, pull a design, prove the device runs your bundle) belong in skills: a skill body costs nothing until invoked.

4 · Documentation architecture — PROPOSED ​

4.1 The decision: portal for knowledge, .claude/ for loading glue, CLAUDE.md for constitution + routing ​

A parallel knowledge base under .claude/docs/ was evaluated and rejected:

  • The portal is already the declared SSOT (its own index.md says so), with 240 pages and four gates over it: check:docs, check:portal-nav, check:docs-impact, check:claims. Anything under .claude/ is scanned by none of them today. A second knowledge base is the owner's own named failure: "an external page is a second home nobody maintains."
  • The portal renders, has a sidebar, search and ::: containers the vocabulary gate exempts. .claude/ has none of that.
  • .claude/ does hold what the portal cannot: files Claude Code loads by mechanism (rules/ with paths:, skills/, agents/, settings.json). That is its job.

4.2 Target tree — PROPOSED ​

/
├── CLAUDE.md                         Tier 0. Constitution + routing. ≤320 lines, gate-ceilinged.
├── README.md                         Humans. Unchanged.
├── EDGE_FUNCTION_GUIDELINES.md       Tier 3 (existing; CLAUDE.md's citation path is corrected)
│
├── .claude/
│   ├── settings.json                 hooks (existing) + three new (§16)
│   ├── rules/                        Tier 1b — EVERY file carries `paths:`; none loads at launch
│   │   ├── README.md                 what a rule file is, the 60-line cap, the template
│   │   ├── payments.md               money path: invariants · read-before-changing · gates
│   │   ├── edge-functions.md         _shared kit, Type A/B/webhook, config.toml, dropped-table helpers
│   │   ├── migrations.md             expand-contract, COMMENT ON, domain schemas (ADR-0031), core-entity protocol
│   │   ├── data-seam.md              packages/{schemas,domain,data}: RPC vs EF, idempotencyKey, stub ≠ wired
│   │   ├── ui-systemic.md            packages/tokens + apps/*/src/ui: design-first, drift ledger, parity rules
│   │   ├── screens.md                globs DERIVED from the parity contracts (see §16.1)
│   │   ├── setu-card.md              one renderer, manifests as data, purge-on-write, /:slug/setu-card
│   │   ├── consumer-biodata.md       biodata invariants, disclosure rules, ADR-0032 identity
│   │   ├── chat.md                   principals, phone as credential, one chat-core module
│   │   ├── media.md                  R2 only, setPublicMediaBaseUrl, fail-closed images
│   │   ├── i18n.md                   catalog keys, *marks*, no em dash, check:i18n-keys
│   │   ├── release.md                releases/**: release.json SSOT, change records, promotion checklist
│   │   ├── load-bearing-files.md     config.toml · release.json · package.json · workflows · app.json · eslint/metro configs
│   │   └── tools-and-gates.md        tools/**, .husky/**, .github/**: gate design rules, mutation tests, silence ≠ success
│   ├── skills/                       procedures (existing: deboard-dev-user; run-mobile/run-web live in apps/*)
│   │   ├── promote-to-prod/          the sixth rule's checklist as executable steps
│   │   ├── build-both-natives/       wraps guides/android-ios-build-and-test.md
│   │   ├── pull-design/              DesignSync → scratchpad → design:spec → contract
│   │   └── verify-device-bundle/     the four-step stale-bundle proof
│   ├── context-routing.json          task shape → keywords → mandatory pages (renders the CLAUDE.md table)
│   ├── agents/  workflows/           existing
│
├── apps/mobile/CLAUDE.md             Tier 1b (lazy): route/feature map, @/ui import rule, runners, ports
├── apps/web/CLAUDE.md                Tier 1b (lazy): SSR landmines, headers export, run-web driver
├── supabase/CLAUDE.md                Tier 1b (lazy): local stack, sb wrapper, refs table, archive warning
├── packages/CLAUDE.md                Tier 1b (lazy): DAG, no DOM/Node types, .ts extensions, naming gate
│
└── documentation/portal/             Tiers 2–4. The SSOT.
    ├── guides/
    │   ├── quality-gates.md          NEW — one H2 per check:/test:/deploy: script; check:claims C3 target
    │   ├── claude-code-operations.md NEW — skills, ports, aria-hidden, probe limits, Windows traps, stale bundle
    │   ├── prerequisites.md          NEW — the toolchain page CLAUDE.md already claimed existed
    │   └── claude-context-architecture.md   THIS PAGE — the placement policy's SSOT
    ├── architecture/
    │   ├── index.md                  NEW (no index today)
    │   ├── operating-rules.md        NEW — R-00…R-07 in full; CLAUDE.md's rule block is GENERATED from it
    │   └── platform-model.md         NEW — the four-layer model, enterprise tree, campaigns (ADR-0020..25 summary)
    ├── payments/index.md             gains "Read before changing" (the model for every domain index)
    ├── design-system/index.md        NEW (38 pages, no index today)
    ├── backend/index.md              NEW — kit, guidelines, config.toml, archive warning
    ├── features/ guides/ integrations/ overview/ verticals/   index.md NEW in each (none today)
    └── dev-tracker/
        └── operating-manual-corrections.md   NEW — the dated "this said X until…" log, append-only

4.3 Per-location contract ​

LocationBelongsDoes not belongRead whenMandatoryEvolves
CLAUDE.mdRules, invariants, map, routing, placement policy, inventory blockRationale beyond one sentence, incidents, per-gate detail, anything dated, code beyond a command nameAlwaysYesOnly by displacement; gate-ceilinged; corrections replace text and go to the log
.claude/rules/*.mdOne area's invariants + ordered mandatory-read list + gatesExplanations, history, anything not specific to the globbed pathsAutomatically on touchBy mechanismNew area ⇒ new file; every file has paths:; ≤ 60 lines
<dir>/CLAUDE.mdConventions a newcomer to that directory needsRules already in .claude/rulesLazily on first read thereBy mechanism≤ 120 lines
.claude/skills/Procedures with steps and commandsKnowledge, rulesOn invocationOptionalOne skill per procedure
Portal domain index"Read before changing" list, the domain's pages, ADRs, gates, related domainsDetailVia routing or ruleYes for that domainOne row per new page
Portal pagesEverything else, at any lengthNothing is too long hereWhen routedAs the index saysFreely; gated
operating-manual-corrections.mdEvery dated correction, verbatim, with what it said beforeLive rulesWhen investigating whyNoAppend-only
ADRs / tracker / delivery logDecisions, incidents, deliveriesLive operating rulesWhen routedNoExisting discipline

5 · Task-to-document routing — PROPOSED ​

Three layers; each catches what the previous cannot. A script can verify every target exists; none depends on Claude remembering.

5.1 Layer A — path-scoped rules fire on touch ​

.claude/rules/<area>.md with paths: globs. Reading a file under the razorpay-webhook function loads payments.md automatically. This catches the most dangerous case: a task that goes straight to code. Every rule file has the same four blocks so it can be linted:

markdown
---
paths:
  - "supabase/functions/razorpay-webhook/**"
  - "supabase/functions/reconcile-payments/**"
  - "packages/data/src/payments/**"
---
# Payments (money path)
## Invariants            ← ≤ 10 one-liners, each with its QRS/ADR
## Read before changing  ← ordered; the FIRST is always the domain index
## Gates to run          ← the exact npm scripts
## Do not                ← the 3–5 most expensive mistakes

5.2 Layer B — the routing table in CLAUDE.md, by task shape ​

Planning, assessment, design pulls, promotion, "why does X work this way": no file is read first, so no rule fires. The always-loaded table covers these by task shape, never by feature, and is rendered from .claude/context-routing.json so the table and the prompt router (§16) cannot drift.

Task shapeMandatory first readThen
Change anything on the money pathpayments/index.md → its Read-before-changing listrule payments.md fires on touch
Add or change a migrationguides/migrations.md · architecture/architecture-change-protocol.md for a core entityrule migrations.md
Add or change an Edge Functionbackend/index.md → EDGE_FUNCTION_GUIDELINES.mdrule edge-functions.md
Build or change a screendesign-system/index.md → screen-coverage-mandate → the parity contractrule screens.md
Touch packages/tokens or apps/*/src/uidesign-system/index.md § systemic surface · drift ledgerrule ui-systemic.md
Public Setu Card, templates, cachedesign-system/public-setu-card-spec.md · ADR-0019 · ADR-0027rule setu-card.md
Consumer / Marriage Biodataconsumer/index.md → end-to-end-plan.md · decisions.mdrule consumer-biodata.md
Chat, identity, addressingADR-0032 · communications/index.mdrule chat.md
Auth, OTP, sessionsguides/google-oauth-and-social-auth-setup.md · integrations/zeptomail.md · ADR-0018 (dangling)—
Release, promotion, deployreleases/index.md → process.md · PROMOTION_RUNBOOK.mdskill promote-to-prod
Any gate, hook or CI changeguides/quality-gates.mdrule tools-and-gates.md
A screen that does not exist yetdesign-system/screen-coverage-mandate.md § processskill pull-design
Device, build, parity verificationguides/android-ios-build-and-test.md · guides/parity-verification.mdskill build-both-natives
Anything architecturalarchitecture/current-state.md (read its date first) · the relevant ADR—
After a context resetproject-state (injected) → architecture/current-state.md—
Adding documentationthe placement policy (§7) → the domain index—

Rows are 1:1 with rule files and capped at 20: a feature cannot add a row without a rule, and the churn-coverage metric (§16) decides whether the rule is warranted.

5.3 Layer C — "Read before changing" at the top of every domain index ​

The routing table sends Claude to an index; the index carries the ordered reading path, the gates, and two to four questions whose answers must appear in the plan or hand-off. payments/index.md is the existing model. An answer that is not in the hand-off is a read that did not happen — the parity-contract discipline (pass · gap · blocked, never blank) applied to reading.

6 · The mandatory-read mechanism — PROPOSED ​

6.1 npm run check:context (new gate, pre-commit + CI) ​

RuleAssertsWhy
X1CLAUDE.md ≤ ceiling lines and ≤ ceiling chars, max line 140 (ratchet). Raising the ceiling needs a per-commit Context-Ceiling: <reason> trailer, checked at pre-push exactly as check:docs-impact scopes its trailerRegrowth is the failure this exists to prevent
X2Every .claude/rules/*.md has paths:; every glob matches ≥ 1 file on disk; body ≤ 60 lines / 4,000 chars; the four blocks present; aggregate ≤ 600 lines; worst-case overlapping load per churned file ≤ 150 linesA rule without paths: is always-loaded; a glob matching nothing is a green no-op (QRS-013)
X3Every path or page cited in a rule, the routing table, or a Read-before block resolves; no portal link to a CLAUDE.md section anchorA dangling mandatory read is worse than none
X4Every domain index has a Read-before-changing block (≤ 25 lines), scanned with the retired-vocabulary list (containers are exempt in check:docs, so not here)The routing targets must be real entry points
X5Churn coverage: files from git log --since=90.days --name-only matched against all globs; uncovered churn is ratcheted downward; top-20 uncovered printed. Plus: every implementation path in design-system/parity-contracts/*.json is matched by screens.mdHand-written globs missed the files that actually change (§16.1)
X6No @import in CLAUDE.md or any rule file, outside code fencesImports load eagerly
X7project-state.md ≤ 250 lines; §1 keeps a ⇢ CONTINUE FROM HERE line; every link in §1 resolvesThe second CLAUDE.md
X8Zero lines in CLAUDE.md or any rule file match the correction pattern (until 20xx-, this file said, as of 20xx-xx, (measured 20xx) — 85 lines match todayThe growth vector itself
X9Nested CLAUDE.md ≤ 120 lines; skill descriptions ≤ 250 chars, ≤ 12 skills; the inventory block ≤ 40 linesCapping the root moves the water elsewhere
X10Every R-nn cited anywhere resolves to architecture/operating-rules.md; every quoted CLAUDE.md heading in tools/, .claude/, .github/ still existsSplit-brain and dangling citations

Mutation-tested in both directions; at least two cases spawn the CLI and assert on stdout (QRS-1155).

6.2 Deterministic controls for the three expensive domains ​

For payments, migrations and release, a wrong edit costs money or production, so the control is not advisory. Owner decision 2026-09-23: block.

  • Edit gated by read — tools/hooks/gate-edit-on-read.mjs, PreToolUse on Edit|Write|MultiEdit: maps the target path to a domain via the rules' globs, parses the session transcript for a prior Read or Bash read of each mandatory page, and refuses the write listing the missing pages — the guard-bash.mjs "instead" pattern (exit 2, text on stderr). Advisory (exit 0 + message) everywhere else, because a blocking hook that fires on legitimate work gets switched off. No hook in this repo reads transcript_path today; it is spiked first.
  • Bash-read router — tools/hooks/on-bash-read.mjs, PostToolUse on Bash: extracts path-like tokens from the command (reusing blankQuoted / blankHeredocs from guard-bash.mjs), matches them against the rules' paths: (parsed from the rule files, one source), and emits the rule body once per rule per session. Built regardless of the sentinel result: this harness and .claude/agents/impl-analyst.md are Bash-driven by design.
  • Prompt router — tools/hooks/on-prompt-route.mjs, UserPromptSubmit: reads context-routing.json, prints a 3–5 line pointer when the prompt matches a task shape's keywords. A pointer, never a page, so a false positive costs ~50 tokens.

6.3 Regrowth guard on CLAUDE.md edits ​

tools/hooks/on-claude-md-edit.mjs, PostToolUse on Edit|Write of CLAUDE.md: diff-aware — if the added text matches the correction pattern or a dated QRS-###, prints the exact destination (dev-tracker/operating-manual-corrections.md or the domain rule) and the headroom left under the ceiling. Advisory; X1 and X8 are the hard stop at commit.

7 · Placement policy — where new knowledge goes ​

Apply the first rule that matches.

  1. A correction to something already written? Replace the wrong sentence in place. Record what it said, when, and the QRS in dev-tracker/operating-manual-corrections.md. Never append the correction beside the original. Classify it live (the fact is restated tenselessly in a rule or page) or historical (log only, with superseded-by: <page#anchor>).
  2. Would Claude violate a rule on an arbitrary task without it? CLAUDE.md, ≤ 3 lines: the rule, the enforcing gate, one link. The paragraph of why goes to architecture/operating-rules.md under the same R-nn.
  3. Applies only when code under one area is touched? .claude/rules/<area>.md (invariant + read list); detail in that area's portal page.
  4. A procedure (steps + commands)? A skill, referencing the guide it executes.
  5. A convention of one app or package? That directory's CLAUDE.md (lazy), or its README.md.
  6. How a gate works, its blind spots, its incident? guides/quality-gates.md (one H2 per script) and the script header. Never CLAUDE.md beyond the script's name.
  7. A fact about the current build state? The generated inventory block if countable; architecture/current-state.md if not.
  8. A decision with options and trade-offs? An ADR (cross-cutting) or a tracker row (local).
  9. A defect, debt or risk? A QRS-### row. CLAUDE.md cites the id, never the story.
  10. What happened during a delivery? delivery-log.md.
  11. Dated, superseded, or about something that no longer exists? The corrections log, a supersession banner, or the documentation/ archive.
  12. A new feature or integration? Domain page(s) + a row in the domain index + a rule glob (or file)
    • config.mjs registration. Nothing in CLAUDE.md unless rule 2 applies.

Tests that stop the policy being gamed: a dated line in CLAUDE.md fails X8; a rule file over 60 lines fails X2; CLAUDE.md over the ceiling fails X1.

8 · Cross-references and authority ​

  • Stable ids over paths. The rules become R-00 (verified, never assumed) … R-07 (proactive). CLAUDE.md renders each in ≤ 8 lines from architecture/operating-rules.md, so the two cannot drift; tool comments cite the id. Renumbering is forbidden; a split rule carries superseded-by:.
  • CLAUDE.md links indexes; indexes link pages. A page rename touches one index.
  • Every moved paragraph keeps its identifiers, asserted per section by the loss check (§10).
  • Authority order, explicit and inverted for pointers: (1) the enforcement point — a migration, release.json, a parity contract, a test; (2) the portal page a rule or the routing table points at; (3) CLAUDE.md for the rule text itself; (4) the state record and memory, as pointers. The portal index.md clause "CLAUDE.md wins" is rewritten to this order.
  • Terminology is anchored once in overview/glossary.md (it already backs check:docs).
  • Subsystem-to-subsystem references live on each domain index's "Related domains" line.

9 · Classification of every current section ​

Verdicts: KEEP (stays, compressed to rule form) · RULE+REF (≤ 8 lines stay; body moves) · MOVE · CONSOLIDATE (merged into an existing page) · LOG (dated narrative → corrections log or tracker) · REMOVE (only where the duplicate is named). Nothing is deleted without a destination.

#Section (lines)~TokensVerdictStays in CLAUDE.mdDestination for the restWhy it is safe
1Generated inventory + header banner (1–39)1.8KKEEP block · LOG bannerThe 19-line generated blockThe two banner paragraphs → corrections log; one line noting the dangling plan file stays until memory/qrsetu-standards-plan.md and PROMOTION_RUNBOOK.md are correctedThe block is regenerated by check:claims; the banner is history
2What QRSETU is (40–70)0.7KKEEP, compressed10 linesThe pillar-retirement narrative → corrections log (already in overview/platform.md)Duplicate confirmed
3Three user categories (71–170)2.3KRULE+REF12 lines: the table + five one-line consequencesarchitecture/user-lifecycle.md (355 lines on this) absorbs the essay; the consumer-scope narrative → consumer/index.md + logThe reasoning lives in user-lifecycle.md and ADR-0020..0025
4Proactive, not reactive (171–240)1.3KRULE+REF6 linesoperating-rules.md § R-07 + overview/product-vision.md gains the five-question gateThe rule is universal; the essay is read when a feature is specified
5First rule: toolchain (241–259)0.4KKEEP, compressed4 linesguides/prerequisites.md (new); the wrangler narrative → logThe page CLAUDE.md already cites gets written
6Second rule: Dev is SSOT (260–303)0.8KRULE+REF8 lines under R-02operating-rules.md § R-02 (the three-times story); ADR-0020's hand-off repointedStory is Tier 4
7Third rule: automate the check (304–410)2.0KRULE+REF8 lines under R-03operating-rules.md § R-03; the nine-gate status table → guides/quality-gates.mdA 🔴/🟡/🟢 status board goes stale; it is a portal artefact
8Fourth rule (411–490)1.4KRULE+REF8 lines under R-04operating-rules.md § R-04; the can/cannot-see table → design-system/screen-conformance.mdThe table documents the gate
9Fifth rule (491–573)1.5KRULE+REF8 lines under R-05 incl. the three-axis table in three linesoperating-rules.md § R-05 + architecture/capability-classification.md gains the forbidsADR-0021 + capability-classification carry the model
10Sixth rule (574–649)1.2KRULE+REF8 lines under R-06, updated: the EF half is measured (check:ef-drift)releases/process.md (checklist in full) + guides/quality-gates.md § check:ef-driftreleases/* is the declared SSOT
11Non-negotiable engineering standards (650–1082)15.9KRULE+REF + MOVE~40 lines: DoD checklist; R-00; ~15 invariants; parity in 3 lines; one line each for disk, observability, docs-impact, README, COMMENT ONParity checklist incl. device layout → guides/parity-verification.md (rewritten, step 7 is stale); disk → guides/windows-build-environment.md; observability → integrations/sentry.md; docs-impact → guides/quality-gates.md; README/COMMENT ON/stance/copy rules → guides/coding-standards.md; dependency triage → guides/dependency-policy.md (new); business-opportunity obligation → strategy/index.mdEvery destination exists except two, and each already covers most of the section
12Commands (1083–2119)36KMOVE + names table25 lines: ~12 daily commands by name; the four-check gate-reading rule; three ports; e2e:quick is the pre-push web gateguides/quality-gates.md (new, one H2 per script — the C3 target); lint-speed and Sonar notes → coding-standards.md; web landmines → apps/web/CLAUDE.md; drivers, ports, stale-bundle proof, Windows traps, git add -A, hook inventory → guides/claude-code-operations.md (new)Each tool's header already holds its own narrative (verified for 14 of them)
13Path aliases & TS config (2120–2143)0.5KMOVE2 lines in the repo mappackages/CLAUDE.md + coding-standards.md § TypeScriptArea convention
14Where the code lives (2146–2173)0.7KKEEPThe 10-line treelegacy/ warning → architecture/tiers.mdMap is Tier 0
15Mobile app structure (2174–2217)1.1KMOVE—apps/mobile/CLAUDE.md + architecture/tiers.mdLoads only when mobile files are read
16Data access rule (2218–2256)1.0KRULE+REF3 linesarchitecture/data-access.md gains the "ungated" warning; .claude/rules/data-seam.mdThe page says everything except the ungated caveat
17Platform model (2257–2306)1.4KRULE+REF4 linesarchitecture/platform-model.md (new; ADR-0020..0025 are Proposed)The compact summary has no home today
18Enterprise tree + Campaigns (2307–2335)0.7KMOVE—architecture/platform-model.mdNeeded only on enterprise/campaign tasks
19Setu Card, Catalog & templates (2336–2456)3.5KRULE+REF + LOG3 linesdesign-system/public-setu-card-spec.md + ADR-0003/0019/0027; the three rounds of "known gaps" corrections → architecture/current-state.md + log; .claude/rules/setu-card.mdThe gaps block is state
20Orders, payments & money path (2457–2527)2.0KRULE+REF4 linespayments/index.md gains the invariants; the two closed blockers → log; .claude/rules/payments.mdThe heading names the portal as SSOT
21Supabase client, real vs stub (2528–2594)1.9KRULE+REF + LOG2 linesarchitecture/data-access.md § wiring; the "wrong four times" narratives → log; EF-deploy notes → supabase/CLAUDE.mdThe count lives in the generated block
22Auth & session (2595–2601)0.2KMOVE—apps/mobile/CLAUDE.mdArea detail
23Edge Functions (2602–2682)2.4KRULE+REF3 linesEDGE_FUNCTION_GUIDELINES.md (corrected: it still names deleted functions) + backend/index.md (new); .claude/rules/edge-functions.mdThe guidelines are the declared standard
24Migrations & DB rules (2683–2689)0.2KRULE+REF3 linesguides/migrations.md + .claude/rules/migrations.mdAlready a guide
25Design System & UI/UX (2690–2739)1.5KRULE+REF4 linesdesign-system/index.md (new) with the two-project table38 pages with no entry point is itself a discoverability defect
26A screen that does not exist yet (2740–2918)5.2KRULE+REF3 linesdesign-system/screen-coverage-mandate.md absorbs the seven-step process and stops pointing back; brand → brand-and-typography.md (font corrected); copy rules → coding-standards.mdThe mandate page cites CLAUDE.md as its source today
27TypeScript (2919–2927)0.2KRULE+REF1 linecoding-standards.md § TypeScriptDuplicate
28Naming + SQL families + table names (2928–2965)1.0KREMOVE, pointer stays2 linesAlready verbatim in guides/coding-standards.mdConfirmed duplicate by heading match
29Domain schemas (2966–3011)1.3KRULE+REF2 linesADR-0031 + .claude/rules/migrations.mdThe ADR is the record
30Feature-scoped naming (3012–3083)2.0KRULE+REF3 linescoding-standards.md § naming gains the rule and the collision tableThe gate exists; the table documents it
31Lint & format (3084–3111)0.8KRULE+REF2 linescoding-standards.md § Lint + guides/quality-gates.mdDuplicate
32Testing model (3112–3156)1.1KMOVE2 linesguides/testing-strategy.md (rewritten: it still describes a Vitest-only model)Already a guide
33Release management (3157–3204)1.0KRULE+REF3 lines inside R-06releases/index.md / hld.md / process.mdDuplicate of the declared SSOT
34Environments, CI/CD (3205–3393)6.7KMOVE + KEEP refs8 lines: the three-project ref table; "run projects list first"; no scheduled workflowsguides/environment-strategy.md, guides/deployment.md, guides/github-actions-usage-forensics.md; workflow list → guides/quality-gates.md § CIEvery workflow's own header is the authority
35Dev portal & tracker discipline (3394–3552)5.5KRULE+REF + LOG6 linesdev-tracker/index.md; the ADR list → architecture/adr/index.md; the OTP diagnosis → integrations/zeptomail.md (already there) + tracker; the log-count saga → logThe ADR index already names 0013/0018
36Frontend platform (3553–3585)0.8KREMOVE— (covered by §2 identity + ADR-0011/0012)architecture/tiers.md + ADR-0011Third statement of the two-stack model in one file
37HLD / LLD (3586–3591)0.1KLOG—A tracker row (unwritten)It records an absence
38Legacy documentation/ archived (3592–3612)0.4KKEEP 1 line1 lineAlready in documentation/README.mdDuplicate
39check:docs freshness (3613–3677)1.4KMOVE—guides/quality-gates.md § check:docs, incl. the mermaid-not-gated noteGate documentation

Totals: ~125 lines of KEEP/RULE text + 19 inventory + ~24 routing + 12 placement + 25 commands + 15 map/refs ≈ 230–280 lines, inside the target.

10 · Migration plan with validation gates — PROPOSED ​

The owner's ten phases, with three changes: a Phase 0 that builds the ratchet and answers the sentinel question first; targets created before content moves; and the loss check run per move.

PhaseWorkExit gate (a command; output pasted into QRS-1288)
0 · Freeze, ratchet, sentinelRecord the baseline (wc -l -c CLAUDE.md, /context). Build check:context with X1 at the current size (ratchet only) + X6, wire pre-commit + CI. Run the sentinel matrix. Spike transcript_path in a hookcheck:context green at baseline; sentinel and spike results recorded here
1 · AuditThis page (done); the overlap map (§17)check:portal-nav, check:docs green
2 · Classify§9 becomes the migration ledger with a status columnNo row without a verdict
3 · DependenciesRetarget C3 (H2 + non-empty "Cannot see" per script) to guides/quality-gates.md; lower the check:docs root baseline in lockstep; extend ROOT_DOCS to .claude/**check:claims + check:docs green with the retarget
4 · Targets firstCreate every destination in §4.2 empty: the eight missing indexes with Read-before blocks, operating-rules.md stubs R-00..R-07, context-routing.json, .claude/rules/*.md with longhand paths: and empty blocks, the four nested CLAUDE.mdX2–X5 green (globs resolve, coverage printed, indexes present)
5 · Move, one section per commitFor each §9 row: verbatim copy to the destination first; then the ≤ 8-line form; dated lines classified live/historical; the X1 ceiling lowered to the new size. Order: Commands → Architecture → standards → rules → the rest. Each of the §17.2 conflicts reconciled against code and named in the commit bodyPer commit: check:context, check:claims, check:docs, check:portal-nav green and the loss check reports zero missing
6 · Loss checkSection-scoped: each old H2 → its destination(s); every QRS-, ADR-, CR-, npm run …, repo path and structured backticked token must appear in that section's destinations ∪ new CLAUDE.md; a known-stale allowlist for the 8 paths that already do not resolveZero missing; intentional drops enumerated with reasons
7 · ReferencesTool and agent comments → R-nn; the EDGE_FUNCTION_GUIDELINES.md path; the portal index.md authority clause; ADR-0020's and the mandate page's hand-offs; the leaked [[app-size…]] link in apps/mobile/README.md; memory files opportunisticallyX3 and X10 green
8 · Discoverability probesFive scripted tasks in a fresh session, one per routing shape (money path, migration, screen, gate change, promotion) plus one negative (a file the globs miss): which rule fired (/context), which index opened, were the Read-before questions answered in the planA six-row pass/gap table on this page; every gap becomes a glob fix or a routing row
9 · Measure/context; wc; the before/after table in §14 re-filledTotal ≤ 35K
10 · FinaliseCeiling at 350 / 35,000; X7 on the state record with its narratives moved and nextAction: frontmatter injected inline; MEMORY.md under 15 KB; delivery-log entry; close QRS-1288All gates green

Effort: Phases 0–4 about a day; Phase 5 two to three days (39 sections, 12 of them one-line moves); Phases 7–10 a day. Every commit is independently revertible.

Where knowledge is most at risk: Phase 5, in the rule-form rewrite. The verbatim copy lands first and is the safety net; the compression is checked against it.

11 · Governance ​

  • Ownership: CLAUDE.md and .claude/rules/ belong to the standards programme; each portal domain is owned by its index page. A rule file's paths: is its ownership boundary.
  • Adding a capability: domain page(s) → Read-before rows in the domain index → a rule glob or file → config.mjs → check:context and check:portal-nav. CLAUDE.md is untouched unless a new universal rule is born, and then it displaces something.
  • Corrections discipline: a wrong sentence is replaced; its history goes to the corrections log. A CLAUDE.md diff that adds a date fails X8.
  • Ceilings, all measured: CLAUDE.md 350 lines / 35,000 chars · rule file 60 lines · nested CLAUDE.md 120 · routing rows 20 · project-state 250 · MEMORY.md < 15 KB (surfaced at every session start, not gateable in CI).
  • Cadence is the gate, not a calendar: re-measure at release gate G3 and whenever check:context prints a ceiling within 10%. This repo's measured completion rate for calendar reviews is ~0 (QRS-180).
  • Skills hold procedures; when a procedure is written twice in prose, it becomes a skill.

12 · Example target CLAUDE.md (~280 lines) ​

markdown
# CLAUDE.md — QRSETU operating constitution
<!-- BEGIN:MEASURED-INVENTORY --> … 19 generated lines … <!-- END:MEASURED-INVENTORY -->

## 0 · How to use this file (6 lines)
Rules here are complete; detail is not. Every rule cites its gate and its page. Before changing a
domain, open its index (§8). Authority: enforcement point > cited page > this file > state record > memory.

## 1 · What QRSETU is (12 lines)
## 2 · Three user categories (12 lines) — the table + five one-line consequences
## 3 · The constitution (≈70 lines) — <!-- BEGIN:RULES --> R-00 … R-07, ≤8 lines each, GENERATED <!-- END:RULES -->
## 4 · Definition of Done (14 lines, checklist)
## 5 · Hard invariants (≈22 one-liners, each with gate + id)
## 6 · Repository map (16 lines)
## 7 · Commands you run daily (22 lines) — names only + the pre-commit / pre-push / CI split + three ports
## 8 · Where to read next — <!-- BEGIN:ROUTING --> task shape → mandatory entry, GENERATED <!-- END:ROUTING -->
## 9 · Environments (10 lines) — the three-project ref table; "run projects list first"; no scheduled workflows
## 10 · Where new knowledge goes (12 lines) — the placement policy
## 11 · After a context reset (4 lines)

13 · Example index and rule file ​

Domain index (payments/index.md, top of page):

markdown
::: danger Read before changing
1. This page · 2. marketplace-payments.md · 3. data-model.md · 4. reconciliation-and-finance.md · 5. ADR-0002
Invariants: `.claude/rules/payments.md` (auto-loads when you touch the money path — if it has not, open it).
Gates: `npm run test:db` · `check:sql` · `check:rpc` · `payments-watchdog.yml` (manual dispatch).
Your plan must answer: which correlation key(s) does the change touch? which
`payment_counts_as_collected()` call sites? does anything cross the disclosure boundary (`provider_fee_*`)?
Related domains: orders, Setu Card ordering.
:::

Rule file (.claude/rules/payments.md):

markdown
---
paths:
  - "supabase/functions/manage-order/**"
  - "supabase/functions/place-public-order/**"
  - "supabase/functions/razorpay-webhook/**"
  - "supabase/functions/reconcile-payments/**"
  - "packages/data/src/orders/**"
  - "packages/data/src/payments/**"
  - "packages/data/src/publicOrder/**"
  - "packages/domain/src/payments/**"
---
# Payments — invariants that load when you touch the money path
## Invariants
- Never test `status = 'captured'`; use `payment_counts_as_collected()` (QRS-706).
- Status advances monotonically by rank; `failed` sits below `captured` on purpose.
- Never hardcode or depend on a Razorpay fee; `provider_fee_*` are recorded, read by nothing.
- The merchant never sees `provider_fee_minor` / `provider_fee_tax_minor`; a test pins it.
- Webhook correlation order: payment_id → link_id → order_id → order_id-as-UUID. A 5xx is inert; dead events need REPLAY.
- The reconciler is DETECT-ONLY. `NULL` and `0` reach different code in `complete_order_with_settlement`.
- Deterministic refusals never raise class-40/08 SQLSTATEs (QRS-1276/1277).
## Read before changing
1. payments/index.md  2. marketplace-payments.md  3. data-model.md  4. ADR-0002
## Gates to run
`npm run test:db` · `npm run check:sql` · `npm run check:rpc` · after deploy: `check:db-health`, `check:ef-drift`
## Do not
- Retry a dead-lettered webhook; replay it. · Normalise NULL into 0. · Conflate the subscription model with Razorpay Route.

Gate catalogue entry (guides/quality-gates.md), fixed sub-headings so C3 and a human find the same thing:

markdown
## `check:state`
**Asks:** is the project-state record within 10 commits of HEAD (and under its size ceiling)?
**Runs:** pre-push; the SessionStart/PreCompact hooks share its library.
**Cannot see:** whether a sentence in the record is TRUE.
**Incident:** QRS-1155 — exited 0 with zero output for its whole life on Windows.
**Tests:** 11 mutation cases; two spawn the CLI and assert stdout.

14 · Before and after ​

PayloadBeforeAfter (proposed)Mechanism
CLAUDE.md121.3K tokens, always~10–12K, alwaysConstitution + routing; X1
Unconditional .claude/rules00X2 requires paths:
project-state.md via hook~0.75K delivered (a 2 KB preview of 135 KB)≤ 3.5K, all of it deliveredthe extract (QRS-1289); X7
MEMORY.md~8K, at 90% of its hard cap≤ 5KTrimmed
Skill descriptions~0.3K~0.5KNames + descriptions only
Fixed per session~130K~25–30K−77% to −81%
Per domain task, on touch0 (all pre-loaded)1–3 rule files (≤ 2K) + 1 index (≤ 2K) + 2–4 pages (5–15K)Path rules → index → pages
Worst realistic task (payments migration touching EF + data + SQL)130K fixed~30K fixed + ~20K targetedStill 2.6× lighter than the old floor

15 · Risks and mitigations ​

#Failure modeLikelihoodMitigationCost
1No rule fires — planning, a grep, a design pull; no globbed file is readHighRouting table by task shape; the prompt router; Read-before questions whose answers must appear in the plan; Phase 8 probes~24 lines of CLAUDE.md
2Bash reads do not trigger path rulesUnknown (undocumented)Phase 0 sentinel matrix; the Bash-read router hook is built regardless1 hour + ~120-line hook
3Read but skimmedMediumRead-before questions; for the three expensive domains, the edit is refused until the pages were openedIndex authoring; one hook
4Split-brain between the short rule and the full pageMediumThe short form is generated from the page under R-nn; X10~100 lines in the gate
5Knowledge lost in compressionMedium–High in Phase 5Verbatim copy first; section-scoped identifier check per commit; live/historical classification of every dated lineDiscipline + one script
6Regrowth by another route — longer lines, rule bloat, a rule losing paths:, imports, the routing table growingHigh over monthsX1 counts chars and max line; X2, X6, X8, X9; rows 1:1 with rules; the diff-aware edit hookcheck:context
7Gates break on the move (C3, check:docs stale allowance)Certain if unhandledPhase 3Half a day
8A second knowledge base nobody gatesMediumKnowledge in the portal only; ROOT_DOCS extended to .claude/**One line
9Stale routing targetsMediumX3 on every commit; CLAUDE.md links indexes, not leavesIncluded
10Nested CLAUDE.md become mini-manualsMediumX9Included
11The state record is not delivered (a 2 KB preview, QRS-1289)RealisedThe SessionStart extract within the hook budget; Phase B still shortens the recordDone 2026-09-24
12MEMORY.md silently truncatesHigh within weeksTrim now to < 15 KB30 minutes
13Owner instructions lose their voiceLowThe verbatim quotes move to operating-rules.md; the one-line quote stays in the short formNone
14A future session re-inlines detail because a pointer looked thinMediumThe ## 0 block says detail is elsewhere by design; X1 fails the commitNone
15Globs miss a new code areaMediumX5 prints the uncovered set every runIncluded
16Subagents or post-compaction sessions do not load rulesUnknownIn the sentinel matrix; session-state.mjs re-prints the rules for files touched this session on compact~50 lines if transcript_path is available

16 · Hardening from the adversarial review ​

An independent review attacked the first draft for context-skipping and regrowth. Each measured finding was re-verified and folded into §§5–6, 10 and 15 above; this is the record of what changed.

16.1 Measured defects in the first draft ​

FindingMeasurementAmendment
The proposed screens glob apps/*/src/tiers/**/screens/** is dead on the web app0 of 59 .tsx under apps/web/src/tiers (web uses features/ and app/routes/)Globs derived from the parity contracts' implementation paths; a zero-match glob fails X2
The highest-churn files fall under no proposed rule90-day commits: package.json 51 · ci.yml 33 · supabase/config.toml 20 · apps/mobile/app.json 20load-bearing-files.md; data-seam.md widened to all of packages/*/src; X5 becomes churn coverage
X4 is vacuous today8 of 15 portal sections have no index.mdPhase 4 creates all eight first
C3 is a substring testprose.includes(scriptName)H2 per script + non-empty "Cannot see"
A ceiling raise "justified in the commit message" cannot run at pre-commitThe message does not exist yetFail-on-growth at pre-commit; the raise trailer at pre-push (QRS-570 scoping)
Brace globs and Windows separators are unverifiedHooks here already defend with [\\/]Longhand globs; X2 expands each against the tree
CLAUDE.md cites repo paths that no longer exist8 of 108 backticked repo paths do not resolveknown-stale allowlist in the loss check; each becomes a correction
A dated correction can be a live facte.g. "the card is NOT at /:slug"Live/historical classification (§7 rule 1)
::: container text is exempt from check:docsBy designX4 scans Read-before blocks with the retired list

16.2 Mechanisms adopted ​

Generated blocks for anything that can drift (rules, routing) · the Bash-read router · edit gated by read for payments, migrations and release · the prompt router · the sentinel matrix · shape ratchets (chars, max line, the correction regex, overlapping load) · rows 1:1 with rules · nextAction: frontmatter on thread pages · the section-scoped loss check · citations by id · skills never as a mandatory-read path.

16.3 The control hierarchy this leaves ​

Deterministic (a script refuses or fails): X1–X10, churn coverage, edit-gated-by-read for the three expensive domains, C3 upgraded, generated blocks. Mechanism-triggered (fires without recall): path rules, the Bash-read hook, the prompt router, SessionStart. Discipline (human-checked): Read-before answers in the plan, the placement policy at review. No single layer carries the design, and the two that matter for money and production are deterministic.

17 · The overlap sweep ​

An independent sweep grepped two or three distinctive phrases from every CLAUDE.md topic into every candidate home (portal, ADRs, tool headers, READMEs), excluding the log-type files that mention everything (tracker, delivery log, state record, change log).

17.1 Sixteen topics whose only home is CLAUDE.md — and their destinations ​

#TopicDestination
1First rule: verify and bootstrap the toolchain (no prerequisites.md exists)guides/prerequisites.md (new) + R-01
2Second rule's four BANNED items and three legitimate uses (ADR-0020 hands off to CLAUDE.md)operating-rules.md § R-02; ADR-0020 repointed
3Third rule: nine-gate table, "must not be claimed to cover", sequencingoperating-rules.md § R-03 + guides/quality-gates.md
4Fourth rule as a rule (owner quote, forbids/requires); only the mechanism is in check-design-parity.jsoperating-rules.md § R-04; design-system/index.md states it
5Fifth rule's forbids list; the rule otherwise lives only in apps/mobile/src/features/README.mdoperating-rules.md § R-05; capability-classification.md gains the forbids
6Sixth rule's seven-line pre-promotion checklist and forbidsreleases/process.md + R-06
7The six proactive principles and the five-question gateoverview/product-vision.md + R-07
8"Name which user categories a feature serves" + the R1 consumer-scope decisionarchitecture/user-lifecycle.md + consumer/index.md
9Verified never assumed · surface the business opportunity · the Principal-Architect obligationsoperating-rules.md § R-00 · strategy/index.md · coding-standards.md
10The device-layout parity step as prose (exists only as check:parity R11–R14 and QA cases); the current no-exception checklist (parity-verification.md step 7 is stale)guides/parity-verification.md rewritten
11COMMENT ON and check:docs-impact as standards (mechanisms only in tool headers)coding-standards.md · guides/quality-gates.md
12Reading a gate's output (five variants); why there is no local Sonarguides/quality-gates.md (the four-check rule stays in CLAUDE.md)
13The seven-step "screen that does not exist" process (the mandate page points back at CLAUDE.md)design-system/screen-coverage-mandate.md absorbs it
14Feature-scoped naming as prosecoding-standards.md § naming + collision table
15Copy rules: no em dash (QRS-231), no volunteered privacy reassurance (QRS-549)coding-standards.md § copy + brand-and-typography.md
16TS config and path aliases (allowImportingTsExtensions has zero hits elsewhere)packages/CLAUDE.md + coding-standards.md

17.2 Nine facts that conflict between CLAUDE.md and its destination — reconciled against code, not against either document ​

FactCLAUDE.md saysDestination saysReconcile by
Entry-route keyaccount_type decides resolveEntryRouteoverview/user-ecosystem.md: renamed primary_contextentryRoute.ts and handle_new_user
Workspace tree depth cap6ADR-0023 line 70: 4Migration 20260808100500_v2_workspace_path_trigger.sql says 1–6; ADR-0023 gets a banner
deploy:manual authorisationRefuses after 2026-08-31apps/web/scripts/deploy-manual.mjs: re-authorised 2026-09-20, EXPIRES 30 Sep 2026The script wins
"Nothing measures what is DEPLOYED"Still assertedcheck:ef-drift exists, documented 900 lines later in the same fileR-06 updated: the EF half is measured, eight classes are not
Parity exceptionsRetired 2026-08-02guides/parity-verification.md step 7: "exceptions with prior approval"CLAUDE.md wins; page rewritten
UI fontBaloo 2 (fonts.ui)brand-and-typography.md line 28: Plus Jakarta Sanspackages/tokens wins (Plus Jakarta is webFonts.ui)
Test runnersThree (jest-expo · vitest · node --test)guides/testing-strategy.md: a Vitest-only modelpackage.json wins; page rewritten
Card URL/:slug/setu-cardADR-0011 line 169 still shows the pre-August /b/ formapps/web/src/app/routes.ts wins; ADR gets a banner
EF reference examplemanage-item / manage-reminderEDGE_FUNCTION_GUIDELINES.md names an archived function eight times and lists a deleted one twiceRepo wins; guidelines corrected in the same move
Consumer dashboard designExists (a pull, not a request)screen-coverage-mandate.md 18–27: "no design"Design registry wins; page corrected

The identifier loss check proves nothing was dropped; it cannot prove the surviving sentence is true. Phase 5 therefore names, per move, the file each conflict was reconciled against.

17.3 Other sweep findings ​

  • tools/README.md lists 9 of 28 check-* scripts → guides/quality-gates.md is generated from package.json so the denominator cannot drift.
  • apps/mobile/README.md § app size points back at CLAUDE.md and carries a memory-style [[app-size-and-versioning-policy]] link that resolves nowhere in the repo.
  • Every portal sentence that says "see CLAUDE.md for X" is repointed to the page that now owns X; X3 fails on a portal link to a CLAUDE.md anchor.
  • .claude/rules/ does not exist yet; the hook stack does, so the three new hooks slot into existing matchers plus one new UserPromptSubmit.

18 · Decisions taken with the owner (2026-09-23) ​

  • Edit gate: BLOCK on payments, migrations and release until the transcript shows the mandatory pages were opened; advisory elsewhere. Spike transcript_path first.
  • Rule text: generated ≤ 8-line form in CLAUDE.md under R-00…R-07, rendered from architecture/operating-rules.md; the owner's one-line quote stays in the short form.
  • Scope and go: the owner asked whether a strict cap risks context loss (§19) and then chose do not proceed yet. The staged option in §19 is the recommended scope when the go comes.

19 · Does a strict cap risk context loss, inconsistency or hallucination? ​

A cap applied by deletion would. This cap is applied by relocation plus deterministic routing, nothing is deleted, and the current state already produces the failure the question describes.

  1. Where loss can actually happen, and what stops it. Compression (80 lines → 8) can drop a "forbids" bullet: the verbatim copy lands first, the short form second, the section-scoped identifier check runs per commit, and placement rule 1 forbids deleting a fact. Routing that does not fire loses a moved page in practice: three independent triggers plus the always-loaded table, and for money, migrations and release a hook that refuses the edit. A true sentence landing in a stale page: §17.2 found nine; each is reconciled against code.
  2. The opposite risk is already realised, measurably. Adherence degrades with context length (the official guidance is under 200 lines for this reason). This file's history is the evidence: 66 dated corrections were appended for rules that were loaded the whole time — the em-dash rule sat in four loaded pages and reached no implementation until it was gated (QRS-231); the device-layout defects (QRS-1152/1153) shipped past a fully loaded parity section; the inventory block exists because prose counts were wrong four times while in context. A rule competing with 3,676 other lines is not "safe because loaded". And the ~130K fixed load brings compaction forward — the largest context-loss event there is: the summary keeps what and loses why (the state record's own warning), and the record meant to restore the why arrived as a 2 KB preview (QRS-1289).
  3. What the cap does not claim, and the safety valve. No mechanism guarantees a page is understood; what changes is that a skipped read becomes observable, and on the three expensive domains impossible to act on. The ceiling is a ratchet with a documented escape: one commit trailer raises it, and the gate prints the reason.

Recommendation: stage it. Cap CLAUDE.md first (Phases 0–9), run the five probe tasks plus one week of ordinary work, record any regression in the tracker, then cap the state record. The memory index trim is independent and urgent regardless: it is 2.5 KB from silent truncation.

20 · Would a code graph help? ​

What it is. A machine-built graph of the codebase: nodes are files, modules, symbols, tables and SQL functions; edges are imports, calls, definitions and references — and in this repo the cross-boundary edges that matter most (a client *_RPC constant → a SQL function; an EDGE_FN key → a function folder; a portal page → the code it documents). The agent queries it ("who imports X", "what breaks if Z changes") instead of grepping.

Measured state. Claude Code has no built-in code graph, index or LSP; the documented route is the code-intelligence plugins (for example the TypeScript language-server plugin from the official marketplace) for definitions and references. This repo has zero graph tooling: no dependency-cruiser, madge or tree-sitter in any package.json or the lockfile, no .mcp.json, no TypeScript project references. Four existing gates are hand-rolled edge checks over an implicit graph, each parsing the tree its own way: check:rpc, check:parity R9, check:naming, check:docs-impact.

HelpsHowStrengthens
Removes the structural 10–15% of CLAUDE.mdRepo map, route lists, wired services are derivable; the official guidance excludes "file-by-file descriptions" for this reason§9 rows 13–15, 21
Makes routing derived, not hand-writtenHand-written globs missed the files that change; an import graph derives a file's domain and its blast radius (editing a payments domain module → the webhook function imports it → payments.md loads)§16.1 churn coverage; the router and the edit gate
Turns "impact analysis is part of the change" into a commandCLAUDE.md demands it; nothing produces itR-00, the Definition of Done
One parsed graph for the four edge-check gatesInstead of four parsersguides/quality-gates.md

Where it does not help. About 85% of CLAUDE.md is knowledge no graph contains: owner rules, the three-category threat model, gate blind spots, "never test captured directly", "Digious will renegotiate the fee". The knowledge architecture in §§3–8 is needed regardless. And a graph does not lower the fixed load by itself: a repo map injected at session start raises it; it pays only as query-on-demand.

Risks in this repo's terms. Another index that goes stale, and a stale index gives confident wrong answers — this repo's most repeated defect. The TypeScript plugin covers the TypeScript half only; the cross-boundary edges (PostgREST binds by NAME — QRS-640) are exactly what a language server cannot see and what the hand-rolled tools already cover.

Recommendation. Not a replacement for §§1–19; an optional Phase 11 — "derive, don't hand-write", after the migration has landed and been measured: install the official TypeScript code-intelligence plugin; generate the import graph in CI with dependency-cruiser (a dev-dependency, zero runtime weight, subject to the dependency-policy callout) and commit its JSON; use it to validate and propose rule globs, to compute blast radius for the edit gate, and to generate the structural pages so they cannot drift. Leave the cross-boundary edges with the existing gates.

21 · How should .claude/rules/ be structured as the platform grows? (owner question, 2026-09-23) ​

The first sixteen rule files were written flat: payments.md, setu-card.md, consumer-biodata.md, chat.md, media.md, release.md beside edge-functions.md, migrations.md, data-seam.md, ui-systemic.md, screens.md, i18n.md, merchant-app.md, web-app.md, tools-and-gates.md, load-bearing-files.md. The owner asked whether one flat folder of feature-specific files is the right long-term shape for a platform expected to reach hundreds of features. Measured before answering:

FactMeasurement
Files · size16 rules, 31–60 lines / 1.9–3.4K chars each (cap 60 / 4,000)
Two axes in one namespace10 layer rules (bounded by the architecture: EFs, migrations, data seam, systemic UI, screens, i18n, the two apps, tooling, load-bearing config) and 6 domain rules (unbounded: payments, Setu Card, consumer/biodata, chat, media, release)
Per-touch load, files changed in 90 days1,882 source files covered: 962 load one rule, 818 load two, 102 load three; heaviest is 78 lines of rule body (card-editor screens: merchant-app + screens + setu-card)
Duplicationevery domain rule restates invariants that its portal home page also carries (e.g. payments/index.md now holds the same list) — two hand-written copies of one contract
Loading semanticsClaude Code loads a rule by its paths: glob when a matching file is read; the directory layout does not change what loads. Subdirectories are discovered.

21.1 Diagnosis ​

The flat folder is not wrong today; it is wrong at N. Layer rules are bounded (an architecture has ~10 layers); domain rules grow with the product (every bounded context, integration and vertical wants one), so the folder becomes domain-dominated, a multi-domain file (a consumer chat screen) stacks three or four rules, and every domain contract exists twice — once in the rule, once in the portal page — with nothing but discipline keeping them equal. That is the split-brain this programme was written to remove.

21.2 Four approaches ​

A · Flat, hand-written (today).

.claude/rules/ payments.md · setu-card.md · chat.md · … · edge-functions.md · migrations.md · …

Pros: simplest; works; nothing to build. Cons: two axes indistinguishable at a glance; duplicates the portal's invariants by hand; ownership invisible; the only size control is the per-file cap. Scales to ~20–25 files; beyond that a human cannot scan it and the overlap ceiling starts failing with no structural remedy.

B · Hierarchical, hand-written.

.claude/rules/
  layers/     edge-functions.md · migrations.md · data-seam.md · ui-systemic.md · screens.md · i18n.md · merchant-app.md · web-app.md · tools-and-gates.md · load-bearing-files.md
  domains/    payments.md · setu-card.md · consumer-biodata.md · chat.md · release.md
  integrations/  media-r2.md · whatsapp.md · zeptomail.md
  README.md

Pros: the axes are visible; a folder is an ownership boundary; still one mechanism. Cons: identical loading, identical duplication, identical drift — a tidier A. Worth doing only as the output layout of C.

C · Manifest-driven, generated rules (recommended). The source of truth for a domain's context contract is one manifest, colocated with the domain's portal home page as frontmatter; layer contracts (bounded, standards-owned) live in one JSON. npm run check:context -- --write GENERATES the rule files, the CLAUDE.md routing block and the cross-references; check mode fails on drift.

documentation/portal/payments/index.md          ← frontmatter `context:` = the payments manifest (paths · reads · gate ·
                                                   invariants ≤10 · gates · doNot · shape · keywords · related)
documentation/portal/consumer/index.md          ← consumer-biodata manifest
documentation/portal/communications/index.md    ← chat manifest
documentation/portal/design-system/public-setu-card-spec.md ← setu-card manifest
documentation/portal/integrations/cloudflare.md ← media manifest
documentation/portal/releases/index.md          ← release manifest
.claude/context-map.json                        ← the 10 layer contracts + task shapes with no domain (auth, reset, …)
.claude/rules/                                  ← GENERATED, committed, never hand-edited (header says so)
  layers/*.md   domains/*.md   README.md

Pros: DRY — an invariant is written once, next to the docs that explain it, and the rule is a projection; ownership is the page owner; size is bounded by schema (≤ 10 invariants · ≤ 5 reads · ≤ 6 gates · ≤ 5 do-nots ⇒ ≤ ~40 lines), so a manifest that outgrows the cap is told to move detail into the page rather than the rule; the routing table, the hooks and the Read-before blocks all derive from the same data, so they cannot disagree; adding a feature is one frontmatter block; a hundred domains are a hundred small generated files nobody edits (the qa:workbook precedent — SSOT in tools/qa/cases-*.mjs, the workbook generated and stale-checked). Cons: a generator to build (~200 lines in check-context-map.mjs) and one more generated-artifact convention to learn; frontmatter becomes an authoring surface. Per-touch load is still the sum of matching rules — bounded by schema to ~3 × 40 = 120 lines, and the X2 overlap ceiling still governs.

D · Nested CLAUDE.md per code directory as the rule layer.

supabase/functions/razorpay-webhook/CLAUDE.md · packages/data/src/payments/CLAUDE.md · …

Pros: native lazy loading, no globs, ownership = directory. Cons: a cross-cutting domain (payments spans EFs, data, domain, migrations, tests, a workflow) needs the same text in five directories or @imports — which load eagerly and defeat the purpose; Bash reads do not trigger it; presence sprawl in code directories. Kept only for the four area manuals (apps/mobile, apps/web, supabase, packages), which is what it is good at.

E · Router-only (no rules; hooks inject pointers from JSON). Minimal files, but keyword matching is probabilistic and a pointer is not an invariant. Kept as Layer B (§5.2), never as the primary.

21.3 Recommendation: C, restructured now, with B as its output layout ​

  • Now, not later. Nothing in .claude/rules/ is committed; the hooks' rule loader needs only a recursive walk; the gate is still being built, so a generator is a spec change rather than a rewrite. Every week of accumulation makes the conversion cost more.
  • Two axes, two sources. Layers → .claude/context-map.json (owned by the standards programme; grows with the architecture, not the product). Domains → frontmatter on the domain's home page (owned by whoever owns the domain; grows with the product). The generated rules/layers/ and rules/domains/ folders make the axes visible.
  • Size by schema, not by discipline. The manifest schema caps counts; check:context X2 fails a manifest over cap and a generated file that does not match its manifest; the overlap ceiling stays.
  • Discovery is one graph. File touch → glob → generated rule (Read tool) or on-bash-read (Bash); prompt → on-prompt-route from the same shapes; planning → CLAUDE.md's generated routing table; the domain page's Read-before block lists the same reads. Four surfaces, one source.
  • Dependencies are data. A manifest declares related: [chat, orders]; the generated rule prints "Related domains" and the page's Related line is checked against it (X3). A relationship nobody wrote down cannot exist.
  • Every domain has exactly one home page. If a domain has none, creating it is part of adding the domain — the placement policy already demands the page; the manifest is one more frontmatter block on it.

21.4 What changes in this migration ​

The sixteen files are the seed: their paths/reads/gate and top invariants move into six frontmatter manifests and one JSON; check:context --write regenerates them under rules/layers/ and rules/domains/; context-routing.json folds into context-map.json; the hooks' loader walks subdirectories. Rule content is unchanged; only its source of truth moves.

22 · Execution record — Phase A (started 2026-09-23 on the owner's "go ahead, staged") ​

Every line below is something a command produced or a file that exists; the final measurements are in "Re-measured" at the end of this page.

22.1 Phase 0 — the sentinel test and the hook facts ​

QuestionAnswerEvidence
Does the Read tool load a path-scoped rule?YesSENTINEL-READ appeared after a Read of the matching file
Does a brace glob work?YesSENTINEL-BRACE appeared through {brace-a,brace-b}.txt
Is a rule created mid-session picked up?Yesall three rules were created in the running session
Does a Windows absolute path match a POSIX glob?Yesthe Read used D:\…\read-target.txt
Does a Bash cat load the rule?NoSENTINEL-BASH never appeared — on-bash-read.mjs exists because of this trial
Do hooks reload mid-session?Yesthe four new hooks fired in the same session they were wired ([context-rule …], [context-route …] lines)
Is transcript_path on every hook payload?Yes (documented)hooks reference; the transcript is JSONL with tool_use blocks, written with lag
Untestedsubagent context · after /compactleft for the probe week (Phase 8)

Repeatable procedure: tools/context-sentinel/README.md.

22.2 What landed ​

  • Manifests, not hand-written rules (§21, owner decision). Six domain manifests as context: frontmatter on their home pages (payments/index.md, consumer/index.md, communications/index.md, design-system/public-setu-card-spec.md, integrations/cloudflare.md, releases/index.md); ten layer manifests plus the ordered routes in .claude/context-map.json. check:context -- --write generates .claude/rules/layers/*.md and domains/*.md (16 files, 732 lines in total, ≈ 46 each); the hand-written seed files were deleted once their generated counterparts existed.
  • Four hooks (on-bash-read, gate-edit-on-read, on-prompt-route, on-claude-md-edit) + a shared parser library, 44 tests (105 in test:hooks), wired in .claude/settings.json; block on payments · migrations · release.
  • The gate check:context (X1–X10 + the generator), wired at pre-push (it takes ~2.4 s, over the sub-second pre-commit budget) and in ci.yml's workspace job; C3 of check:claims retargeted to guides/quality-gates.md (H2 + Cannot see per script); check:docs now scans .claude/rules/**, .claude/skills/**/SKILL.md and the nested CLAUDE.md files.
  • Destinations created before content moved: architecture/operating-rules.md (R-00…R-07 + P-01, short form + verbatim full text), architecture/platform-model.md, guides/quality-gates.md (58 script sections, each with a Cannot-see line), guides/claude-code-operations.md, guides/engineering-standards.md, guides/prerequisites.md, guides/dependency-policy.md, dev-tracker/operating-manual-corrections.md (the 81 dated corrections as a table), eight section indexes with Read-before-changing blocks, four nested CLAUDE.md area manuals, four procedure skills.
  • Every one of the 39 sections moved verbatim under a provenance banner (20 appends into existing pages, the rest into new pages); the nine cross-document conflicts in §17.2 are reconciled in the destination text.
  • Repointed: the portal's authority clause, ADR-0020's hand-off (as an appended pointer — ADRs are logs), the mandate page, the mobile README; nine portal pages received vocabulary allowances with reasons because the moved corrections legitimately name what they retired.
  • Found and fixed on the way: the portal build (docs:build, which CI's docs job runs) was already failing on a bare <slug> in releases/26.0.1/00-scope.md, untouched by this work.

22.3 Gates at the end of Phase A (outputs pasted, 2026-09-23) ​

✓ check:context — CLAUDE.md 264/264 lines · 28,675/28,675 chars · corrections 0/0 · manifests 10 layers + 6 domains · rules 16 (130 uncovered churn of 1,684) · X1–X10 green
✅ check:docs — 257 portal page(s) + 28 root/Claude-read doc(s) scanned, 13 retired term group(s), 157 baselined mention(s), 0 new.
✓ CLAUDE.md's measured inventory matches the repo, and every gate has its entry in documentation/portal/guides/quality-gates.md   (check:claims)
check-portal-nav passed — 222 page(s) reachable, 246 nav link(s) resolve.
loss check — identifiers checked: 1468 · missing: 0 · pre-existing stale paths allow-listed: 8
test:hooks — # tests 105 / # pass 105 / # fail 0    (44 new)
check-context-map.test.mjs — 41 pass · check-doc-claims.test.mjs — 19 pass · check-docs-vocabulary.test.mjs — 21 pass

The loss check is section-scoped (§16.2 item 9): every QRS-, ADR-, CR-, npm run …, repo path and structured backticked token in each of the 39 old sections was found in that section's destination page(s) or the new CLAUDE.md; the 8 allow-listed paths did not resolve before the migration either.

22.4 What Phase B still owes (after the probe week) ​

Cap project-state.md (X7 ceiling is recorded at its current 1,375 lines; the narratives move to portal pages and the thread pages gain nextAction: frontmatter that session-state.mjs prints inline) · the five routing probes in a fresh session plus the two untested sentinel cases (subagent, post-compaction) · re-measure with /context.

22.5 Phase 8 — discoverability probes (2026-09-23) ​

Run in subagents, which start with a fresh context, plus live edit attempts against a throwaway gate: block rule (deleted afterwards). Raw results: tools/context-sentinel/README.md.

ProbeResultAction
Read of a matching file loads its rule — main session and subagentpass, 5 of 5; negative control (.gitignore) loads nothing—
Bash read loads its rule (on-bash-read)pass, but delivered as "hook blocking error" (exit 2)switched to documented additionalContext JSON on exit 0; measured live, now "hook additional context"
Same rule sent twice (Read tool, then Bash)gap, ~2 KB duplicated per ruleBash hook skips a rule the transcript shows the Read tool already loaded
Edit gate blocks an unread editpass live: Edit blocked, allowed after cat—
Edit gate sees Bash writesgap: sed -i, redirects, heredocs bypassed it, and the harness prefers Bash for editswrittenCommandPaths; gate wired on PreToolUse Bash; sed -i blocked live
An ls/wc of a page counted as reading itgapreadCommandPaths: only printing verbs count; wc -l then Edit stays blocked, live
Edit gate inside a subagentdefect: blocked forever — the subagent payload carries the PARENT's transcript_path (captured)ownTranscriptPath → <session>/subagents/agent-<agent_id>.jsonl; live: blocked, then allowed after the read
Bash-rule memory inside a subagentdefect: keyed by session_id, which a subagent shares, so a subagent never received a rule its parent already hadmemory keyed by session and agent
Prompt router on harness textgap: a turn whose user text was "please continue" routed to three domains from a subagent report's wordingstripHarnessBlocks removes system reminders, task notifications, agent messages; pasted content kept
Nested apps/*/CLAUDE.md created mid-sessionnot loaded, main session and subagenteach manual added to its layer rule's reads; recheck in a fresh session
New CLAUDE.md visible to subagentssubagents inherit the parent's session-start snapshot (the old file)expected; only a fresh session shows the new file
After /compactmade compaction-aware before the test: reads count only after the transcript's last compact_boundary (plus the messages it kept), and the PreCompact hook clears the Bash-rule memory so rules are sent again; router keyword trigger narrowed after it misrouted a question about compactingowner runs /compact; then check that CLAUDE.md is the new file, a payments Read re-injects its rule, and the edit gate asks for the pages again

Also changed: CLAUDE.md's routing table now prints only the entry page per row (the rule carries the full list), which cut it from 28,675 to 27,210 characters. Tests: test:hooks 119/119 (was 105), check-context-map.test.mjs 41/41.

22.6 After /compact (2026-09-24, measured) ​

The owner ran /compact once the hooks were compaction-aware. Transcript: one compact_boundary (manual, 888,913 → 38,661 tokens, 5 messages preserved).

ProbeResultAction
The new CLAUDE.md is the one loadedpass — the 264-line constitution, not the old file—
A Read of a payments file re-injects its rulespass — _shared/commission.ts loaded domains/payments + layers/edge-functions—
A Bash touch re-sends a rule sent before compactionpass — layers/tools-and-gates arrived again, so PreCompact had cleared the memory—
The edit gate forgets pre-compaction readspass — a Write under packages/data/src/payments/ was BLOCKED listing all four pagesthe allowed-after-read half is proven by a spawned test, not live
The state record is re-injected⚠ defect — a 2 KB preview of 130.9 KB, ending one line before the first thread; the same at session start; the record has been over the threshold since its first commit (13 KB)QRS-1289: the hook prints an extract (7,504 chars)
Hook output size limitmeasured — 12.8 KB of rules from on-bash-read (five areas in one command) arrived as a 2 KB preview holding one rule; ~6 KB arrived whole. Not in the hooks reference; the reported threshold is 10,000 charactershook-output-budget.mjs: every hook budgets to 9,000; rules that do not fit become pointers and are not marked sent
CLAUDE.md cross-referencesgap — five stale lines found reading it back (§0 cited §10 for §11; context-routing.json ×2; "nested CLAUDE.md load when you work there", measured false mid-session; §11.3 described hand-written rules)replaced in place, logged in the corrections log

Corrected on this page in the same pass: the "~49K tokens injected every session" and the ~178K fixed load built on it were inferred from the file's size, never measured in context; what arrived was a 2 KB preview, so the real fixed load was ~130K. Tests: test:hooks 132/132 (was 123), with both fixes mutation-tested (reverted: 4 fail).

Re-measured ​

PayloadTokensDateMethod
CLAUDE.md (before)121,3002026-09-23IDE context counter
CLAUDE.md (after Phase A)≈ 10,5002026-09-2328,675 chars ÷ 2.72 — owner to confirm with the IDE counter in a fresh session
CLAUDE.md (after Phase 8)≈ 10,0002026-09-2327,210 chars ÷ 2.72 (routing table shows the entry page only)
project-state.md (hook), as delivered≈ 7502026-09-24the transcript's hook_success entries: "Output too large (130KB) … Preview (first 2KB)" at session start and after /compact
project-state.md (hook), the extract≈ 2,7602026-09-247,504 chars ÷ 2.72, all of it delivered (QRS-1289)
CLAUDE.md (after the post-compaction fixes)≈ 10,0002026-09-2427,208 chars ÷ 2.72
MEMORY.md~8,100 → see below2026-09-23IDE shows 8.2K before the trim

Add a row whenever any of the three is re-measured; the gate proposed in §6.1 would print all three.