Merge feature/claude-global-md-rename into develop

This commit is contained in:
Bastien Chanot
2026-07-14 18:43:36 +02:00
21 changed files with 963 additions and 371 deletions
+13
View File
@@ -83,6 +83,8 @@ rules:
| BDR-060 | 2026-07-08 | job9: CC orchestration floor = v2.1.172 (nested dispatch), supersedes implicit v2.1.83 whole-system floor | accepted | | BDR-060 | 2026-07-08 | job9: CC orchestration floor = v2.1.172 (nested dispatch), supersedes implicit v2.1.83 whole-system floor | accepted |
| BDR-061 | 2026-07-08 | job9: seo/geo analyzers → fix-bundle→L1 by doctrine (validator-analyzer pattern), not by version constraint | accepted | | BDR-061 | 2026-07-08 | job9: seo/geo analyzers → fix-bundle→L1 by doctrine (validator-analyzer pattern), not by version constraint | accepted |
| BDR-062 | 2026-07-08 | supersede BDR-031's 275 CLAUDE.md target — 305 assumed reality (extraction done at job1; more compression costs clarity > tokens); guard threshold realigned 280→320 | accepted | | BDR-062 | 2026-07-08 | supersede BDR-031's 275 CLAUDE.md target — 305 assumed reality (extraction done at job1; more compression costs clarity > tokens); guard threshold realigned 280→320 | accepted |
| BDR-063 | 2026-07-10 | GSC multi-account: OAuth2 installed-app flow + label-keyed token store, explicit (account,property) args, no global state | accepted |
| BDR-064 | 2026-07-14 | global memory split: repo file → CLAUDE.global.md (deployed name unchanged), CLAUDE.md freed for project scope; consumer/maintainer wording rule | accepted |
--- ---
@@ -950,3 +952,14 @@ rules:
- **Why**: user needs real field data (the one edge marketplace `claude-seo` had that personal skills lacked); multi-account without cross-site leakage; secrets never in code (all from `~/.claude/.env`). - **Why**: user needs real field data (the one edge marketplace `claude-seo` had that personal skills lacked); multi-account without cross-site leakage; secrets never in code (all from `~/.claude/.env`).
- **Alternatives rejected**: (a) service-account — GSC needs per-property owner grant + no interactive consent, wrong for a personal multi-client tool. (b) API-key-only — GSC has no key auth (CrUX does → `CRUX_API_KEY`). (c) single "current account" global + switch verb — a race the moment two audits run; explicit args dissolve it by construction. - **Alternatives rejected**: (a) service-account — GSC needs per-property owner grant + no interactive consent, wrong for a personal multi-client tool. (b) API-key-only — GSC has no key auth (CrUX does → `CRUX_API_KEY`). (c) single "current account" global + switch verb — a race the moment two audits run; explicit args dissolve it by construction.
- **Reference**: `lib/seo-data/` (tokenstore.py, connect.py, google_seo.py, fetch.sh), `lib/seo-data/README.md`; fronted by [[LRN-119]] (fail-open contract). - **Reference**: `lib/seo-data/` (tokenstore.py, connect.py, google_seo.py, fetch.sh), `lib/seo-data/README.md`; fronted by [[LRN-119]] (fail-open contract).
---
## BDR-064 — Global memory split: repo global file → CLAUDE.global.md, CLAUDE.md freed for project scope
- **Date**: 2026-07-14
- **Status**: accepted (shipped feature/claude-global-md-rename, merge pending human GO)
- **Decision**: repo-root global memory `git mv` → `CLAUDE.global.md`; deployed name unchanged (`~/.claude/CLAUDE.md` symlink via link.sh). `CLAUDE.md` name freed → real project-scope memory for claude-config (Health Stack + rules/ doctrine — ex-"This repo only" section + ex-rules/README body; rules/README = 3-line pointer, keeps `paths:` frontmatter). Wording rule (user-arbitrated): consumer-facing hook strings say "global CLAUDE.md" (deployed name — foreign sessions resolve via symlink, repo filename means nothing there); maintainer comments say `CLAUDE.global.md`. Guards follow: session-start 320-guard path, doctor EXACT readlink-target check (new), GUARDED_CONFIGS 4 entries (keeps "CLAUDE.md" — graphify rewrite target = project file now), doc-commit exclusions, CHANGELOG BREAKING(layout) line ("run bash link.sh once after pull").
- **Why**: "This repo only" section + rules/README doctrine loaded in EVERY project (~40+280 tok waste + foreign-project glob over-match); repo had no project-scope memory slot — filename occupied by global content.
- **Alternatives rejected**: `CLAUDE.prod.md` name ("prod" implies deploy env that doesn't exist); project `.claude/rules/repo.md` (works, less idiomatic than project CLAUDE.md, no natural home for future repo-specific content). NOT a revival of BDR-021's rejected 2-file split — that was global content in 2 SYNCED files; here scopes disjoint, zero sync.
- **Reference**: feature/claude-global-md-rename (9496538 rename R98%, e9a38a0 guards), spec `docs/superpowers/specs/2026-07-12-claude-global-md-rename-design.md`. Linked [[BDR-021]], [[BDR-031]], [[BDR-062]], [[LRN-122]], [[LRN-123]].
+3
View File
@@ -377,3 +377,6 @@ rules:
- Docs synced (`/doc`, `4a15c73` on `chore/doc-sync-gsc-crux`): README (seo-connect, make-test glob, /seo row) + USAGE (/seo FULL real-data) + CHANGELOG Added entry. Pending: merge `chore/doc-sync-gsc-crux`→develop (human GO), then delete transient spec+plan `docs/superpowers/…gsc-crux…`. - Docs synced (`/doc`, `4a15c73` on `chore/doc-sync-gsc-crux`): README (seo-connect, make-test glob, /seo row) + USAGE (/seo FULL real-data) + CHANGELOG Added entry. Pending: merge `chore/doc-sync-gsc-crux`→develop (human GO), then delete transient spec+plan `docs/superpowers/…gsc-crux…`.
- Post-ship housekeeping merged to develop: `chore/doc-sync-gsc-crux` (`8a1fac0`, docs+memory+transient-cleanup), then `bugfix/seo-connect-env-source` (`61a98d3`) — `make seo-connect` never sourced `~/.claude/.env` so OAuth creds never reached connect.py; found by real `make seo-connect` run (403 discover_properties after consent = Search Console API not enabled + the env bug). Live OAuth validated end-to-end by user (consent OK, app published to Production for non-expiring refresh token). - Post-ship housekeeping merged to develop: `chore/doc-sync-gsc-crux` (`8a1fac0`, docs+memory+transient-cleanup), then `bugfix/seo-connect-env-source` (`61a98d3`) — `make seo-connect` never sourced `~/.claude/.env` so OAuth creds never reached connect.py; found by real `make seo-connect` run (403 discover_properties after consent = Search Console API not enabled + the env bug). Live OAuth validated end-to-end by user (consent OK, app published to Production for non-expiring refresh token).
- `/feat` feature/seo-account-mgmt (unmerged, human GO pending): account-management verbs — tokenstore remove/clear, fetch.sh forget, connect.sh wrapper (sources env, runs from any project), `/seo connect|accounts|forget` routing, Makefile delegates to wrapper. Commits `8bf7459` (feat) + `887341d` (doc USAGE). Security loop hit its cap: 3 GATE-2 BLOCKs on the label guard (injection → parser differential → per-line-grep newline), closed categorically by a whole-string POSIX `case` guard [[LRN-121]]; final fresh scan PASS (~50 vectors, 0 bypass). 85/85 engine + `make test` green throughout. forget = local delete, NOT Google revocation (surfaces myaccount.google.com/permissions). - `/feat` feature/seo-account-mgmt (unmerged, human GO pending): account-management verbs — tokenstore remove/clear, fetch.sh forget, connect.sh wrapper (sources env, runs from any project), `/seo connect|accounts|forget` routing, Makefile delegates to wrapper. Commits `8bf7459` (feat) + `887341d` (doc USAGE). Security loop hit its cap: 3 GATE-2 BLOCKs on the label guard (injection → parser differential → per-line-grep newline), closed categorically by a whole-string POSIX `case` guard [[LRN-121]]; final fresh scan PASS (~50 vectors, 0 bypass). 85/85 engine + `make test` green throughout. forget = local delete, NOT Google revocation (surfaces myaccount.google.com/permissions).
## 2026-07-14
- `/ship-feature` feature/claude-global-md-rename (unmerged, human GO pending): global memory → CLAUDE.global.md + project-scope CLAUDE.md, 8 commits (a4ee7e1 docs → e9a38a0 guards). Full pipeline: analyzer + contract (17 criteria), brainstorm/spec/plan gates, SDD 5 tasks (all task reviews Approved), verifier CONFORME 17/17 (after user-arbitrated criterion-9 consumer-wording + FILE-SCOPE [gated] enrichment), security PASS (semgrep 43 rules, 0), final review "Yes" after 2 Important fixes (guard-test drift → 7/7; doctor exact-target check). Decided [[BDR-064]]; learned [[LRN-122]] (2-commit rename split), [[LRN-123]] (exact symlink target). `make test` green throughout. settings.json plugin toggles = session-scoped, NOT committed — restore (gstack/ui-ux-pro-max/frontend-design/emil-design-eng/darwin-skill/magic ON) after merge.
+18
View File
@@ -1214,3 +1214,21 @@ rules:
- **why it matters**: three distinct bypasses of the SAME guard = the approach was wrong, not each patch. `grep`'s line-orientation + locale-sensitive ranges, plus argv-prescan-vs-real-parser grammar drift, are the three classic ways an allowlist "passes" a string it shouldn't. Whole-string `case` in C locale closes all three at once. These were defense-in-depth (downstream used `"$2"`/`"$@"`/JSON-key, never `sh -c`/`eval` → not exploitable in the real exec chain) — but the backstop still took a categorical rewrite, and 3 security-gate BLOCKs to get there. - **why it matters**: three distinct bypasses of the SAME guard = the approach was wrong, not each patch. `grep`'s line-orientation + locale-sensitive ranges, plus argv-prescan-vs-real-parser grammar drift, are the three classic ways an allowlist "passes" a string it shouldn't. Whole-string `case` in C locale closes all three at once. These were defense-in-depth (downstream used `"$2"`/`"$@"`/JSON-key, never `sh -c`/`eval` → not exploitable in the real exec chain) — but the backstop still took a categorical rewrite, and 3 security-gate BLOCKs to get there.
- **future application**: validate shell input WHOLE-STRING (`case` or bash `[[ =~ ]]`), never `grep -q` (per-line). Set `LC_ALL=C` for byte-wise ranges. A guard that pre-scans argv must be STRICTER than the downstream parser (reject `=`-joined/abbrev) or validate post-parse against the value the parser settled on. When a fix is bypassed twice → STOP patching, replace the mechanism (re-plan, not whack-a-mole). - **future application**: validate shell input WHOLE-STRING (`case` or bash `[[ =~ ]]`), never `grep -q` (per-line). Set `LC_ALL=C` for byte-wise ranges. A guard that pre-scans argv must be STRICTER than the downstream parser (reject `=`-joined/abbrev) or validate post-parse against the value the parser settled on. When a fix is bypassed twice → STOP patching, replace the mechanism (re-plan, not whack-a-mole).
- **cousin**: [[LRN-119]] (fail-open engine this hardens), [[BDR-063]] (token store whose labels these guard), [[LRN-045]] (renaming-command leak-guard regexes — same charset-guard family). - **cousin**: [[LRN-119]] (fail-open engine this hardens), [[BDR-063]] (token store whose labels these guard), [[LRN-045]] (renaming-command leak-guard regexes — same charset-guard family).
---
## LRN-122 — git mv + recreate source path in same commit = rename detection dead
- **pattern**: rename file + create NEW file at old path in ONE commit → git never pairs the rename (source path never vanishes — index sees modify(old)+add(new)). `git log --follow` chain lost; deterministic, persists forever. Fix: TWO commits — pure rename first (paired at R~98%), recreation second. Found live: Task-2 implementer hit the plan's own "2 hunks" STOP gate, diagnosed root cause, escalated instead of patching around it.
- **why**: contract criterion (history preserved) outranks plan packaging ("atomic commit"). Commit-level atomicity ≠ deploy-level atomicity — deployed symlink already fixed by running link.sh, independent of commit split.
- **future application**: ANY rename-and-replace-in-place (config forks, template splits, versioned API files). Old path must be re-occupied → split commits; verify `git diff -M --stat parent` shows the `=>` rename line before proceeding.
- **cousin**: [[BDR-064]] (the split this served), [[LRN-120]] (review-base hygiene — same git-range-semantics family).
---
## LRN-123 — "resolves inside repo" symlink check green-lights stale link once old path re-occupied
- **pattern**: doctor's check_symlink asserted only `readlink -f` lands inside `$REPO` — safe while ONE candidate file existed. Rename freed old path for a NEW file → stale post-pull link (`~/.claude/CLAUDE.md` → `$REPO/CLAUDE.md`) resolves to project file (inside repo) → check PASS, global doctrine silently absent every session. Fix: assert EXACT readlink target (`$REPO/CLAUDE.global.md`), warn + remedy cmd (`run: bash link.sh`). Caught by final whole-branch review (fresh most-capable model), not by any earlier gate.
- **why**: containment predicates (inside-dir, prefix-match) silently weaken the moment layout gains a second valid-looking target; exactness costs nothing.
- **future application**: symlink/path health checks → assert exact expected target whenever the old target path can be re-occupied; test all three states (correct / stale / missing).
- **cousin**: [[BDR-064]], [[LRN-104]] (hook message = test contract — same guard-must-follow-the-change family).
@@ -0,0 +1,45 @@
# CONTRACT — claude-global-md-rename
- date: 2026-07-12 | flow: ship-feature | branch: (feature branch off develop, created at STEP 4)
- status: active
## REQUEST (verbatim — IMMUTABLE)
> pour les soucis 1 et 2, ne serais-ce pas plus judicieux de mettre notre claude.md de ce repo, qui es tle global, le renommer en CLAUDE.prod.md ou quelqeu chose comme ca, avec tout ce qui concerne le userscope, le link.sh fait un lien symbolique de ce fichier avec ce nom vers ~/.claude/CLAUDE.md car on peut avoir un nom differnt du lien, et ca permet d'avoir le claude.md du projet dasn le quel on met ces deux partie qui sont pas destine au userscope. qu'en pense tu ?
> oui, utilise /ship-feature pour faire les modification vers un CLAUDE.global.md et toute les dependance et iunstallateur et update etc
(Name arbitrated in conversation: `CLAUDE.global.md`, not `CLAUDE.prod.md`.)
## CLARIFICATIONS
none — request complete (design questions resolved at STEP 1 brainstorm, gated at STEP 3)
## ACCEPTANCE CRITERIA
1. `CLAUDE.global.md` exists at repo root, renamed via `git mv` (history preserved: `git log --follow CLAUDE.global.md` shows pre-rename commits), containing the former global content MINUS the `# This repo only (claude-config)` section, PLUS a short scope header stating it is the user-scope global memory deployed as `~/.claude/CLAUDE.md`.
2. A new project-level `CLAUDE.md` exists at repo root containing: a short scope header (project-only, not user-scope), the former "This repo only" content (Health Stack / shellcheck), and the rules/ maintenance doctrine migrated from `rules/README.md` (what belongs in rules/, lazy-load semantics, machine-owned context7/BDR-053 note).
3. `link.sh` links `<repo>/CLAUDE.global.md` → `~/.claude/CLAUDE.md`; after running it, `readlink ~/.claude/CLAUDE.md` resolves to `<repo>/CLAUDE.global.md` (stale link replaced, no dangling symlink).
4. `hooks/session-start.sh` line-count guard (BDR-062) reads `CLAUDE.global.md` (new path), threshold 320 unchanged, and does not silently fail-open on the old path.
5. `doctor.sh` passes: `~/.claude/CLAUDE.md` symlink check green; size/token reporting reads `CLAUDE.global.md`.
6. `install-plugins.sh` GUARDED_CONFIGS protects `CLAUDE.global.md` (installer drift guard follows the renamed file).
7. `lib/doc-commit.sh` exclusion list covers `CLAUDE.global.md` as read-only/never-target (BDR-022 unchanged in spirit).
8. `rules/README.md` slimmed to a minimal pointer (keeps `paths:` frontmatter; doctrine lives in the project CLAUDE.md).
9. No stale script reference remains: `grep -rn 'CLAUDE\.md' *.sh hooks/*.sh lib/*.sh` shows no reference meaning the repo-root GLOBAL file under its old name (references to `~/.claude/CLAUDE.md` symlink name and to per-project CLAUDE.md concept are expected and unchanged). [gated 2026-07-14 clarification, user-arbitrated] CONSUMER-facing hook strings (messages injected into sessions, which run in any project) reference the global by its DEPLOYED name — "global CLAUDE.md" — because consumers resolve it via ~/.claude/CLAUDE.md; only MAINTAINER-facing comments use the repo filename CLAUDE.global.md. Both are conformant, not stale.
10. `README.md` / `USAGE.md` / `MIGRATION.md` layout descriptions updated where they mean the repo-root global file.
11. `shellcheck` passes on every modified `.sh` file (repo Health Stack).
12. [gated 2026-07-13] New project CLAUDE.md is MINIMAL — scope header + Health Stack + rules/ maintenance doctrine (incl. context7/BDR-053 note and the foreign-project glob caveat); no empty template sections.
13. [gated 2026-07-13] rules/README.md keeps `paths: ["rules/**"]` frontmatter; body reduced to a pointer referencing the project CLAUDE.md.
14. [gated 2026-07-13] Global file nets 305 → 301 lines; scope header = 2-line HTML comment above the title; `git diff -M --cached -- CLAUDE.global.md` shows exactly two hunks (header insertion, tail-section deletion).
15. [gated 2026-07-13] `GUARDED_CONFIGS` has 4 entries: keeps `"CLAUDE.md"` (graphify's rewrite target = project file) AND adds `"CLAUDE.global.md"`; mktemp error message lists all four.
16. [gated 2026-07-13] USAGE.md / MIGRATION.md / update-all.sh verified as having zero references to the repo-root global file — deliberately not edited.
17. [gated 2026-07-13] Spec + plan docs are the feature branch's first commit; the session-scoped plugin toggles in settings.json are NEVER staged in any commit of this branch.
## FILE SCOPE
- CLAUDE.md → CLAUDE.global.md (git mv + content split)
- CLAUDE.md (new project-level file)
- link.sh
- hooks/session-start.sh
- doctor.sh
- install-plugins.sh
- lib/doc-commit.sh
- rules/README.md
- README.md, USAGE.md, MIGRATION.md (doc references)
- [gated 2026-07-14] hooks/config-protection.sh, hooks/design-toolchain-reminder.sh (required by criterion 9's sweep — global-file references in comments/messages)
- [gated 2026-07-14] docs/superpowers/specs/2026-07-12-claude-global-md-rename-design.md, docs/superpowers/plans/2026-07-13-claude-global-md-rename.md (required by criterion 17 — branch's first commit)
+1
View File
@@ -91,6 +91,7 @@ skills-disabled/
.claude/settings.local.json .claude/settings.local.json
.claude/agent-memory/ .claude/agent-memory/
.claude/gstack/ .claude/gstack/
.audit/
# Generated outputs # Generated outputs
graphify-out/ graphify-out/
+1
View File
@@ -7,6 +7,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
## [Unreleased] ## [Unreleased]
### Changed ### Changed
- BREAKING(layout): repo-root global memory renamed CLAUDE.md → CLAUDE.global.md; run `bash link.sh` once after pulling (doctor.sh now checks the exact target)
- graphify skill dist refreshed 0.8.45 → 0.9.6 (out-of-band `make plugin`; SKILL.md + query/extraction references updated by the generator). - graphify skill dist refreshed 0.8.45 → 0.9.6 (out-of-band `make plugin`; SKILL.md + query/extraction references updated by the generator).
- `/deploy` checklist reshaped on first-real-run feedback, in two passes: runbook steps are **one command per line, interactive-session style** (an early step opens the ssh session; later lines run on the box; local steps say "from your machine") instead of folded `ssh host "cd … && …"` one-liners — step = comment header + command lines up to the next blank line, a `@delta:` directive governs the whole block; and the checklist is now **display-only** — `NEXT.sh` is no longer written at all (throwaway artifact; `PENDING.json` + the live runbook regenerate it in any session) and every hand-back **ends the turn with the full checklist as the final text, no tool call after it** (a checklist printed above a blocking question tool was observed never reaching the user). Template `templates/deploy/PROCEDURE.md` restyled to match. - `/deploy` checklist reshaped on first-real-run feedback, in two passes: runbook steps are **one command per line, interactive-session style** (an early step opens the ssh session; later lines run on the box; local steps say "from your machine") instead of folded `ssh host "cd … && …"` one-liners — step = comment header + command lines up to the next blank line, a `@delta:` directive governs the whole block; and the checklist is now **display-only** — `NEXT.sh` is no longer written at all (throwaway artifact; `PENDING.json` + the live runbook regenerate it in any session) and every hand-back **ends the turn with the full checklist as the final text, no tool call after it** (a checklist printed above a blocking question tool was observed never reaching the user). Template `templates/deploy/PROCEDURE.md` restyled to match.
- `settings.json`: `inputNeededNotifEnabled: true` adopted (harness notification toggle); committed layout otherwise unchanged. - `settings.json`: `inputNeededNotifEnabled: true` adopted (harness notification toggle); committed layout otherwise unchanged.
+301
View File
@@ -0,0 +1,301 @@
<!-- USER-SCOPE GLOBAL memory — deployed as ~/.claude/CLAUDE.md via link.sh.
Repo-specific instructions live in ./CLAUDE.md (project scope). -->
# Global coding preferences
Apply unless repo-specific instructions override.
## Code style
- Simple, readable, maintainable > clever or compact.
- One responsibility per function/method.
- Preserve existing behavior unless asked.
- Scope changes to task — no unrelated edits.
## Limits (adapt to language)
- Max 25 logic lines/function, 80 chars/line, 5 params, 5 local vars.
Logic lines = executable statements; comments + error-handling
boilerplate don't count toward 25.
- Too many params → struct/object. Too many vars → split/extract.
- No global state. Explicit data flow.
## Comments & readability
- Document intent, not mechanics. Use project doc style (docstring, JSDoc…).
- Explicit, consistent, meaningful names. Straight control flow,
no hidden side effects.
## Refactoring
- Priority: safety → readability → consistency.
- Remove dead code, stale comments, obsolete flags after changes.
- Non-trivial change: ask "more elegant solution exists?"
Hacky fix → rebuild clean, no over-engineering.
## Session start
1. Read `.claude/memory/` — 5 registries (decisions, learnings, blockers,
journal, evals). Apply before touching anything.
2. Read `.claude/tasks/TODO.md` — current state.
3. Either missing → create before starting
(templates: `~/.claude/templates/memory/`).
## Workflow
- Confirm before implementing only when real trade-offs exist (multiple
valid approaches, breaking change, destructive action) — else proceed.
- Minimal changes unless broader refactor requested. State trade-offs.
- Sub-agents keep main context clean — one task per sub-agent.
More compute on hard problems. Task fans out across independent
items (many files, parallel searches, multi-point checks) → delegate
to sub-agents, don't iterate serially. Default to delegation for
multi-file exploration. Counters model tendency to under-delegate.
- One question upfront if needed — don't interrupt mid-task.
*Exception: skill-mandated gates and checkpoints (orchestrator
validation gates, approval gates, darwin checkpoints) always fire.*
- Bug received → fix directly: check logs, find root cause, resolve
autonomously.
- Something goes wrong → STOP, re-plan. Never push through.
- Deviations: minor or clearly justified → do, explain after.
Significant or shaky justification → ask before deviating.
- Root causes only. No temp fixes. Never assume — verify paths, APIs,
variables before use.
## Planning & TODO (`.claude/tasks/TODO.md`)
- When to plan: task touches logic (new behavior, control flow, state,
API, dependencies) → write it in `.claude/tasks/TODO.md` first,
decomposed into subtasks. One complex task still needs a plan.
Borderline case (single file, small obvious logic change) → skip plan,
stay pragmatic.
- Exempt (skip TODO.md): pure reads, explanations, questions, typos,
cosmetic CSS, single config-value change. Same scope as `/hotfix`
(≤2 files, obvious fix).
- How to track, once a task qualifies:
1. Plan → task written before code.
2. Decompose → one subtask = one coherent change.
3. Track → check off as you go.
4. Summarize → high-level note at each milestone.
## After code changes
1. Run tests, lint, build, type-check if available.
2. Report what verified, what not.
3. List remaining risks, surviving deviations.
4. Don't mark complete without proof it works.
Bar: "would staff engineer approve?"
5. Correction or notable event → capitalize to right registry
(see "Memory registries").
## Memory registries (`.claude/memory/`)
Five registries persist across sessions. Capitalize during/after work.
Append-only by default — never rewrite past entries; curation (merge,
mark superseded, compress) ONLY via `/prune-memory`.
| File | ID format | Purpose |
|------|-----------|---------|
| `decisions.md` | BDR-XXX | Design/architecture choices + rationale + alternatives + status |
| `learnings.md` | LRN-XXX | Reusable patterns + context + future application |
| `blockers.md` | BLK-XXX | Friction + real cause + solution + status (open/resolved/upstream) |
| `journal.md` | date heading | 3-5 lines/session — done, decided, blocked |
| `evals.md` | EVAL-XXX | Quality check of Claude's output + method + anomalies + action |
**Language — registries always English.** Rationale: consistent vocab,
lower token cost, cross-project reuse. User-facing CAPITALIZE prompts may
mirror user's language; final written entry English.
**Format — registries always caveman.** Drop articles + filler, fragments
OK, short synonyms. Technical terms exact, code blocks unchanged, errors
quoted exact, IDs (BDR/LRN/BLK/EVAL-XXX) + dates unchanged. Pattern:
`[thing] [action] [reason]. [next step].` Rationale: registries load
every session — caveman cuts ~40% input tokens, zero substance loss.
Applies to direct writes AND skill CAPITALIZE steps (close, ship-feature,
feat, bugfix, hotfix, commit-change). Legacy entries (pre-format-rule):
compress manually or via claude.ai on demand.
**Routing — what goes where:**
- Choice with tradeoffs you'd defend → `decisions.md`.
- Pattern worth reusing → `learnings.md`.
- Dead end with root cause identified → `blockers.md`.
- One-line log of session → `journal.md`.
- Did Claude's output actually work? → `evals.md`.
**Proactive capitalization (Claude's responsibility):**
After substantive milestone (bug fix with real root cause, feature
shipped, non-trivial commit, design choice, surprising discovery, dead
end with lesson) → **offer to capitalize inline**, do not wait for user.
Pre-fill entry from context; user approves/edits before write.
Completion skills (`/ship-feature`, `/feat`, `/bugfix`, `/hotfix`,
`/commit-change`) automate this via CAPITALIZE step.
**Session-close ritual** (`/close` = `/capitalize --ritual`, or inline when asked):
1. What decided? → `decisions.md` (if non-trivial).
2. What learned? → `learnings.md` (if reusable).
3. What blocked? → `blockers.md`.
# Architecture decisions
Override default framework/tooling choices. Apply at project creation,
scaffolding, brainstorming.
## Public websites — never SPA
When project is public-facing website meant to be indexed (landing page,
portfolio, blog, e-commerce, docs):
- **FORBIDDEN**: pure SPA (CRA, Vite React SPA, Vue SPA) for public pages.
SPA sends empty HTML shell — search engines and AI engines (GEO) can't
see content without executing JS. SEO and AI visibility destroyed.
- **Astro** = default for informational sites (portfolio, docs, blog,
landing). Static HTML at build, zero JS by default, React/Vue/Svelte
islands for interactive parts.
- **Next.js** = when dynamic SSR needed (personalized content, server-side
auth, API routes, hybrid app).
- **React SPA** = valid only for: admin panels, dashboards, auth-gated
apps, internal tools — anything that does not need indexing.
- **Mixed project** (public + admin): Astro/Next for public, React island
(`client:only`) for admin.
- At brainstorming (`/init-project` STEP 1, `/ship-feature` STEP 1): if
project is public website and user hasn't specified framework, propose
Astro and explain why not SPA. Never silently pick React CRA.
## Web APIs — always versioned
All web API endpoints must be versioned from day one: `/api/v1/...`.
- New project → start at `/api/v1/`, no bare `/api/` routes.
- Breaking changes → new version (`v2`). Old version stays functional —
clients migrate at own pace.
- Non-breaking additions (new fields, new endpoints) → current version.
- Each version is self-contained contract. Don't modify existing version
behavior to match newer one.
- Router structure reflects versioning explicitly (e.g. `api/v1/routes/`).
## Version control — gitflow (universal)
Every git action follows gitflow — in a skill, or an ad-hoc commit made outside
one on request. `main` (prod) · `develop` (integration, off main) · `feature/*`
`bugfix/*` + `chore/*` (off develop → develop; `chore/*` = memory/doc
maintenance, e.g. standalone `/capitalize` `/close` `/prune-memory`
`/reconcile`) · `release/*` (off develop → main + back-merge develop) ·
`hotfix/*` (off main → main + develop [+ any open release/*]). `master`→`main`
everywhere.
Never commit code directly on `main` or `develop`: branch first from the
correct base as `<type>/<name>` (`.claude/**` memory/config commits are
hook-exempt, following the work). Branch/merge only via the lib, never by hand:
`bash ~/.claude/lib/gitflow.sh start <type> <name>` · `… finish`. Run `finish`
(merge) only on an explicit human signal ("merge it", "feature OK"), never
because tests pass, a plan step says "merge", or "ship" implied it. Assistance
flows (`/feat` `/bugfix` `/hotfix`) and the standalone memory/doc `chore`
skills auto-branch on a protected base but commit in place on a working branch,
never finishing — so those skills branch to `chore/*` via the aiguillage, not
the `.claude/**` exemption. New/onboarded projects get the model + the
versioned pre-commit hook via `gitflow init`. Advisory, so two deterministic
backstops apply: the per-repo pre-commit hook (blocks code commits on
main/develop, exempts `.claude/**` + merges + the root commit) and Gitea branch
protection on `main`/`develop`. Don't lean on `--no-verify` to bypass them.
## Security — non-negotiable defaults
Apply at every dev step: design, scaffolding, implementation, review.
### Input & data
- Never trust user input. Validate type, length, format, range before use.
- Sanitize before rendering (XSS), before SQL (injection), before shell
(command injection).
- Use parameterized queries / prepared statements. String concatenation
into SQL = immediate blocker.
### Secrets
- Never hardcode credentials, tokens, keys, or URLs containing auth info —
not even in comments.
- Always use env vars. Provide `.env.example` with placeholder values only.
- If secret appears in code during review, flag and stop — do not proceed.
### Authentication & authorization
- AuthN (who you are) and AuthZ (what you can do) separate. Never assume
AuthN implies AuthZ.
- Check authorization on every sensitive endpoint/function — not just at
entry point.
- Default to deny. Explicit allowlist > implicit denylist.
### Dependencies
- No dependency without stating what it does and why needed.
- Prefer well-maintained, widely-used packages. Flag abandoned or
single-maintainer packages.
- Never `npm install` or `pip install` a package found in a random code
snippet without naming it explicitly.
### Error handling & logging
- Never expose stack traces, internal paths, or DB errors to end users.
Log internally, return generic message.
- Never log secrets, passwords, tokens, or PII — even at DEBUG level.
- Fail closed: on unexpected error, deny access rather than grant.
### Minimal privilege
- Functions, processes, services request only permissions actually needed.
- Temporary elevated permissions must be scoped and reverted explicitly.
# Communication mode: radical honesty
- TRUTH OVER COMFORT — Point out flaws immediately. No sugarcoating,
no "not bad but…".
- ZERO COMPLACENCY — Never validate idea just because I proposed it.
Evaluate arguments on merit.
- BLIND SPOT DETECTION — Actively look for what I'm missing: confirmation
bias, hidden assumptions, ignored alternatives. Flag without waiting
for permission.
- ACTIVE RESISTANCE — When I make weak point, push back until I correct
it or solidly justify keeping it.
- UNCERTAINTY TRANSPARENCY — If you don't know, say so. No invention,
no vague answers to save face.
# Tooling & skills
## Skill routing
Most skills route by name — match the request to the skill whose
description fits (full list is in context). Rules below cover only the
non-obvious cases: gstack fallbacks, disambiguation, cryptic names.
- Product idea, "worth building?" → office-hours
- Bug / error / 500 → investigate (bugfix if gstack off)
- feat / hotfix / bugfix distinguished by file count → see descriptions
- Ship / deploy / PR → ship (ship-feature if gstack off)
- Cut a release / tag a version (develop ahead of main) → release-candidate
- Docs post-ship → document-release (doc if gstack off); stale-doc audit → doc
- Audit of changes since last run → audit-delta
- Grouped all-axes sweep (clean+security+reconcile+doc, "tir groupé",
tour of one or more projects, fix + loop until clean) → tour
- Open-work inventory / "queue empty?" / stale TODO vs real git → reconcile
- Design / UI (build, system, audit, polish) → see "Design work" below
- Architecture review → plan-eng-review
- Before /clear or /compact → capitalize; end-of-session ritual → close
- SEO+GEO → seo (GEO only → geo)
- W3C + WCAG a11y (HTML/CSS validity, axe, pa11y) → web-validate
- Security audit (secrets, CVE, OWASP) → cso
- New project → init-project; onboard existing repo → onboard
gstack OFF → its skills (investigate, ship, qa, review, health, retro,
office-hours, context-save…) are gone: use the fallback above, else say so.
## Design work — full toolchain (tiered by scope)
Trigger = UI work: editing a component/style file (.tsx/.vue/.svelte/.css…)
OR a design/UI request — not the keyword "design" alone in a prompt. Single
source for design routing; the design-toolchain hook reinforces it.
- Trivial (≤2 files, one cosmetic value) → /hotfix, no toolchain.
- Build UI (component, page, redesign) → ui-ux-pro-max + frontend-design
(anti-slop) + Magic MCP /ui + emil-design-eng (polish) +
design-motion-principles (if motion) + design-html (if static).
Post-build floor: `npx impeccable detect <files>` (45 deterministic
anti-slop rules, exit 2 = findings) when impeccable installed.
- Design system / brand → design-consultation first, then the build tools.
- Review / audit → design-review + emil-design-eng + design-motion-principles
+ /impeccable audit|critique (skill) + `impeccable detect` floor.
Scope doubt → don't silently skip: ask, or default to Build tier.
Gate: lightweight skills run `~/.claude/lib/design-gate.md`; orchestrators via
plugin-check. Magic MCP costs API calls — generation, not micro-tweaks.
## graphify
ALL rules apply only if `graphify-out/graph.json` exists — else read files
directly.
- Codebase-wide question → `graphify query`; relationships → `path A B`;
concept → `explain`. Scoped subgraph beats raw grep.
- Known file / small task → read directly, no graphify.
- `wiki/index.md` → broad-nav entry; `GRAPH_REPORT.md` → whole-architecture.
- After editing code → `graphify update .` (AST-only, free).
+25 -301
View File
@@ -1,305 +1,29 @@
# Global coding preferences <!-- PROJECT SCOPE ONLY (claude-config repo). The user-scope GLOBAL memory is
./CLAUDE.global.md, deployed as ~/.claude/CLAUDE.md by link.sh — edit
THAT file for cross-project doctrine. -->
Apply unless repo-specific instructions override. # claude-config — project instructions
## Code style
- Simple, readable, maintainable > clever or compact.
- One responsibility per function/method.
- Preserve existing behavior unless asked.
- Scope changes to task — no unrelated edits.
## Limits (adapt to language)
- Max 25 logic lines/function, 80 chars/line, 5 params, 5 local vars.
Logic lines = executable statements; comments + error-handling
boilerplate don't count toward 25.
- Too many params → struct/object. Too many vars → split/extract.
- No global state. Explicit data flow.
## Comments & readability
- Document intent, not mechanics. Use project doc style (docstring, JSDoc…).
- Explicit, consistent, meaningful names. Straight control flow,
no hidden side effects.
## Refactoring
- Priority: safety → readability → consistency.
- Remove dead code, stale comments, obsolete flags after changes.
- Non-trivial change: ask "more elegant solution exists?"
Hacky fix → rebuild clean, no over-engineering.
## Session start
1. Read `.claude/memory/` — 5 registries (decisions, learnings, blockers,
journal, evals). Apply before touching anything.
2. Read `.claude/tasks/TODO.md` — current state.
3. Either missing → create before starting
(templates: `~/.claude/templates/memory/`).
## Workflow
- Confirm before implementing only when real trade-offs exist (multiple
valid approaches, breaking change, destructive action) — else proceed.
- Minimal changes unless broader refactor requested. State trade-offs.
- Sub-agents keep main context clean — one task per sub-agent.
More compute on hard problems. Task fans out across independent
items (many files, parallel searches, multi-point checks) → delegate
to sub-agents, don't iterate serially. Default to delegation for
multi-file exploration. Counters model tendency to under-delegate.
- One question upfront if needed — don't interrupt mid-task.
*Exception: skill-mandated gates and checkpoints (orchestrator
validation gates, approval gates, darwin checkpoints) always fire.*
- Bug received → fix directly: check logs, find root cause, resolve
autonomously.
- Something goes wrong → STOP, re-plan. Never push through.
- Deviations: minor or clearly justified → do, explain after.
Significant or shaky justification → ask before deviating.
- Root causes only. No temp fixes. Never assume — verify paths, APIs,
variables before use.
## Planning & TODO (`.claude/tasks/TODO.md`)
- When to plan: task touches logic (new behavior, control flow, state,
API, dependencies) → write it in `.claude/tasks/TODO.md` first,
decomposed into subtasks. One complex task still needs a plan.
Borderline case (single file, small obvious logic change) → skip plan,
stay pragmatic.
- Exempt (skip TODO.md): pure reads, explanations, questions, typos,
cosmetic CSS, single config-value change. Same scope as `/hotfix`
(≤2 files, obvious fix).
- How to track, once a task qualifies:
1. Plan → task written before code.
2. Decompose → one subtask = one coherent change.
3. Track → check off as you go.
4. Summarize → high-level note at each milestone.
## After code changes
1. Run tests, lint, build, type-check if available.
2. Report what verified, what not.
3. List remaining risks, surviving deviations.
4. Don't mark complete without proof it works.
Bar: "would staff engineer approve?"
5. Correction or notable event → capitalize to right registry
(see "Memory registries").
## Memory registries (`.claude/memory/`)
Five registries persist across sessions. Capitalize during/after work.
Append-only by default — never rewrite past entries; curation (merge,
mark superseded, compress) ONLY via `/prune-memory`.
| File | ID format | Purpose |
|------|-----------|---------|
| `decisions.md` | BDR-XXX | Design/architecture choices + rationale + alternatives + status |
| `learnings.md` | LRN-XXX | Reusable patterns + context + future application |
| `blockers.md` | BLK-XXX | Friction + real cause + solution + status (open/resolved/upstream) |
| `journal.md` | date heading | 3-5 lines/session — done, decided, blocked |
| `evals.md` | EVAL-XXX | Quality check of Claude's output + method + anomalies + action |
**Language — registries always English.** Rationale: consistent vocab,
lower token cost, cross-project reuse. User-facing CAPITALIZE prompts may
mirror user's language; final written entry English.
**Format — registries always caveman.** Drop articles + filler, fragments
OK, short synonyms. Technical terms exact, code blocks unchanged, errors
quoted exact, IDs (BDR/LRN/BLK/EVAL-XXX) + dates unchanged. Pattern:
`[thing] [action] [reason]. [next step].` Rationale: registries load
every session — caveman cuts ~40% input tokens, zero substance loss.
Applies to direct writes AND skill CAPITALIZE steps (close, ship-feature,
feat, bugfix, hotfix, commit-change). Legacy entries (pre-format-rule):
compress manually or via claude.ai on demand.
**Routing — what goes where:**
- Choice with tradeoffs you'd defend → `decisions.md`.
- Pattern worth reusing → `learnings.md`.
- Dead end with root cause identified → `blockers.md`.
- One-line log of session → `journal.md`.
- Did Claude's output actually work? → `evals.md`.
**Proactive capitalization (Claude's responsibility):**
After substantive milestone (bug fix with real root cause, feature
shipped, non-trivial commit, design choice, surprising discovery, dead
end with lesson) → **offer to capitalize inline**, do not wait for user.
Pre-fill entry from context; user approves/edits before write.
Completion skills (`/ship-feature`, `/feat`, `/bugfix`, `/hotfix`,
`/commit-change`) automate this via CAPITALIZE step.
**Session-close ritual** (`/close` = `/capitalize --ritual`, or inline when asked):
1. What decided? → `decisions.md` (if non-trivial).
2. What learned? → `learnings.md` (if reusable).
3. What blocked? → `blockers.md`.
# Architecture decisions
Override default framework/tooling choices. Apply at project creation,
scaffolding, brainstorming.
## Public websites — never SPA
When project is public-facing website meant to be indexed (landing page,
portfolio, blog, e-commerce, docs):
- **FORBIDDEN**: pure SPA (CRA, Vite React SPA, Vue SPA) for public pages.
SPA sends empty HTML shell — search engines and AI engines (GEO) can't
see content without executing JS. SEO and AI visibility destroyed.
- **Astro** = default for informational sites (portfolio, docs, blog,
landing). Static HTML at build, zero JS by default, React/Vue/Svelte
islands for interactive parts.
- **Next.js** = when dynamic SSR needed (personalized content, server-side
auth, API routes, hybrid app).
- **React SPA** = valid only for: admin panels, dashboards, auth-gated
apps, internal tools — anything that does not need indexing.
- **Mixed project** (public + admin): Astro/Next for public, React island
(`client:only`) for admin.
- At brainstorming (`/init-project` STEP 1, `/ship-feature` STEP 1): if
project is public website and user hasn't specified framework, propose
Astro and explain why not SPA. Never silently pick React CRA.
## Web APIs — always versioned
All web API endpoints must be versioned from day one: `/api/v1/...`.
- New project → start at `/api/v1/`, no bare `/api/` routes.
- Breaking changes → new version (`v2`). Old version stays functional —
clients migrate at own pace.
- Non-breaking additions (new fields, new endpoints) → current version.
- Each version is self-contained contract. Don't modify existing version
behavior to match newer one.
- Router structure reflects versioning explicitly (e.g. `api/v1/routes/`).
## Version control — gitflow (universal)
Every git action follows gitflow — in a skill, or an ad-hoc commit made outside
one on request. `main` (prod) · `develop` (integration, off main) · `feature/*`
`bugfix/*` + `chore/*` (off develop → develop; `chore/*` = memory/doc
maintenance, e.g. standalone `/capitalize` `/close` `/prune-memory`
`/reconcile`) · `release/*` (off develop → main + back-merge develop) ·
`hotfix/*` (off main → main + develop [+ any open release/*]). `master`→`main`
everywhere.
Never commit code directly on `main` or `develop`: branch first from the
correct base as `<type>/<name>` (`.claude/**` memory/config commits are
hook-exempt, following the work). Branch/merge only via the lib, never by hand:
`bash ~/.claude/lib/gitflow.sh start <type> <name>` · `… finish`. Run `finish`
(merge) only on an explicit human signal ("merge it", "feature OK"), never
because tests pass, a plan step says "merge", or "ship" implied it. Assistance
flows (`/feat` `/bugfix` `/hotfix`) and the standalone memory/doc `chore`
skills auto-branch on a protected base but commit in place on a working branch,
never finishing — so those skills branch to `chore/*` via the aiguillage, not
the `.claude/**` exemption. New/onboarded projects get the model + the
versioned pre-commit hook via `gitflow init`. Advisory, so two deterministic
backstops apply: the per-repo pre-commit hook (blocks code commits on
main/develop, exempts `.claude/**` + merges + the root commit) and Gitea branch
protection on `main`/`develop`. Don't lean on `--no-verify` to bypass them.
## Security — non-negotiable defaults
Apply at every dev step: design, scaffolding, implementation, review.
### Input & data
- Never trust user input. Validate type, length, format, range before use.
- Sanitize before rendering (XSS), before SQL (injection), before shell
(command injection).
- Use parameterized queries / prepared statements. String concatenation
into SQL = immediate blocker.
### Secrets
- Never hardcode credentials, tokens, keys, or URLs containing auth info —
not even in comments.
- Always use env vars. Provide `.env.example` with placeholder values only.
- If secret appears in code during review, flag and stop — do not proceed.
### Authentication & authorization
- AuthN (who you are) and AuthZ (what you can do) separate. Never assume
AuthN implies AuthZ.
- Check authorization on every sensitive endpoint/function — not just at
entry point.
- Default to deny. Explicit allowlist > implicit denylist.
### Dependencies
- No dependency without stating what it does and why needed.
- Prefer well-maintained, widely-used packages. Flag abandoned or
single-maintainer packages.
- Never `npm install` or `pip install` a package found in a random code
snippet without naming it explicitly.
### Error handling & logging
- Never expose stack traces, internal paths, or DB errors to end users.
Log internally, return generic message.
- Never log secrets, passwords, tokens, or PII — even at DEBUG level.
- Fail closed: on unexpected error, deny access rather than grant.
### Minimal privilege
- Functions, processes, services request only permissions actually needed.
- Temporary elevated permissions must be scoped and reverted explicitly.
# Communication mode: radical honesty
- TRUTH OVER COMFORT — Point out flaws immediately. No sugarcoating,
no "not bad but…".
- ZERO COMPLACENCY — Never validate idea just because I proposed it.
Evaluate arguments on merit.
- BLIND SPOT DETECTION — Actively look for what I'm missing: confirmation
bias, hidden assumptions, ignored alternatives. Flag without waiting
for permission.
- ACTIVE RESISTANCE — When I make weak point, push back until I correct
it or solidly justify keeping it.
- UNCERTAINTY TRANSPARENCY — If you don't know, say so. No invention,
no vague answers to save face.
# Tooling & skills
## Skill routing
Most skills route by name — match the request to the skill whose
description fits (full list is in context). Rules below cover only the
non-obvious cases: gstack fallbacks, disambiguation, cryptic names.
- Product idea, "worth building?" → office-hours
- Bug / error / 500 → investigate (bugfix if gstack off)
- feat / hotfix / bugfix distinguished by file count → see descriptions
- Ship / deploy / PR → ship (ship-feature if gstack off)
- Cut a release / tag a version (develop ahead of main) → release-candidate
- Docs post-ship → document-release (doc if gstack off); stale-doc audit → doc
- Audit of changes since last run → audit-delta
- Grouped all-axes sweep (clean+security+reconcile+doc, "tir groupé",
tour of one or more projects, fix + loop until clean) → tour
- Open-work inventory / "queue empty?" / stale TODO vs real git → reconcile
- Design / UI (build, system, audit, polish) → see "Design work" below
- Architecture review → plan-eng-review
- Before /clear or /compact → capitalize; end-of-session ritual → close
- SEO+GEO → seo (GEO only → geo)
- W3C + WCAG a11y (HTML/CSS validity, axe, pa11y) → web-validate
- Security audit (secrets, CVE, OWASP) → cso
- New project → init-project; onboard existing repo → onboard
gstack OFF → its skills (investigate, ship, qa, review, health, retro,
office-hours, context-save…) are gone: use the fallback above, else say so.
## Design work — full toolchain (tiered by scope)
Trigger = UI work: editing a component/style file (.tsx/.vue/.svelte/.css…)
OR a design/UI request — not the keyword "design" alone in a prompt. Single
source for design routing; the design-toolchain hook reinforces it.
- Trivial (≤2 files, one cosmetic value) → /hotfix, no toolchain.
- Build UI (component, page, redesign) → ui-ux-pro-max + frontend-design
(anti-slop) + Magic MCP /ui + emil-design-eng (polish) +
design-motion-principles (if motion) + design-html (if static).
Post-build floor: `npx impeccable detect <files>` (45 deterministic
anti-slop rules, exit 2 = findings) when impeccable installed.
- Design system / brand → design-consultation first, then the build tools.
- Review / audit → design-review + emil-design-eng + design-motion-principles
+ /impeccable audit|critique (skill) + `impeccable detect` floor.
Scope doubt → don't silently skip: ask, or default to Build tier.
Gate: lightweight skills run `~/.claude/lib/design-gate.md`; orchestrators via
plugin-check. Magic MCP costs API calls — generation, not micro-tweaks.
## graphify
ALL rules apply only if `graphify-out/graph.json` exists — else read files
directly.
- Codebase-wide question → `graphify query`; relationships → `path A B`;
concept → `explain`. Scoped subgraph beats raw grep.
- Known file / small task → read directly, no graphify.
- `wiki/index.md` → broad-nav entry; `GRAPH_REPORT.md` → whole-architecture.
- After editing code → `graphify update .` (AST-only, free).
# This repo only (claude-config)
Apply when working directory = the claude-config repo itself.
## Health Stack ## Health Stack
- shell: `shellcheck *.sh hooks/*.sh lib/*.sh` - shell: `shellcheck *.sh hooks/*.sh lib/*.sh`
## rules/ maintenance
Modular instruction files loaded by Claude Code alongside the global memory.
`rules/` is symlinked to `~/.claude/rules` by `link.sh` (user scope, ALL
projects). One rule = one file = one concern.
A rule WITH `paths:` YAML frontmatter (glob list) loads lazily — only when
Claude reads a file matching a glob; a rule WITHOUT it loads at session
start, same cost as the global memory. Extract from CLAUDE.global.md only
what can be path-scoped (the token win) or what is generated; always-on
doctrine stays in CLAUDE.global.md. `paths:` globs match against the
CURRENT project's tree — a broad glob (e.g. `rules/**`) can fire in foreign
projects; keep rule bodies tiny.
Docs: https://code.claude.com/docs/en/memory.md#path-specific-rules
Machine-owned: `rules/context7.md` is DELETED BY DESIGN (BDR-053,
2026-07-06) — `ctx7 setup --claude --cli` still writes it, but
install-plugins.sh STEP ctx7 purges it right after; the find-docs skill is
the single ctx7 surface. If it reappears (manual `ctx7 setup`), delete it
or re-run `make plugin`.
+2 -1
View File
@@ -13,7 +13,8 @@ This repo is your personal Claude Code setup, versioned and reproducible across
``` ```
claude-config/ claude-config/
├── CLAUDE.md # Global coding preferences (style, rules, workflow) ├── CLAUDE.global.md # Global coding preferences — deployed as ~/.claude/CLAUDE.md
├── CLAUDE.md # Project-scope instructions (this repo only)
├── settings.json # Global permissions (deny / ask / allow rules) ├── settings.json # Global permissions (deny / ask / allow rules)
├── install.sh # Bootstrap: Claude Code CLI + auth + submodules + link + plugins ├── install.sh # Bootstrap: Claude Code CLI + auth + submodules + link + plugins
├── install-plugins.sh # One-shot installer: prerequisites + all plugins ├── install-plugins.sh # One-shot installer: prerequisites + all plugins
@@ -0,0 +1,365 @@
# CLAUDE.global.md Rename Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Rename the repo-root global memory to `CLAUDE.global.md` and free the `CLAUDE.md` name for a real project-scope file, following every dependent script and doc.
**Architecture:** One atomic file-split task (git mv + both file contents + link.sh retarget, so no commit leaves the tree incoherent), then a script-followers task, then docs, then a verification sweep. Spec: `docs/superpowers/specs/2026-07-12-claude-global-md-rename-design.md`. Contract: `.claude/tasks/contracts/2026-07-12-claude-global-md-rename-2342.md`.
**Tech Stack:** bash (shellcheck-clean), markdown, git (gitflow via `~/.claude/lib/gitflow.sh`).
## Global Constraints
- BDR-062: line-count guard threshold stays **320**; only its path changes.
- BDR-021: `## Security`, `# Architecture decisions` content and the heading `## Design work — full toolchain (tiered by scope)` stay **byte-identical** in CLAUDE.global.md.
- BDR-031: global file must NOT grow — expected net: 305 → 301 lines (−7 tail section incl. leading blank, +3 header incl. trailing blank).
- LRN-044: edit repo paths only (`/home/bchanot/Documents/claude/...`), never through `~/.claude/CLAUDE.md`.
- Gitflow: every commit lands on `feature/claude-global-md-rename` (created Task 1). Never commit on develop.
- Shell code: ≤80 cols, shellcheck-clean (`shellcheck *.sh hooks/*.sh lib/*.sh`).
- Deployed-state note: between Task 2's `git mv` and its `bash link.sh` step, `~/.claude/CLAUDE.md` dangles — Task 2 must run to completion without interruption.
---
### Task 1: Feature branch + commit spec and plan
**Files:**
- Commit: `docs/superpowers/specs/2026-07-12-claude-global-md-rename-design.md` (exists)
- Commit: `docs/superpowers/plans/2026-07-13-claude-global-md-rename.md` (this file)
**Interfaces:**
- Produces: branch `feature/claude-global-md-rename` off develop — all later tasks commit here.
- [ ] **Step 1: Create the branch via the gitflow lib (never by hand)**
Run: `bash "$HOME/.claude/lib/gitflow.sh" start feature claude-global-md-rename`
Expected: branch created off develop; `git branch --show-current` → `feature/claude-global-md-rename`
- [ ] **Step 2: Commit the two docs**
```bash
git add docs/superpowers/specs/2026-07-12-claude-global-md-rename-design.md \
docs/superpowers/plans/2026-07-13-claude-global-md-rename.md
git commit -m "docs(spec): CLAUDE.global.md rename — design + implementation plan"
```
Expected: clean commit; `git status` shows the two files gone from untracked.
NOTE: `settings.json` is dirty with session-scoped plugin toggles — do NOT stage it in any task.
---
### Task 2: Atomic file split (git mv + contents + link.sh)
**Files:**
- Rename: `CLAUDE.md` → `CLAUDE.global.md` (git mv)
- Modify: `CLAUDE.global.md` (add header, drop tail section)
- Create: `CLAUDE.md` (project scope, new content below)
- Modify: `link.sh:20`
**Interfaces:**
- Produces: `CLAUDE.global.md` = global memory (Task 3 scripts point here); `CLAUDE.md` = project memory (stays graphify's / GUARDED_CONFIGS' target name).
- [ ] **Step 1: git mv**
```bash
cd /home/bchanot/Documents/claude
git mv CLAUDE.md CLAUDE.global.md
```
- [ ] **Step 2: Add the scope header to CLAUDE.global.md**
Insert at the very top, ABOVE `# Global coding preferences`:
```markdown
<!-- USER-SCOPE GLOBAL memory — deployed as ~/.claude/CLAUDE.md via link.sh.
Repo-specific instructions live in ./CLAUDE.md (project scope). -->
```
- [ ] **Step 3: Remove the repo-only tail section from CLAUDE.global.md**
Delete exactly these final lines (and the blank line before `# This repo only`):
```markdown
# This repo only (claude-config)
Apply when working directory = the claude-config repo itself.
## Health Stack
- shell: `shellcheck *.sh hooks/*.sh lib/*.sh`
```
The file now ends with the graphify section's last line:
`- After editing code → \`graphify update .\` (AST-only, free).`
- [ ] **Step 4: Create the project-scope CLAUDE.md**
Full content:
```markdown
<!-- PROJECT SCOPE ONLY (claude-config repo). The user-scope GLOBAL memory is
./CLAUDE.global.md, deployed as ~/.claude/CLAUDE.md by link.sh — edit
THAT file for cross-project doctrine. -->
# claude-config — project instructions
## Health Stack
- shell: `shellcheck *.sh hooks/*.sh lib/*.sh`
## rules/ maintenance
Modular instruction files loaded by Claude Code alongside the global memory.
`rules/` is symlinked to `~/.claude/rules` by `link.sh` (user scope, ALL
projects). One rule = one file = one concern.
A rule WITH `paths:` YAML frontmatter (glob list) loads lazily — only when
Claude reads a file matching a glob; a rule WITHOUT it loads at session
start, same cost as the global memory. Extract from CLAUDE.global.md only
what can be path-scoped (the token win) or what is generated; always-on
doctrine stays in CLAUDE.global.md. `paths:` globs match against the
CURRENT project's tree — a broad glob (e.g. `rules/**`) can fire in foreign
projects; keep rule bodies tiny.
Docs: https://code.claude.com/docs/en/memory.md#path-specific-rules
Machine-owned: `rules/context7.md` is DELETED BY DESIGN (BDR-053,
2026-07-06) — `ctx7 setup --claude --cli` still writes it, but
install-plugins.sh STEP ctx7 purges it right after; the find-docs skill is
the single ctx7 surface. If it reappears (manual `ctx7 setup`), delete it
or re-run `make plugin`.
```
- [ ] **Step 5: Retarget link.sh**
`link.sh:20` — change:
```bash
link_file "$REPO/CLAUDE.md" "$CLAUDE/CLAUDE.md"
```
to:
```bash
link_file "$REPO/CLAUDE.global.md" "$CLAUDE/CLAUDE.md"
```
- [ ] **Step 6: Run link.sh and verify the symlink**
```bash
bash link.sh
readlink "$HOME/.claude/CLAUDE.md"
```
Expected: `✅ 1 symlink(s) updated in ~/.claude/` (ln -sf replaces the stale
link) and readlink prints `/home/bchanot/Documents/claude/CLAUDE.global.md`.
- [ ] **Step 7: Stage, then verify sizes and byte-identity of protected content**
```bash
wc -l CLAUDE.global.md # expected: 301 (≤ 320, BDR-062 margin)
git add CLAUDE.global.md CLAUDE.md link.sh
git diff -M --cached --stat # rename CLAUDE.md→CLAUDE.global.md + new CLAUDE.md + link.sh
git diff -M --cached -- CLAUDE.global.md
```
Expected: rename detected (similarity ~97%); exactly TWO hunks on
CLAUDE.global.md (header insertion at top, tail-section deletion) — nothing
else. `## Security`, `# Architecture decisions`, `## Design work — full
toolchain (tiered by scope)` untouched.
- [ ] **Step 8: shellcheck + commit**
```bash
shellcheck link.sh
git commit -m "feat(memory): split user-scope global (CLAUDE.global.md) from project CLAUDE.md"
```
---
### Task 3: Point dependent scripts at CLAUDE.global.md
**Files:**
- Modify: `hooks/session-start.sh:202-211`
- Modify: `doctor.sh:244,251,277`
- Modify: `install-plugins.sh:33-41,65-68`
- Modify: `lib/doc-commit.sh:44-50`
**Interfaces:**
- Consumes: `CLAUDE.global.md` from Task 2.
- Produces: guard/stats/exclusions used by Task 5's verification sweep.
- [ ] **Step 1: session-start.sh — line-count guard follows the file (BDR-062)**
Replace lines 202-211:
```bash
# CLAUDE.global.md line-count guard (anti-regression). BDR-062 supersedes
# BDR-031's 275 target: 305 is the assumed reality (extraction done at
# job1; further compression costs clarity > token gain) — warn past 320.
if [ -n "$REPO_DIR" ] && [ -f "$REPO_DIR/CLAUDE.global.md" ]; then
_claude_lines=$(wc -l < "$REPO_DIR/CLAUDE.global.md")
if [ "$_claude_lines" -gt 320 ]; then
_cmd_warn="CLAUDE.global.md ${_claude_lines}L (>320) — density pass"
printf "│ ⚠️ %-44s│\n" "${_cmd_warn:0:44}"
unset _cmd_warn
fi
unset _claude_lines
fi
```
(Behavior guard: without this change the check would silently measure the
NEW 30-line project CLAUDE.md and never warn again — fail-open.)
- [ ] **Step 2: doctor.sh — stats read the global file**
Line 244 comment: `# The passive footprint (CLAUDE.md + skill descriptions`
→ `# The passive footprint (CLAUDE.global.md + skill descriptions`.
Line 251:
```bash
CLAUDE_MD_CHARS=$(wc -c < "$REPO/CLAUDE.global.md" 2>/dev/null || echo 0)
```
Line 277 (keep the `~` column aligned — 4 spaces after the colon):
```bash
echo " CLAUDE.global.md: ~${CLAUDE_MD_TOKENS}t"
```
Lines 32, 52, 65 (symlink-NAME references `~/.claude/CLAUDE.md`) unchanged.
- [ ] **Step 3: install-plugins.sh — guard both memory files**
Line 36: `These 3 files` → `These 4 files`. After line 40 (`— anything the
installer should add…`), append one comment line, then replace line 41:
```bash
# CLAUDE.md = project memory (graphify's rewrite target); CLAUDE.global.md
# = user-scope global memory (deployed as ~/.claude/CLAUDE.md).
GUARDED_CONFIGS=("CLAUDE.md" "CLAUDE.global.md" ".claude/settings.json"
"settings.json")
```
Lines 65-68 err message:
```bash
err "Config guard could not be created (mktemp failed) — refusing to run" \
"unguarded: CLAUDE.md/CLAUDE.global.md/.claude/settings.json/settings.json" \
"could be silently rewritten by the installer. Fix mktemp/TMPDIR and retry."
```
- [ ] **Step 4: lib/doc-commit.sh — exclude the global file (BDR-022)**
Comment (line ~44-45): `or a CLAUDE.md (root or nested)` → `or a CLAUDE.md /
CLAUDE.global.md memory file (root or nested)`. Case patterns:
```bash
_forbidden_path() {
case "$1" in
.claude | .claude/* | */.claude/* | CLAUDE.md | */CLAUDE.md | \
CLAUDE.global.md | */CLAUDE.global.md) return 0 ;;
*) return 1 ;;
esac
}
```
- [ ] **Step 5: Lint + smoke test + commit**
```bash
shellcheck hooks/session-start.sh doctor.sh install-plugins.sh lib/doc-commit.sh
bash doctor.sh | sed -n '/Token budget/,/────/p' # shows "CLAUDE.global.md: ~Nt", N≈3600
git add hooks/session-start.sh doctor.sh install-plugins.sh lib/doc-commit.sh
git commit -m "feat(memory): guards, doctor stats and doc-commit exclusions follow CLAUDE.global.md"
```
---
### Task 4: rules/README.md pointer + README tree
**Files:**
- Rewrite: `rules/README.md`
- Modify: `README.md:16`
- [ ] **Step 1: Rewrite rules/README.md (full new content)**
```markdown
---
paths: ["rules/**"]
---
# rules/
User-scope rules, deployed to `~/.claude/rules` by `link.sh`.
Maintenance doctrine (what belongs here, lazy-load `paths:` semantics,
machine-owned files): see `CLAUDE.md` (project scope) at the repo root.
```
- [ ] **Step 2: README.md tree — show both memory files**
Replace line 16:
```
├── CLAUDE.md # Global coding preferences (style, rules, workflow)
```
with:
```
├── CLAUDE.global.md # Global coding preferences — deployed as ~/.claude/CLAUDE.md
├── CLAUDE.md # Project-scope instructions (this repo only)
```
Lines 29, 100, 152 (per-project CLAUDE.md concept) and 189 (symlink name)
unchanged. USAGE.md / MIGRATION.md / update-all.sh audited: zero references
to the repo-root global file — no edits (contract criterion 10 satisfied
by verification).
- [ ] **Step 3: Commit**
```bash
git add rules/README.md README.md
git commit -m "docs: slim rules/README to a pointer; README tree lists both memory files"
```
---
### Task 5: Verification sweep (no new code)
**Files:** none modified (fixes only if a check fails — then loop the owning task).
- [ ] **Step 1: link.sh idempotence**
Run: `bash link.sh`
Expected: `✅ All symlinks already up to date.`
- [ ] **Step 2: doctor.sh green**
Run: `bash doctor.sh`
Expected: `~/.claude/CLAUDE.md` symlink PASS (resolves inside repo); token
stats line reads `CLAUDE.global.md:`; no new FAIL vs pre-change baseline.
- [ ] **Step 3: Residual reference grep**
```bash
grep -rn '\$REPO/CLAUDE\.md\|\$REPO_DIR/CLAUDE\.md' -- *.sh hooks lib || echo CLEAN
grep -rn 'CLAUDE\.md' -- *.sh hooks/*.sh lib/*.sh
```
First: `CLEAN`. Second — every hit must be one of: link.sh `$CLAUDE/CLAUDE.md`
(dst name), session-start.sh `readlink "$HOME/.claude/CLAUDE.md"`, doctor.sh
`check_symlink "CLAUDE.md"` + name comments, install-plugins.sh
`"CLAUDE.md"` guard entry + comments, doc-commit.sh case patterns,
update-all.sh graphify comment (targets the project file — accurate).
- [ ] **Step 4: History + guard smoke**
```bash
git log --follow --oneline CLAUDE.global.md | tail -3 # pre-rename commits
awk '/line-count guard/,/^fi$/' hooks/session-start.sh | grep -c 'CLAUDE\.global\.md' # ≥ 2
wc -l CLAUDE.global.md CLAUDE.md # 301 and ~31
```
- [ ] **Step 5: Full Health Stack**
Run: `shellcheck *.sh hooks/*.sh lib/*.sh`
Expected: exit 0, no findings on modified files.
@@ -0,0 +1,125 @@
# Design — CLAUDE.global.md rename + project-scope CLAUDE.md
- Date: 2026-07-12
- Flow: /ship-feature
- Contract: `.claude/tasks/contracts/2026-07-12-claude-global-md-rename-2342.md`
- Status: approved (design gate 2026-07-12)
## Problem
The repo-root `CLAUDE.md` is the user-scope global memory, symlinked to
`~/.claude/CLAUDE.md` by `link.sh`. Because the filename `CLAUDE.md` is taken
by global content, this repo has no project-level CLAUDE.md — repo-only
instructions (`# This repo only (claude-config)`) ride the global file and
load in every project (~40 tok/session waste), and the `rules/` maintenance
doctrine lives in `rules/README.md`, a user-scope rule whose
`paths: ["rules/**"]` glob over-matches any foreign project with a `rules/`
directory.
## Decision
Rename the global source file and free the `CLAUDE.md` name for a real
project-scope file. Name arbitrated: `CLAUDE.global.md`
(`CLAUDE.prod.md` rejected — "prod" implies a deployment environment that
does not exist here).
This does NOT contradict BDR-021's rejected "split into 2 files" alternative:
that rejection targeted splitting GLOBAL content into two synced files. Here
the scopes are disjoint — zero synchronization between the two files.
## Changes
### 1. File split
- `git mv CLAUDE.md CLAUDE.global.md` (history preserved; verify with
`git log --follow`).
- `CLAUDE.global.md` drops the `# This repo only (claude-config)` section
(6 lines) and gains a scope header above the title:
```markdown
<!-- USER-SCOPE GLOBAL — deployed as ~/.claude/CLAUDE.md via link.sh symlink.
Repo-specific instructions live in ./CLAUDE.md (project scope). -->
```
Net ≈ 301 lines — under the 320 guard (BDR-062). Security, Architecture
decisions, and the "Design work — full toolchain (tiered by scope)" heading
stay byte-identical (BDR-021: hook quotes the heading verbatim).
- New project-scope `CLAUDE.md` (~25 lines), minimal structure (YAGNI — no
empty template sections):
- mirror scope header: project scope only; global doctrine is in
`CLAUDE.global.md`, deployed as `~/.claude/CLAUDE.md`; edit THAT file for
cross-project rules;
- `## Health Stack` — `shellcheck *.sh hooks/*.sh lib/*.sh` (moved verbatim);
- `## rules/ maintenance` — doctrine migrated from `rules/README.md`:
what belongs in `rules/` (one rule = one file = one concern), lazy-load
semantics (`paths:` frontmatter → loads on matching file read; no
`paths:` → session-start cost, keep always-on doctrine in
CLAUDE.global.md), machine-owned files note (context7.md DELETED BY
DESIGN, BDR-053), link to the docs page.
### 2. link.sh
Mapping line changes: link `<repo>/CLAUDE.global.md` → `~/.claude/CLAUDE.md`.
`ln -sf` in `link_file()` replaces the stale symlink cleanly. Post-condition:
`readlink ~/.claude/CLAUDE.md` = `<repo>/CLAUDE.global.md`.
### 3. Dependent scripts (surgical)
| File | Change |
|---|---|
| `hooks/session-start.sh:205-206` | line-count guard reads `CLAUDE.global.md`; threshold 320 unchanged (BDR-062). Repo detection via `readlink` (line 81) already works post-rename. |
| `doctor.sh:251,277` | size/token stats read `CLAUDE.global.md`. Symlink check (line 65) unchanged — link name `~/.claude/CLAUDE.md` is stable. |
| `install-plugins.sh:33-41,66` | `GUARDED_CONFIGS` KEEPS `"CLAUDE.md"` (graphify's installer targets that name — now the project file, still needs the drift guard) and ADDS `"CLAUDE.global.md"`. Comment + mktemp error message updated. |
| `lib/doc-commit.sh:48` | add `CLAUDE.global.md` to the doc-sync exclusion list (BDR-022 spirit: memory/config files are never doc-commit targets). |
### 4. rules/README.md
Slimmed to a 3-line pointer, frontmatter kept:
```markdown
---
paths: ["rules/**"]
---
User-scope rules, deployed to `~/.claude/rules` by `link.sh`.
Maintenance doctrine (what belongs here, lazy-load semantics, machine-owned
files): see `CLAUDE.md` (project scope) at the claude-config repo root.
```
Over-match in foreign projects with a `rules/` dir becomes harmless (~30 tok).
### 5. Docs
`README.md`, `USAGE.md`, `MIGRATION.md`: update only references meaning the
repo-root GLOBAL file. References to the `~/.claude/CLAUDE.md` symlink name
and to the per-project CLAUDE.md concept are unchanged. `templates/project-CLAUDE.md`
unchanged (its "Global rules: ~/.claude/CLAUDE.md" line stays accurate).
## Verification
1. `shellcheck` on every modified `.sh` (repo Health Stack).
2. `bash link.sh` → `readlink ~/.claude/CLAUDE.md` resolves to
`CLAUDE.global.md`; no dangling link.
3. `bash doctor.sh` → symlink check green, stats read the new file.
4. Residual grep: no script reference to the repo-root global file under the
old name (`grep -rn 'CLAUDE\.md' *.sh hooks/*.sh lib/*.sh` reviewed).
5. Session-start guard smoke test: guard finds `CLAUDE.global.md`, no
fail-open on the old path.
6. `git log --follow CLAUDE.global.md` shows pre-rename history.
## Constraints honored
- **BDR-062**: guard path follows the rename, threshold 320 untouched.
- **BDR-021**: Security/Architecture verbatim; design heading byte-identical.
- **BDR-031**: global file net-shrinks (−6 +2 lines); no re-inflation.
- **LRN-044**: all edits on resolved repo paths, never through
`~/.claude/CLAUDE.md`.
- **Gitflow**: all commits on a `feature/*` branch off develop; this spec is
committed as the branch's first commit (never directly on develop).
## Out of scope
- graphify-section extraction from the global file (separate suggestion,
not requested here).
- Any content rewrite of the global doctrine beyond the section move and
scope header.
+14 -3
View File
@@ -63,6 +63,17 @@ check_symlink() {
} }
check_symlink "CLAUDE.md" check_symlink "CLAUDE.md"
# check_symlink only asserts the canonical path lands inside $REPO — after a
# `git pull` without `link.sh`, ~/.claude/CLAUDE.md can still resolve inside
# $REPO but at the wrong file (the 29-line project CLAUDE.md instead of
# CLAUDE.global.md), passing green while the global doctrine is silently gone.
_claude_md_target=$(readlink "$HOME/.claude/CLAUDE.md" 2>/dev/null || true)
if [ "$_claude_md_target" != "$REPO/CLAUDE.global.md" ]; then
# shellcheck disable=SC2088 # literal label, not a tilde-expansion attempt
warn "~/.claude/CLAUDE.md points to $_claude_md_target — expected \
$REPO/CLAUDE.global.md; run: bash link.sh"
fi
unset _claude_md_target
check_symlink "settings.json" check_symlink "settings.json"
check_symlink "agents" check_symlink "agents"
check_symlink "skills" check_symlink "skills"
@@ -241,14 +252,14 @@ echo ""
# 6. Token budget estimate # 6. Token budget estimate
# ──────────────────────────────────────────────────────────── # ────────────────────────────────────────────────────────────
echo "── Token budget estimate ──" echo "── Token budget estimate ──"
# The passive footprint (CLAUDE.md + skill descriptions + plugin session-injects) # The passive footprint (CLAUDE.global.md + skill descriptions + plugin session-injects)
# loads into the CONTEXT WINDOW every session — it competes with the ~200k default # loads into the CONTEXT WINDOW every session — it competes with the ~200k default
# context, NOT a per-session token quota (the old "~11k/5h budget" denominator was # context, NOT a per-session token quota (the old "~11k/5h budget" denominator was
# a category error → false "92% CRITICAL", LRN-047). Measured ~11.4k post-audit # a category error → false "92% CRITICAL", LRN-047). Measured ~11.4k post-audit
# 2026-07-02 (LRN-088); the chars/4 sum below is a coarse proxy of that footprint. # 2026-07-02 (LRN-088); the chars/4 sum below is a coarse proxy of that footprint.
# Thresholds: WARNING >15% of context (~30k), CRITICAL >25% (~50k). # Thresholds: WARNING >15% of context (~30k), CRITICAL >25% (~50k).
CLAUDE_MD_CHARS=$(wc -c < "$REPO/CLAUDE.md" 2>/dev/null || echo 0) CLAUDE_MD_CHARS=$(wc -c < "$REPO/CLAUDE.global.md" 2>/dev/null || echo 0)
CLAUDE_MD_TOKENS=$((CLAUDE_MD_CHARS / 4)) CLAUDE_MD_TOKENS=$((CLAUDE_MD_CHARS / 4))
# Skill descriptions only (frontmatter description field — loaded passively at startup) # Skill descriptions only (frontmatter description field — loaded passively at startup)
@@ -274,7 +285,7 @@ CONTEXT_WINDOW=200000 # Claude Code default context window (conservative; 1M i
PCT=$((TOTAL_TOKENS * 100 / CONTEXT_WINDOW)) PCT=$((TOTAL_TOKENS * 100 / CONTEXT_WINDOW))
echo "" echo ""
echo " CLAUDE.md: ~${CLAUDE_MD_TOKENS}t" echo " CLAUDE.global.md: ~${CLAUDE_MD_TOKENS}t"
echo " Skill descriptions: ~${SKILL_DESC_TOKENS}t (${SKILL_COUNT} skills)" echo " Skill descriptions: ~${SKILL_DESC_TOKENS}t (${SKILL_COUNT} skills)"
echo " Plugin passive cost: ~${PLUGIN_TOKENS}t (active plugins)" echo " Plugin passive cost: ~${PLUGIN_TOKENS}t (active plugins)"
echo " ─────────────────────────────────────────" echo " ─────────────────────────────────────────"
+2 -2
View File
@@ -14,7 +14,7 @@
# One-shot escape hatch: create .claude/.config-edit-ok (CWD-relative) with a # One-shot escape hatch: create .claude/.config-edit-ok (CWD-relative) with a
# NON-EMPTY reason inside; the hook logs the reason, consumes (rm) the sentinel, # NON-EMPTY reason inside; the hook logs the reason, consumes (rm) the sentinel,
# and allows that single edit. It never persists — a lingering sentinel would be # and allows that single edit. It never persists — a lingering sentinel would be
# a footgun. Discipline, per CLAUDE.md "Root causes only. No temp fixes.": fix # a footgun. Discipline, per CLAUDE.global.md "Root causes only. No temp fixes.": fix
# the code, don't loosen the gate. Fails OPEN (exit 0) on parse failure so it can # the code, don't loosen the gate. Fails OPEN (exit 0) on parse failure so it can
# never wedge editing. # never wedge editing.
@@ -58,7 +58,7 @@ cat >&2 <<EOF
This is a guardrail (permission/hook registry, gitflow enforcement, git This is a guardrail (permission/hook registry, gitflow enforcement, git
pre-commit guard, a hook, the test suite, health diagnostic, or lint config). pre-commit guard, a hook, the test suite, health diagnostic, or lint config).
Don't weaken the gate to make an error pass — fix the root cause instead Don't weaken the gate to make an error pass — fix the root cause instead
(CLAUDE.md: "Root causes only. No temp fixes."). To make one intended edit, (global CLAUDE.md: "Root causes only. No temp fixes."). To make one intended edit,
create .claude/.config-edit-ok with a non-empty reason; it is logged and create .claude/.config-edit-ok with a non-empty reason; it is logged and
consumed (one-shot). consumed (one-shot).
EOF EOF
+3 -3
View File
@@ -2,7 +2,7 @@
# design-toolchain-reminder.sh # design-toolchain-reminder.sh
# #
# UserPromptSubmit hook. When the prompt carries a UI/design signal, inject a # UserPromptSubmit hook. When the prompt carries a UI/design signal, inject a
# reminder to mobilize the full design toolchain (tiered by scope, per CLAUDE.md # reminder to mobilize the full design toolchain (tiered by scope, per CLAUDE.global.md
# "Design work — full toolchain"). A UserPromptSubmit hook's stdout is appended # "Design work — full toolchain"). A UserPromptSubmit hook's stdout is appended
# to the model's context, so the cat block below becomes additional guidance. # to the model's context, so the cat block below becomes additional guidance.
# #
@@ -26,7 +26,7 @@ prompt="$(printf '%s' "$input" \
[ -z "$prompt" ] && prompt="$input" [ -z "$prompt" ] && prompt="$input"
# Harness-generated turns (subagent/task notifications) are not user # Harness-generated turns (subagent/task notifications) are not user
# requests — never fire on them (CLAUDE.md trigger = a design/UI *request*). # requests — never fire on them (CLAUDE.global.md trigger = a design/UI *request*).
case "$prompt" in case "$prompt" in
'<task-notification>'*) exit 0 ;; '<task-notification>'*) exit 0 ;;
esac esac
@@ -54,7 +54,7 @@ if printf '%s' "$lc" | grep -Eq "$pattern"; then
"$(printf '%s' "$lc" | grep -oiE "$pattern" | head -1 || true)" \ "$(printf '%s' "$lc" | grep -oiE "$pattern" | head -1 || true)" \
"$(printf '%s' "$prompt" | tr '\n\t' ' ' | cut -c1-100)" >> "$logf" 2>/dev/null || true "$(printf '%s' "$prompt" | tr '\n\t' ' ' | cut -c1-100)" >> "$logf" 2>/dev/null || true
cat <<'EOF' cat <<'EOF'
Design work detected → apply CLAUDE.md section "Design work — full toolchain" (already in context). Trivial (≤2 files, cosmetic) → /hotfix. Design work detected → apply global CLAUDE.md section "Design work — full toolchain" (already in context). Trivial (≤2 files, cosmetic) → /hotfix.
EOF EOF
fi fi
+6 -6
View File
@@ -199,13 +199,13 @@ unset _active_count _inactive_count
printf "│ 🖥️ CLI : %-40s│\n" "$GSD_STATUS" printf "│ 🖥️ CLI : %-40s│\n" "$GSD_STATUS"
[ -n "$TOKEN_WARN" ] && printf "│ 💰 %-44s│\n" "${TOKEN_WARN:0:44}" [ -n "$TOKEN_WARN" ] && printf "│ 💰 %-44s│\n" "${TOKEN_WARN:0:44}"
printf "│ 📦 v%-45s│\n" "$CONFIG_VERSION" printf "│ 📦 v%-45s│\n" "$CONFIG_VERSION"
# CLAUDE.md line-count guard (anti-regression). BDR-062 supersedes BDR-031's # CLAUDE.global.md line-count guard (anti-regression). BDR-062 supersedes
# 275 target: 305 is the assumed reality (extraction already done at job1; # BDR-031's 275 target: 305 is the assumed reality (extraction done at
# further compression costs clarity > token gain) — warn only past a 320 margin. # job1; further compression costs clarity > token gain) — warn past 320.
if [ -n "$REPO_DIR" ] && [ -f "$REPO_DIR/CLAUDE.md" ]; then if [ -n "$REPO_DIR" ] && [ -f "$REPO_DIR/CLAUDE.global.md" ]; then
_claude_lines=$(wc -l < "$REPO_DIR/CLAUDE.md") _claude_lines=$(wc -l < "$REPO_DIR/CLAUDE.global.md")
if [ "$_claude_lines" -gt 320 ]; then if [ "$_claude_lines" -gt 320 ]; then
_cmd_warn="CLAUDE.md ${_claude_lines}L (>320) — density pass requis" _cmd_warn="CLAUDE.global.md ${_claude_lines}L (>320) — density pass"
printf "│ ⚠️ %-44s│\n" "${_cmd_warn:0:44}" printf "│ ⚠️ %-44s│\n" "${_cmd_warn:0:44}"
unset _cmd_warn unset _cmd_warn
fi fi
+8 -5
View File
@@ -33,12 +33,15 @@ source "$REPO/lib/detect-plugins.sh"
# graphify's installer (Step 7) rewrites CLAUDE.md + .claude/settings.json # graphify's installer (Step 7) rewrites CLAUDE.md + .claude/settings.json
# (clobbers the curated graphify section + injects aggressive MANDATORY # (clobbers the curated graphify section + injects aggressive MANDATORY
# hooks), and `claude plugin install` (Step 5) flips enable-states in # hooks), and `claude plugin install` (Step 5) flips enable-states in
# settings.json. These 3 files are maintained by hand + commit, never by # settings.json. These 4 files are maintained by hand + commit, never by
# the installer. Snapshot them now and restore on exit so a run leaves them # the installer. Snapshot them now and restore on exit so a run leaves them
# exactly as it found them. Pre-existing local edits are preserved; only the # exactly as it found them. Pre-existing local edits are preserved; only the
# installer's drift is undone. NOTE: this makes these files install-immutable # installer's drift is undone. NOTE: this makes these files install-immutable
# — anything the installer should add to them must be committed by hand. # — anything the installer should add to them must be committed by hand.
GUARDED_CONFIGS=("CLAUDE.md" ".claude/settings.json" "settings.json") # CLAUDE.md = project memory (graphify's rewrite target); CLAUDE.global.md
# = user-scope global memory (deployed as ~/.claude/CLAUDE.md).
GUARDED_CONFIGS=("CLAUDE.md" "CLAUDE.global.md" ".claude/settings.json"
"settings.json")
CFG_SNAPSHOT="$(mktemp -d 2>/dev/null || true)" CFG_SNAPSHOT="$(mktemp -d 2>/dev/null || true)"
restore_curated_configs() { restore_curated_configs() {
@@ -63,8 +66,8 @@ if [ -n "$CFG_SNAPSHOT" ]; then
trap restore_curated_configs EXIT trap restore_curated_configs EXIT
else else
err "Config guard could not be created (mktemp failed) — refusing to run" \ err "Config guard could not be created (mktemp failed) — refusing to run" \
"unguarded: CLAUDE.md/.claude/settings.json/settings.json could be" \ "unguarded: CLAUDE.md/CLAUDE.global.md/.claude/settings.json/settings.json" \
"silently rewritten by the installer. Fix mktemp/TMPDIR and retry." "could be silently rewritten by the installer. Fix mktemp/TMPDIR and retry."
exit 1 exit 1
fi fi
@@ -573,7 +576,7 @@ echo ""
# subscription plan its ~75% output-token compression has no cost benefit, # subscription plan its ~75% output-token compression has no cost benefit,
# and the plugin's always-on SessionStart/UserPromptSubmit hooks added # and the plugin's always-on SessionStart/UserPromptSubmit hooks added
# friction on validation gates and client deliverables. The unrelated # friction on validation gates and client deliverables. The unrelated
# memory-registry terse-format convention (CLAUDE.md) is kept. # memory-registry terse-format convention (CLAUDE.global.md) is kept.
# ============================================================ # ============================================================
# STEP 6 — CONTEXT7 CLI (ctx7) # STEP 6 — CONTEXT7 CLI (ctx7)
+5 -3
View File
@@ -41,11 +41,13 @@ _unsafe_state() {
} }
# True (0) when a path is OUT OF SCOPE for a doc commit: anything under .claude/ # True (0) when a path is OUT OF SCOPE for a doc commit: anything under .claude/
# (any depth) or a CLAUDE.md (root or nested). These are doc-syncer's read-only # (any depth) or a CLAUDE.md / CLAUDE.global.md memory file (root or nested).
# context, never sync targets (BDR-022) — their presence is an upstream anomaly. # These are doc-syncer's read-only context, never sync targets (BDR-022) —
# their presence is an upstream anomaly.
_forbidden_path() { _forbidden_path() {
case "$1" in case "$1" in
.claude | .claude/* | */.claude/* | CLAUDE.md | */CLAUDE.md) return 0 ;; .claude | .claude/* | */.claude/* | CLAUDE.md | */CLAUDE.md | \
CLAUDE.global.md | */CLAUDE.global.md) return 0 ;;
*) return 1 ;; *) return 1 ;;
esac esac
} }
+21 -18
View File
@@ -6,7 +6,7 @@
# single-occurrence + column-0 closing brace) so drift in install-plugins.sh # single-occurrence + column-0 closing brace) so drift in install-plugins.sh
# propagates into this test instead of testing a stale copy. GUARDED_CONFIGS, # propagates into this test instead of testing a stale copy. GUARDED_CONFIGS,
# CFG_SNAPSHOT, REPO and an info() stub are defined here — the array literal # CFG_SNAPSHOT, REPO and an info() stub are defined here — the array literal
# at install-plugins.sh:41 is outside the extracted range. # at install-plugins.sh:43-44 is outside the extracted range.
set -u set -u
INSTALL_SH="$(cd "$(dirname "$0")/../.." && pwd)/install-plugins.sh" INSTALL_SH="$(cd "$(dirname "$0")/../.." && pwd)/install-plugins.sh"
pass=0; fail=0 pass=0; fail=0
@@ -19,13 +19,14 @@ awk '/^restore_curated_configs\(\) \{/,/^\}/' "$INSTALL_SH" > "$SUT"
REPO="$(mktemp -d)" REPO="$(mktemp -d)"
CFG_SNAPSHOT="$(mktemp -d)" CFG_SNAPSHOT="$(mktemp -d)"
EXPECT="$(mktemp -d)" # our own reference copy — independent of CFG_SNAPSHOT (SUT rm -rf's it) EXPECT="$(mktemp -d)" # our own reference copy — independent of CFG_SNAPSHOT (SUT rm -rf's it)
GUARDED_CONFIGS=("CLAUDE.md" ".claude/settings.json" "settings.json") GUARDED_CONFIGS=("CLAUDE.md" "CLAUDE.global.md" ".claude/settings.json" "settings.json")
info() { :; } # stub — extracted body calls info(), irrelevant to the assertions info() { :; } # stub — extracted body calls info(), irrelevant to the assertions
mkdir -p "$REPO/.claude" mkdir -p "$REPO/.claude"
printf 'CLAUDE original\n' > "$REPO/CLAUDE.md" printf 'CLAUDE original\n' > "$REPO/CLAUDE.md"
printf '{"a":1}\n' > "$REPO/.claude/settings.json" printf 'CLAUDE.global original\n' > "$REPO/CLAUDE.global.md"
printf '{"b":2}\n' > "$REPO/settings.json" printf '{"a":1}\n' > "$REPO/.claude/settings.json"
printf '{"b":2}\n' > "$REPO/settings.json"
for f in "${GUARDED_CONFIGS[@]}"; do for f in "${GUARDED_CONFIGS[@]}"; do
mkdir -p "$CFG_SNAPSHOT/$(dirname "$f")" "$EXPECT/$(dirname "$f")" mkdir -p "$CFG_SNAPSHOT/$(dirname "$f")" "$EXPECT/$(dirname "$f")"
@@ -33,7 +34,7 @@ for f in "${GUARDED_CONFIGS[@]}"; do
cp "$REPO/$f" "$EXPECT/$f" cp "$REPO/$f" "$EXPECT/$f"
done done
# simulate installer drift: mutate ONE guarded file, leave the other two alone # simulate installer drift: mutate ONE guarded file, leave the other three alone
printf 'CLAUDE CLOBBERED BY INSTALLER\n' > "$REPO/CLAUDE.md" printf 'CLAUDE CLOBBERED BY INSTALLER\n' > "$REPO/CLAUDE.md"
# shellcheck source=/dev/null # shellcheck source=/dev/null
@@ -42,20 +43,22 @@ restore_curated_configs
cmp -s "$REPO/CLAUDE.md" "$EXPECT/CLAUDE.md" cmp -s "$REPO/CLAUDE.md" "$EXPECT/CLAUDE.md"
check T1-mutated-file-restored "$?" 0 check T1-mutated-file-restored "$?" 0
cmp -s "$REPO/CLAUDE.global.md" "$EXPECT/CLAUDE.global.md"
check T2-untouched-global-md-unchanged "$?" 0
cmp -s "$REPO/.claude/settings.json" "$EXPECT/.claude/settings.json" cmp -s "$REPO/.claude/settings.json" "$EXPECT/.claude/settings.json"
check T2-untouched-local-settings-unchanged "$?" 0 check T3-untouched-local-settings-unchanged "$?" 0
cmp -s "$REPO/settings.json" "$EXPECT/settings.json" cmp -s "$REPO/settings.json" "$EXPECT/settings.json"
check T3-untouched-settings-unchanged "$?" 0 check T4-untouched-settings-unchanged "$?" 0
if [ -d "$CFG_SNAPSHOT" ]; then r4=present; else r4=gone; fi if [ -d "$CFG_SNAPSHOT" ]; then r5=present; else r5=gone; fi
check T4-snapshot-dir-removed "$r4" gone check T5-snapshot-dir-removed "$r5" gone
# --- T5: mktemp failure -> fail-closed (install-plugins.sh, the header block # --- T6: mktemp failure -> fail-closed (install-plugins.sh, the header block
# that builds CFG_SNAPSHOT) — refuses to run unguarded instead of warning and # that builds CFG_SNAPSHOT) — refuses to run unguarded instead of warning and
# continuing. Extracted with a WIDER range than the SUT above: this logic # continuing. Extracted with a WIDER range than the SUT above: this logic
# lives in the top-level if/else, outside restore_curated_configs(). # lives in the top-level if/else, outside restore_curated_configs().
SUT2="$(mktemp)" SUT2="$(mktemp)"
awk '/^GUARDED_CONFIGS=/,/^fi$/' "$INSTALL_SH" > "$SUT2" awk '/^GUARDED_CONFIGS=/,/^fi$/' "$INSTALL_SH" > "$SUT2"
ERR5="$(mktemp)" ERR6="$(mktemp)"
( (
# shellcheck disable=SC2329 # invoked indirectly by the sourced snippet below # shellcheck disable=SC2329 # invoked indirectly by the sourced snippet below
mktemp() { return 1; } # force the header's CFG_SNAPSHOT creation to fail mktemp() { return 1; } # force the header's CFG_SNAPSHOT creation to fail
@@ -68,11 +71,11 @@ ERR5="$(mktemp)"
REPO="$(command mktemp -d)" REPO="$(command mktemp -d)"
# shellcheck source=/dev/null # shellcheck source=/dev/null
source "$SUT2" source "$SUT2"
) >/dev/null 2>"$ERR5" ) >/dev/null 2>"$ERR6"
rc5=$? rc6=$?
check T5-mktemp-failure-aborts "$rc5" 1 check T6-mktemp-failure-aborts "$rc6" 1
if grep -qi 'mktemp failed' "$ERR5"; then r5msg=yes; else r5msg=no; fi if grep -qi 'mktemp failed' "$ERR6"; then r6msg=yes; else r6msg=no; fi
check T5-mktemp-failure-loud "$r5msg" yes check T6-mktemp-failure-loud "$r6msg" yes
rm -f "$ERR5" "$SUT2" rm -f "$ERR6" "$SUT2"
printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ] printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ]
+1 -1
View File
@@ -17,7 +17,7 @@ link_file() {
CHANGED=$((CHANGED + 1)) CHANGED=$((CHANGED + 1))
} }
link_file "$REPO/CLAUDE.md" "$CLAUDE/CLAUDE.md" link_file "$REPO/CLAUDE.global.md" "$CLAUDE/CLAUDE.md"
link_file "$REPO/settings.json" "$CLAUDE/settings.json" link_file "$REPO/settings.json" "$CLAUDE/settings.json"
for item in hooks agents skills lib templates rules; do for item in hooks agents skills lib templates rules; do
+3 -27
View File
@@ -4,30 +4,6 @@ paths: ["rules/**"]
# rules/ # rules/
Modular instruction files loaded by Claude Code alongside `CLAUDE.md`. User-scope rules, deployed to `~/.claude/rules` by `link.sh`.
Symlinked to `~/.claude/rules` by `link.sh`, same model as `agents/`, Maintenance doctrine (what belongs here, lazy-load `paths:` semantics,
`skills/`, `lib/`. machine-owned files): see `CLAUDE.md` (project scope) at the repo root.
## What belongs here
One rule = one file = one concern. Candidates: instructions that are
self-contained enough to live outside `CLAUDE.md`'s main flow, or that
tooling generates/owns.
Rules support an optional `paths:` YAML frontmatter (glob list). A rule
WITH `paths` loads lazily — only when Claude reads a file matching a
glob; a rule WITHOUT it loads at session start, same cost as CLAUDE.md.
So: extract from CLAUDE.md only what can be path-scoped (the token win)
or what is generated; always-on doctrine stays in CLAUDE.md.
Docs: https://code.claude.com/docs/en/memory.md#path-specific-rules
## Machine-owned files (gitignored, regenerated)
- `context7.md` — DELETED BY DESIGN (BDR-053, 2026-07-06): `ctx7 setup
--claude --cli` still writes it, but install-plugins.sh STEP ctx7
purges it right after — the find-docs skill is the single ctx7
surface; the rule was a ~490 tok/session session-start duplicate
(job1 F10). If it reappears (manual `ctx7 setup`), delete it or
re-run `make plugin`.
Hand-written rules ARE tracked — add them normally.
+1 -1
View File
@@ -236,7 +236,7 @@
"disableBypassPermissionsMode": "disable", "disableBypassPermissionsMode": "disable",
"additionalDirectories": [] "additionalDirectories": []
}, },
"model": "opus-4-8[1m]", "model": "claude-fable-5[1m]",
"hooks": { "hooks": {
"SessionStart": [ "SessionStart": [
{ {