Claude Code project instructions: current guidance, applied to im-web-prototypes

Draft

Status: research from 2026-10-09, not applied yet. Kept here so it isn't lost; it may become its own doc-only feature and also be applied to im-web. The route shape it recommends (_docs/, a reserved app folder) is in place, with app/ as a URL segment instead of _app/ (see docs/routing.md).

Research date: 2026-10-09. Installed Claude Code: 2.1.295 (changelog entry dated 2026-10-08), so the docs below match the version in use. Read-only research: nothing in the repo was changed. (Plan mode was active, so this report is written to the plan file instead of the scratchpad path that was asked for.)


0. Sources

#SourceDateUsed for
S1https://code.claude.com/docs/en/memory ("How Claude remembers your project")live docs, fetched 2026-10-09; mentions versions up to v2.1.285CLAUDE.md, AGENTS.md, imports, nested files, .claude/rules/
S2https://code.claude.com/docs/en/hooks (reference) and /hooks-guidelive, fetched 2026-10-09 (33 events listed)events, matcher, if, exit codes, output
S3https://code.claude.com/docs/en/skillslive, fetched 2026-10-09SKILL.md frontmatter, paths, progressive disclosure
S4https://code.claude.com/docs/en/features-overview ("Extend Claude Code")live, fetched 2026-10-09CLAUDE.md vs rules vs skills vs hooks
S5https://code.claude.com/docs/en/large-codebaseslive, fetched 2026-10-09per-directory CLAUDE.md vs path rules, deny rules
S6https://code.claude.com/docs/en/permissions (Read and Edit section)live, fetched 2026-10-09path pattern syntax (also used by the hook if field)
S7https://code.claude.com/docs/en/best-practiceslive, fetched 2026-10-09"Write an effective CLAUDE.md"
S8https://code.claude.com/docs/en/changelog (from anthropics/claude-code CHANGELOG.md)top entry 2.1.295, 2026-10-08version dates for features
S9Blog: "Steering Claude Code: when to use CLAUDE.md, skills, hooks, and subagents", Michael Segner (Anthropic), claude.com/blog/steering-claude-code-skills-hooks-rules-subagents-and-more2026-06-18deterministic over prompted
S10Blog: "How Claude Code works in large codebases", claude.com/blog/how-claude-code-works-in-large-codebases-best-practices-and-where-to-start2026-05-14lean, layered CLAUDE.md
S11TanStack Router "File naming conventions", tanstack.com/router/latest/docs/framework/react/routing/file-naming-conventionslive, fetched 2026-10-09 (repo pins react-router 1.170.41)_, (group), -, route.tsx

Relevant changelog dates (S8): .claude/rules/ added 2.0.64 (2025-12-10); InstructionsLoaded hook 2.1.69 (2026-03-05); paths: accepts a YAML list 2.1.84 (2026-03-26); hook if field 2.1.85 (2026-03-26); if fixed for Read/Edit/Write path patterns 2.1.176 (2026-06-12); if: "Edit(src/**)" changed to cwd-anchored in v2.1.214; brace-expansion budget in paths 2.1.217 (2026-07-21); native AGENTS.md reading v2.1.277; /doctor prompt-audit v2.1.283; onFailure: "block" for hooks 2.1.295 (2026-10-08).


1. Verified from the docs

1.1 CLAUDE.md / AGENTS.md (S1, S4, S7, S9, S10)

  • Size: "target under 200 lines per CLAUDE.md file. Longer files consume more context and reduce adherence." Startup shows a warning when a file is over the recommended length, and when the files together pass a combined limit ("Each CLAUDE.md, rules file, and @path import counts as a separate file"). Files over 4 MiB are skipped. (S1)
  • What belongs there: "Keep it to facts Claude should hold in every session: build commands, conventions, project layout, 'always do X' rules. If an entry is a multi-step procedure or only matters for one part of the codebase, move it to a skill or a path-scoped rule instead." (S1). Best practices table (S7): include commands Claude can't guess, style rules that differ from defaults, repo etiquette, project-specific architecture decisions, env quirks, non-obvious gotchas. Exclude what Claude can read from the code, standard conventions, detailed API docs, frequently changing information, tutorials, file-by-file descriptions. Test for each line: "Would removing this cause Claude to make mistakes?"
  • S10: "The root file should be pointers and critical gotchas only."
  • Imports: @path/to/file, relative to the importing file, up to four hops of recursion. Imports don't reduce context cost: "imported files also load at launch." Paths inside backticks or code blocks are not imported. Paths with spaces need \ escapes. (S1)
  • HTML comments (<!-- -->, block level) are stripped before injection: maintainer notes cost no tokens. (S1)
  • Load order: CLAUDE.md from cwd and every ancestor at launch, concatenated root-first. Subdirectory CLAUDE.md files load on demand, "once Claude reads, writes, or edits another file in that subdirectory" (Read tool, or a Bash cat/head on one file). They don't show under /context › Memory files; a Loaded line appears instead. (S1)
  • After /compact: the root CLAUDE.md is re-read from disk. Nested CLAUDE.md and paths: rules reload on demand only when a matching file is touched again. (S1)
  • AGENTS.md (v2.1.277+): read natively only if there is no CLAUDE.md / .claude/CLAUDE.md / CLAUDE.local.md in cwd or above. With CLAUDE.md containing @AGENTS.md (this repo's setup), Claude reads CLAUDE.md with AGENTS.md through the import, never twice. The docs say to keep this setup. Subdirectory AGENTS.md files load on Read when that directory has no CLAUDE.md. .claude/rules/ files still load alongside. (S1)
  • Audits: /doctor proposes trims of a checked-in CLAUDE.md (v2.1.206+). /doctor prompt-audit (v2.1.283+) finds stale, contradictory or outdated-model instructions across CLAUDE.md, AGENTS.md, rules and skills. (S1)

1.2 Path-scoped rules: .claude/rules/ (S1)

  • Location: .claude/rules/**/*.md, discovered recursively (subfolders are fine). User-level: ~/.claude/rules/.
  • Without paths: loaded at launch "with the same priority as .claude/CLAUDE.md" (so no saving over CLAUDE.md).
  • Exact syntax:
    ---
    paths:
      - 'src/api/**/*.ts'
    ---
    
    # API Development Rules
    
    - All API endpoints must include input validation
    paths is the only frontmatter field read ("any other field is ignored without an error"). It takes a YAML list or a comma-separated string. Brace expansion works ("src/**/*.{ts,tsx}"), with a budget of 1,000 expanded patterns per rule. [ starts a bracket expression; escape a literal one as \[. If the YAML doesn't parse, the frontmatter is ignored and the rule loads unconditionally (see claude --debug).
  • When they load: "A path-scoped rule loads when Claude uses the Read, Write, or Edit tool on a matching file", or views one with a single-file Bash read (cat, head). Symlinked paths match too. Glob examples in the docs: **/*.ts, src/**/*, *.md (= root only), src/components/*.tsx.
  • Precedence: none. Everything is concatenated. User rules come before project rules. "If two instructions contradict each other, Claude may pick one arbitrarily."
  • Rules vs skills (S1 note): "Rules load into context every session or when matching files are opened. For task-specific instructions that don't need to be in context all the time, use skills."
  • Per-directory CLAUDE.md vs path rule (S5): directory file = owned and versioned next to the code. Central path rule = "all conventions in one place, or the same rule applies to many scattered paths."
  • Debugging: an InstructionsLoaded hook logs which CLAUDE.md or rule file loaded and why (S1, S2).

1.3 Hooks (S2, S8)

  • Events (33 in 2.1.295): SessionStart, Setup, UserPromptSubmit, UserPromptExpansion, PreToolUse, PermissionRequest, PermissionDenied, PostToolUse, PostToolUseFailure, PostToolBatch, Notification, MessageDisplay, SubagentStart, SubagentStop, TaskCreated, TaskCompleted, Stop, StopFailure, TeammateIdle, InstructionsLoaded, ConfigChange, CwdChanged, DirectoryAdded, FileChanged, WorktreeCreate, WorktreeRemove, PreCompact, PostCompact, PreModelSwitch, PostModelSwitch, Elicitation, ElicitationResult, SessionEnd.
  • Handler types: command, http, mcp_tool, prompt, agent (the last is experimental). Common fields: type, if, timeout, statusMessage, once (only honoured in skill frontmatter). New in 2.1.295: onFailure: "block" for command and http hooks.
  • matcher filters on the tool name only: "It can't filter on file paths." "Edit|Write" is an exact list. Any other character makes it an unanchored JS regex, so Edit.* also matches NotebookEdit.
  • Native path filtering: yes, the handler-level if field (since 2.1.85, path patterns fixed in 2.1.176). It takes one permission rule, with no &&, || or lists. It is evaluated only on PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest and PermissionDenied. On any other event, a handler with if never runs. Exact form:
    {
      "hooks": {
        "PostToolUse": [
          {
            "matcher": "Edit|Write",
            "hooks": [
              {
                "type": "command",
                "if": "Edit(*.ts)",
                "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/x.sh"
              }
            ]
          }
        ]
      }
    }
    Path semantics come from permission rules (S6), which use gitignore syntax: * stays within one segment and ** crosses directories. The anchors: //abs, ~/home, /path (relative to the settings source, which for project settings is the primary working directory), and path or ./path (relative to cwd). "In an if condition for a file tool, a single-segment directory pattern like "Edit(src/**)" matches only the src directory in the working directory… To match a directory named src at any depth, write "Edit(**/src/**)"." Bare filenames match at any depth. Parentheses need no escaping (Edit(./Finance (2024)/**)). For Bash, "Because the if filter is best-effort, use the permission system rather than a hook to enforce a hard allow or deny."
  • Exit codes: 0 = success (stdout becomes context only for UserPromptSubmit, UserPromptExpansion, SessionStart and PostModelSwitch). 2 = blocking: PreToolUse blocks the call, and PostToolUse shows stderr to Claude. Any other code is non-blocking: "exit 1 does not block."
  • PostToolUse feedback without blocking: {"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"..."}}, capped at 10,000 characters. Phrase it as facts, not imperatives.
  • ${CLAUDE_PROJECT_DIR} is the project root. Handlers that are identical across settings files run once. All matching hooks run in parallel.

1.4 Skills (S3, S4, S5)

  • Project skills: .claude/skills/<name>/SKILL.md. Nested <subdir>/.claude/skills/ load the first time Claude touches a file in that subdirectory.
  • Frontmatter: name, description (recommended), when_to_use, argument-hint, arguments, disable-model-invocation, user-invocable, allowed-tools, disallowed-tools, model, effort, context: fork, agent, background, hooks, paths (globs, same format as rules), shell, plus the spec fields metadata, license, compatibility.
  • Loading: the description is always in context (description plus when_to_use are truncated at 1,536 characters, so put the key use first). The body loads on invocation and stays loaded. With disable-model-invocation: true the description is not in context and only /name triggers the skill. paths: "Claude loads the skill automatically only when working with files matching the patterns." Keep SKILL.md under 500 lines and put reference material in supporting files.
  • When to use (S3/S4): a skill when a CLAUDE.md section "has grown into a procedure rather than a fact", for reference material needed only sometimes, and for invocable workflows. Facts stay in CLAUDE.md. "Use disable-model-invocation: true for skills with side effects."

1.5 Deterministic enforcement (S1, S4, S9, S10)

  • S1: CLAUDE.md is "context, not enforced configuration… To block an action regardless of what Claude decides, use a PreToolUse hook." "If the instruction is something that must run at a specific point… write it as a hook."
  • S4: "Put guardrails in hooks. An instruction like 'never edit .env'… is a request, not a guarantee… If a rule must hold every time, make it a hook rather than a prompt instruction."
  • S9 (2026-06-18): "A real guardrail needs to be deterministic, and the enforcement methods are hooks and permissions." "The model choosing to run a formatter is different from the formatter running automatically."
  • S10 (2026-05-14): hooks for linting and formatting "enforce the rules deterministically". It names "Using prompts for things that should run automatically" as a common mistake. Review the configuration every 3 to 6 months and after model releases.
  • S5/S6: for files that must not change, use permissions.deny (Edit(...), Read(...)). A Read deny also blocks Edit and Write. Write(...) path rules are never consulted; use Edit(...).

2. Inferred or unverified (check before relying on it)

  1. Which directory rule paths are relative to. The docs only show root-relative examples (src/api/**/*.ts). I infer that they're relative to the project root for root .claude/rules/. The docs don't say how nested .claude/rules/ resolve. Keep all rules in the root .claude/rules/.
  2. The glob engine for rule paths isn't named. The docs mention braces and [, but say nothing about (. Hook if and permissions use gitignore syntax, where ( is literal. Engines such as picomatch can read (a|b) as a group, so a folder named (app) might match the folder app instead. This risk is the main reason for the naming advice in §4.
  3. if: "Edit(...)" on Write calls. S6 says "Edit rules apply to all built-in tools that edit files", and changelog 2.1.176 talks about "if conditions for Read/Edit/Write tool paths", so I expect Edit(...) to match Write calls too. Test it once with a logging hook.
  4. Timing of path rules on a new file. The rule loads on the Write that creates the file, so the first version of a new file may be written before Claude sees the rule. Hooks and lint catch that. Rules guide later edits.
  5. CLAUDE.md inside src/routes/. I expect the TanStack router generator to ignore .md files (it only picks up route-module extensions). Check with bunx tsr generate after adding one.
  6. _app/route.tsx as a pathless layout written as a directory. S11 documents _ (pathless) and the route token separately but never shows them combined. Verify with the generator before committing to it.
  7. Wildcards in Bash if patterns such as Bash(*3200*) aren't documented. Do the check inside the script instead.

3. Applied to this repo

3.1 Current state

  • CLAUDE.md = @AGENTS.md (correct per S1). AGENTS.md is 151 lines: under 200, but it loads in full every session, and about two thirds of it only matters for some paths (docs styling, statecharts, PDFs, ELK, themes, fixtures, design sources).
  • .claude/settings.json: one PostToolUse Edit|Write hook, oxc.sh (oxlint --fix plus oxfmt, exit 2 with stderr on leftovers). This already follows S9 and S10.
  • .claude/skills/update-im-web/ (52 lines). Its description says "Use ONLY when the user explicitly asks", but the skill is model-invocable, so its description sits in context every session.
  • No .claude/rules/, no permissions, no other hooks.
  • Route layout is inconsistent for docs, which matters for globs:
    • prototypes keep docs at a URL segment docs/ (src/routes/prototypes/<id>/docs/**) and the app in pathless _public/ and _checkout/ plus admin/ and meta/
    • image-grid, page-builder and receipt use a pathless _docs.tsx + _docs/ (which contains a docs/ segment)
    • order-flow uses docs/
    • design-system keeps its docs pages at the feature root (just moved out of _docs/)
    • presentations has no docs

3.2 Target: what goes where

Content now in AGENTS.mdGoes toWhy
Purpose (3 lines), commands, ports, the two experiment kinds, kinds of docs and what binds, "decisions.md wins", one folder per experiment, internal consistency, self-contained repo, how to start and end a session (one line each pointing at skills), Related repos (2 lines)AGENTS.md (core, about 50 to 70 lines)facts needed in every session; cross-cutting; shared with non-Claude agents
Structure treecut, or keep 5 linesS7: "Anything Claude can figure out by reading code"
Per-area rules and gotchas.claude/rules/*.md with paths (§3.3)load only when relevant
Procedures (new experiment, handover, refresh fixtures, stable copy, perf run, im-web update)skills (§3.6)"procedures belong in skills"
"never change X", ports, format and lint, tokens in syncpermissions, hooks, tests, lint (§3.4, §3.5, §3.7)deterministic
Design sources (Figma IDs, Carbon, ntb.no measurements)rule scoped to UI and design-system paths, or a reference skillonly needed for UI or design work
Each experiment's -docs/decisions.mdper-experiment nested CLAUDE.md containing @-docs/decisions.md (§3.3b)loads automatically on first touch of that experiment; decisions files are 15 to 84 lines

Note: .claude/rules/ and nested CLAUDE.md are Claude-only. Codex and other agents only see AGENTS.md, so anything those agents must know stays in AGENTS.md. Because rules can only be scoped by path (no other frontmatter field), the split follows the folder layout.

3.3 Path-scoped rules (proposed files, globs from the actual tree)

All files go in the root .claude/rules/. The globs are written relative to the repo root.

Rule filepathsContent moved from AGENTS.md
experiments.mdsrc/routes/prototypes/**, src/routes/features/**One folder per experiment, what may be shared, prototypes may import features. Read this experiment's -docs/decisions.md first (backup for §3.3b). - prefix = ignored by router. Route files carry full paths. Link paths have no trailing slash. Registry entry in src/platform/prototypes/registry.ts
data-flow.mdsrc/routes/**/*.{ts,tsx}, src/platform/**/*.tsloaders call createServerFn; mutations then router.invalidate({ sync: true }); .validator not .inputValidator; isomorphic loaders, file access only in *.server.ts or server fns; serializable returns, JsonValue[]; per-prototype db.server.ts keyed by PROTOTYPE_ID; no TanStack Query; load the TanStack skill first
public-site-look.mdsrc/routes/prototypes/*/_public/**, src/routes/prototypes/*/_public.tsx, src/routes/prototypes/*/_checkout/**, src/routes/prototypes/*/_checkout.tsx (with §4: src/routes/prototypes/*/_app/**)copy the live public site; one colour mode per page; measured sizes (header 64, toolbar 56, rows 200/8, 768/400)
statecharts.mdsrc/routes/*/*/-lib/rules/**, src/platform/statechart/**ChartDefinition; meta.label, meta: { label, sender }; meta.question; guardText; time as events, not after; derived decision tables; ELK lazy in effects; canvas fit threshold
docs-pages.mdsrc/routes/docs/**, src/routes/*/*/docs/**, src/routes/*/*/_docs/**, src/routes/*/*/_docs.tsx, src/platform/docs/**, src/routes/*/*/-docs/**DocsShell, DocsPage, DocKindBanner; DS tokens without dark:; .ds-light/.ds-dark; Components pages (h2 per variation, "what to check", data-toc-ignore); visual first; every doc viewable in the app; layout principles (sections, containers, rows, cells, 16 px)
im-web-today.mddocs/im-web/**, src/reference/im-web/**, src/routes/docs/im-web/**describes, never prescribes; never "correct" it to match an experiment; change only through /update-im-web
proposals.mdsrc/reference/proposals/**, src/routes/docs/proposals/**not adopted until moved into the working model or a decisions.md
design-system.mdsrc/routes/features/design-system/**, src/styles.css, src/platform/theme/**Carbon as reference, ntb.no as visual source, DS decisions, tokens.ts and tokens.css together, Geist override
themes-vendored.mdsrc/styles/**, src/platform/ui/**, src/platform/ui/use-mobile.tscopied from im-web or shadcn; lint ignores them; don't hand-edit
media.mdsrc/platform/media/**, data/**, scripts/ingest-server.tsMediaObject mirrors NTB search API; refresh steps go in the skill
pdf.md**/*.pdf.ts, src/routes/features/receipt/-lib/pdf.server.tsplaywright-core channel: 'chrome', one browser, localhost not tunnel
vite-config.mdvite.config.tsdevtools console piping stays off (froze Chrome); Vite 8 log forwarding; restart after bun add and rm -rf node_modules/.vite
handovers.mddocs/handovers/**format, pruning
formatting.mdsrc/**/*.{ts,tsx}Intl through src/platform/format.ts; JSDoc on new modules (short: lint enforces most of it)

3.3b Per-experiment nested CLAUDE.md (recommended in addition to experiments.md): src/routes/prototypes/baseline/CLAUDE.md containing:

@-docs/decisions.md
@-docs/status.md

Loads the first time Claude reads or edits any file in that experiment (S1). It enforces "the experiment's decisions.md wins" without a hand-written instruction, and costs nothing in other sessions. scripts/new-prototype.ts would copy it along. Check item 5 in §2 (router ignores .md) first.

3.4 Permissions (deterministic "never change")

Add to .claude/settings.json. Use a leading / (anchored at the project root for project settings), because a single-segment deny such as Edit(data/**) would match any data/ folder at any depth (S6):

"permissions": {
  "deny": [
    "Edit(/data/**)",
    "Edit(/src/routeTree.gen.ts)"
  ],
  "ask": [
    "Edit(/src/platform/media/types.ts)",
    "Edit(/src/styles/**)",
    "Edit(/src/platform/ui/**)",
    "Edit(/src/platform/brand/**)",
    "Edit(/docs/im-web/**)",
    "Edit(/src/reference/im-web/**)"
  ]
}

(ask instead of deny for vendored or im-web files, because the shadcn CLI and /update-im-web legitimately change them.)

3.5 Hooks

Keep oxc.sh. Add:

HookEvent and filterReplaces AGENTS.md text
tokens-check.sh: run bun test src/routes/features/design-system/-lib/tokens.test.ts, exit 2 with output on failurePostToolUse, matcher Edit|Write, "if": "Edit(src/routes/features/design-system/-lib/tokens.*)""Change tokens.ts and tokens.css together"
no-local-paths.sh: grep the edited file for /Users/, /home/, ~/, /private/tmp; exit 2PostToolUse Edit|Write (one handler per area via if, or filter inside the script)"Never reference files outside it… absolute paths"
guard-port.sh: block Bash commands that bind or kill port 3200PreToolUse matcher Bash, check inside the script (§2 item 7), exit 2; consider "onFailure": "block" (2.1.295)"never use 3200"
open-handovers.sh: print docs/handovers/*.md names plus first headingSessionStart (stdout becomes context; no if, it would never run)"Start a session by reading the open ones"
optional: InstructionsLoaded logger to .claude/logs/InstructionsLoadedto confirm the rules from §3.3 load when expected
optional: Stop hook proposing CLAUDE.md or handover updates (S5, S10)Stopthe "end a session with a handover" reminder

Multi-segment if patterns (Edit(src/routes/...)) are anchored at cwd. For "any depth" use Edit(**/_docs/**). One rule per handler, so two areas need two handlers.

3.6 Skills

SkillFrontmatterFrom
update-im-web (exists)add disable-model-invocation: true: it is user-only by its own description, and this keeps its description out of every session—
new-experimentdescription: Start a new prototype or feature…; runs bun scripts/new-prototype.ts, registry entry, -docs/decisions.md and status.md, nested CLAUDE.md, Components docs groupTwo kinds of experiment, new-prototype, registry
handoverdisable-model-invocation: true (invoked as /handover)Handovers rule: write docs/handovers/YYYY-MM-DD-topic.md, prune finished ones, move lasting items into AGENTS.md or -docs/
component-docs-pagepaths: "src/routes/**/docs/components/**, src/routes/**/_docs/**"Components documentation procedure (if it is longer than a rule)
refresh-media-fixturesdisable-model-invocation: true"Refreshing media fixtures" section
stable-copy, perf-rundisable-model-invocation: true, body points to docs/stable-copy.md and docs/performance-testing.mdCommands with ngrok and perf ports

3.7 Lint, types, tests (see the separate lint audit)

Leave these out of the prompt files once enforced: toLocaleString ban and Intl through format.ts, .inputValidator ban, trailing-slash Links, JSDoc on exports, Tailwind class order (oxfmt), dark: inside DS-token docs, ChartDefinition meta.label and sender (a bun test over all machines), tokens.ts against tokens.css (existing test), fixture byte-identity (test with a hash), devtools piping off (a test that reads vite.config.ts). Plus a route-layout test (§4).

3.8 Proposed minimal AGENTS.md outline (about 60 lines)

  1. What this repo is (3 lines) and "experiments may depart from im-web on purpose".
  2. Commands (the existing block), ports: 3100 dev, never 3200, 3300 perf.
  3. Prototype vs feature (table, 4 rows), registry path.
  4. Kinds of docs and what binds (the existing table: it is cross-cutting and decides every task).
  5. Core rules (6 bullets): one folder per experiment; internally consistent and recorded in decisions.md; the experiment's decisions win, then the working model, then im-web for context only; repo is self-contained (cite Jira, Figma, Bitbucket at a hash); format with Intl; lint and format run automatically by hook.
  6. Session flow: open handovers are listed at start (hook); end with /handover.
  7. Pointers: "Area-specific rules live in .claude/rules/ (Claude) and the experiment's -docs/", stack line, Related repos (2 lines).
  8. <!-- maintainer notes --> for anything humans need but Claude doesn't (stripped, S1).

4. Route file-name patterns for reliable globs

TanStack conventions (S11): _name = pathless layout route (no URL segment, has a layout component); (name) = route group folder (no URL segment, no layout); -name = excluded from the route tree; route.tsx = the route file for a directory; . = flat nesting.

What a glob needs: every file of one kind sits under one literal folder name that no other kind uses, with no characters that a glob engine, gitignore, a regex or the shell treat as special, and with the layout file inside that folder.

PatternURLGlob (rules / hook if)Assessment
_app/ pathless (+ layout _app/route.tsx, or sibling _app.tsx)unchanged (/prototypes/x/search)src/routes/*/*/_app/** / Edit(**/_app/**)Recommended. _ is literal in every glob engine, in gitignore, in regex and in the shell. A layout is free (app chrome vs DocsShell). URL collisions between docs and app routes are reported by the router generator as conflicting paths (inferred).
(app)/ route groupunchangedsrc/routes/*/*/(app)/**Parens are literal in gitignore (if, permissions; S6 says no escaping needed). But the rule paths engine is unspecified (§2 item 2), the hook matcher and grep read them as regex groups, and shell scripts need quoting. No layout, so baseline's _public/_checkout nest one level deeper anyway. Avoid.
app/ URL segment/prototypes/x/app/searchsrc/routes/*/*/app/**Globs are as reliable as _app, but it changes URLs shared with test users, and app could collide with a real segment name. Fine if the URL change is acceptable.
.-flat names (_app.search.tsx)—**/_app.* plus **/_app/**Mixing flat and directory styles doubles every glob. Avoid.

Recommendations:

  1. Per experiment: <id>/route.tsx (thin), <id>/_docs/** (docs, the default: _docs/index.tsx = /prototypes/<id>), <id>/_app/** (the running prototype, with _public, _checkout, admin, meta inside it), <id>/-lib/**, <id>/-docs/**. Put layouts inside their folder (_docs/route.tsx, _app/route.tsx) if the generator accepts it (§2 item 6). Otherwise every glob needs a second entry for _docs.tsx / _app.tsx (brace form in rules: src/routes/*/*/_docs{.tsx,/**}; hook if takes one rule, so use two handlers).
  2. Use the same name everywhere: today prototypes use docs/, three features use _docs/, design-system uses the root. Unify to _docs so that src/routes/*/*/_docs/** is the only docs glob. A docs URL segment inside _docs is fine.
  3. Use */* rather than ** in front of _app and _docs when you mean "top level of an experiment": src/routes/*/*/_docs/** matches exactly src/routes/{prototypes,features}/<id>/_docs/. **/_docs/** would also catch any nested _docs folder. The site-level Docs (src/routes/docs/) is deliberately named docs, not _docs, so the two stay separable by glob. Keep it that way.
  4. Enforce the layout with a bun test that walks src/routes/{prototypes,features}/*/ and fails on any route file outside route.tsx, _app/, _docs/, -lib/, -docs/, so the globs stay true. Have new-prototype.ts produce this shape.
  5. Keep -lib shared between docs and app (component previews need the real components). Rules about code go on -lib/** and _app/**, rules about docs on _docs/** and -docs/**.

5. Suggested order of work

  1. Add permissions (§3.4) and the tokens and port hooks (§3.5). Add disable-model-invocation to update-im-web.
  2. Create .claude/rules/ from §3.3 and shrink AGENTS.md to the outline in §3.8. Run /doctor prompt-audit and /context to check.
  3. Rename routes to _docs / _app (§4), add the route-layout test, update new-prototype.ts and the globs.
  4. Add nested per-experiment CLAUDE.md files after checking the router ignores them.
  5. Re-run /doctor prompt-audit after model releases. Review every 3 to 6 months (S10).