Files
claude/.claude/memory/decisions.md
T

262 KiB
Raw Blame History

type, entry_prefix, schema, rules
type entry_prefix schema rules
decisions_registry BDR
id date title decision why alternatives status supersedes
BDR-XXX YYYY-MM-DD string (<= 80 chars) string (what was chosen) string (motivation, context) list of strings (what was rejected + why)
proposed | accepted | deprecated | superseded
BDR-XXX (optional)
Append-only. Never rewrite past entries - add new one with status superseded if needed.
One entry per non-trivial choice. Trivial = reversible under 10 min, no cross-file impact.
Capture why more carefully than what - what rots, why lasts.

Decisions registry (BDR)

Index

ID Date Title Status
BDR-001 2026-04-22 Uniform --help helper via session-start hook (option C) accepted · won't-build 2026-06-30
BDR-002 2026-04-23 Move tasks/ + introduce memory + audits under .claude/ accepted
BDR-003 2026-04-23 Gitignore wildcard + negations pattern for .claude/ accepted
BDR-004 2026-04-27 Adopt auto permission mode as default accepted
BDR-005 2026-04-27 motion as default animation library; advisor stays read-only accepted
BDR-006 2026-05-03 Caveman as 4th always-on plugin (output compression) accepted
BDR-007 2026-05-04 Skill profiles partition gstack by usage (design / dev / qa / audit / minimal) accepted
BDR-008 2026-05-04 Profile system v2: extend to plugins + MCPs + CLIs (web/seo/web-full/backend) accepted
BDR-009 2026-05-05 Mandate caveman format on .claude/memory/ registries accepted
BDR-010 2026-05-07 Gate GEO independently at ≥17/20 in client-handover pipeline accepted
BDR-011 2026-05-07 Client handover deliverable: 4-chapter structure + ZenQuality branded HTML/PDF superseded by BDR-013 (6-chapter doc)
BDR-012 2026-05-07 client-handover cover: white bg + green accents + PNG logo default accepted
BDR-013 2026-05-11 client-handover: 6-chapter doc — promote scores §2 + NAP §4 accepted
BDR-014 2026-05-11 Personal SKILL.md descriptions: "Use when [triggers]…" pattern + 1024-char spec limit accepted
BDR-015 2026-05-12 Exclude broken gstack symlinks from /darwin-skill scope (external ownership) accepted · trigger cleared by BDR-043
BDR-016 2026-05-15 doc-syncer: README AUTO+unconditional, DEPLOY.md prod-only + 14-section VPS template accepted
BDR-017 2026-05-18 full profile = web-full + plan + dev superset for /init-project MVP accepted
BDR-018 2026-06-02 profile gstack on/off verb — toggle gstack keeping active-profile label accepted
BDR-019 2026-06-09 Remove disable-model-invocation repo-wide — align skills with CLAUDE.md routing accepted
BDR-020 2026-06-11 /audit-delta: per-axis SHA markers + always-on fix gate + unreachable-first-run = full report-only accepted
BDR-021 2026-06-27 CLAUDE.md restructure: contradiction purge, project-specific sections labeled, critical sections never compressed accepted
BDR-022 2026-06-18 doc-syncer scoped to public docs; .claude/ + CLAUDE.md read-only context, never targets; conventions + clean mode accepted
BDR-023 2026-06-19 Merge /close into /capitalize — 2 modes + TODO reconcile; /close alias accepted
BDR-024 2026-06-27 profile show --plain = claude-free parse contract for the design gate accepted
BDR-025 2026-06-27 Design gate profile-based; remedy /profile design; magic required-but-manual; unknown → fail-visible; claude via PATH-repair accepted
BDR-026 2026-06-27 Secret source-of-truth outside the repo (~/.claude/.env) reached via a repo/.env symlink accepted
BDR-027 2026-06-27 Minimal npm-via-nvm bootstrap over a centralized prereq lib accepted
BDR-028 2026-06-27 Hand-curated config install-immutable (auto-revert guard) + de-vendor installer-managed skills accepted
BDR-029 2026-06-27 Installer auto-fixes gstack browser on an OS newer than its pinned Playwright supports accepted
BDR-030 2026-06-27 gstack skills activated ON-DEMAND per profile, not pre-installed; OFF by default stays accepted
BDR-031 2026-06-27 global CLAUDE.md lightening = COMPRESSION, not path-scope / externalization accepted · 275-line target superseded by BDR-062
BDR-032 2026-06-27 skill /validate → /web-validate (rename user surface, keep internals) accepted
BDR-033 2026-06-27 design-gate §4: anim-lib suggestion — suggest-only, non-blocking, stateless 1-line accepted
BDR-034 2026-06-26 Coupled-capitalize invariant v1 — memory commit auto per dev flow (Frame 2) accepted
BDR-035 2026-06-26 Analyze-before-plan invariant v1 — read-before bookend of coupled-capitalize accepted
BDR-036 2026-06-27 Doc-sync coupled invariant — commit docs doc-syncer patches (twin of BDR-034, BUILT not reordered) accepted
BDR-037 2026-06-27 v2 capitalize Stop-hook rejected → wire /capitalize+/close to the include accepted
BDR-038 2026-06-27 deploy skill: per-project learning runbook, two-moment cold-resume superseded by BDR-054 (NEXT.sh, hand-back)
BDR-039 2026-06-29 Gitea branch protection = Option-1 owner-pushable, not require-PR accepted
BDR-040 2026-06-29 doc-syncer MINOR-shape oracle: deterministic floor under LLM's MINOR call accepted
BDR-041 2026-06-30 /reconcile = deterministic declared-vs-real engine + thin gated skill (reconciler, not lister) accepted
BDR-042 2026-06-30 /release-candidate = thin orchestrator over gitflow release; the tag lives in the skill, not the lib accepted
BDR-043 2026-06-30 BDR-015 trigger cleared — 5 ex-broken gstack symlinks repaired → darwin re-baseline back in scope (unblocked, NOT run) accepted
BDR-044 2026-06-30 auto-skill-dispatch won't-build — under-routing fear inverted to over-routing by cartography, then measured: model discriminates (clear→route, ambiguous→ask, trivial→abstain) accepted · won't-build
BDR-045 2026-07-01 Standalone memory/doc skills branch to chore/* via aiguillage (hook exemption kept) accepted
BDR-046 2026-07-01 Claude Code installs via official native installer (curl claude.ai/install.sh), drop npm from install.sh accepted
BDR-047 2026-07-01 ECC audit → zero import; local config ahead of reference accepted
BDR-048 2026-07-03 semgrep security gate: engine version + rulesets PINNED, never --config auto; upgrade = deliberate visible human jump accepted
BDR-049 2026-07-03 verifier = fresh + blind (no iteration history) + disk-contract + PROOF-or-fail; mute ≠ PASS; scope enrichment via human micro-gate accepted
BDR-050 2026-07-03 universal pipeline (contract→dev inline→fresh verify→fresh security, loops bounded 3× in main loop) with per-flow weighting; hotfix failure = revert not loop accepted
BDR-051 2026-07-04 contract enrich-at-gate: the contract grows ONLY at a human micro-gate ([gated] marker); the verifier judges the ENRICHED contract, not the seed accepted
BDR-052 2026-07-05 /tour auto mode = branch-as-gate: no mid-run approval gates; unmerged chore branch + per-project TOUR.md = deferred human gate; reconcile report-only; loop bounded 3× accepted
BDR-053 2026-07-06 ctx7 single surface: keep find-docs skill, kill context7.md rule accepted
BDR-054 2026-07-06 supersede BDR-038 NEXT.sh/hand-back artifacts — shipped impl removed both (52f6678, LRN-102) accepted
BDR-055 2026-07-07 job5: delete memory-commit/doc-commit pending verbs — v2 hook rejected (BDR-037), J4-17 closed MOOT accepted
BDR-056 2026-07-07 job6: deps policy = latest gated by integration, not KEEP-PINNED by default accepted
BDR-057 2026-07-07 job7: secrets by reference not by value; redact at capture, not just at rest accepted
BDR-058 2026-07-07 job8: darwin-skill reinstall full pinned tree, detached HEAD (skills CLI single-file-fetch gap) accepted
BDR-059 2026-07-07 job8: explicit ask-gate for all 4 magic MCP tools, empty allow stays empty 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-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
BDR-065 2026-07-14 transient planning artifacts (superpowers spec/plan): committed during run, deleted post-merge; git history = archive; codified in project CLAUDE.md accepted
BDR-066 2026-07-15 Model routing: reflection inline (session big model) + sonnet-pinned executors + blocking gate accepted
BDR-067 2026-07-16 first public release: versioning reset to v1.0.0 (override "never restart at v1.0.0") — 2026-07-16 SHIPPED
BDR-068 2026-07-16 /capitalize + /close auto-persist memory (finish→develop + push); scoped LRN-069 exception — 2026-07-16 implemented on feature/close-auto-persist
BDR-069 2026-07-16 permissions deny: keep broad .env.* glob, keep .env.example name (option A) — 2026-07-16 implemented on chore/fix-inert-write-deny-rules
BDR-070 2026-07-17 claude-seo: cherry-pick scripts into our tree, never install; /seo stays sole entry accepted
BDR-071 2026-07-17 No viable free backlink source → Off-page axis stays brand-mentions-only (FINAL, not placeholder) accepted
BDR-072 2026-07-17 SPA: honest refuse (On-page N/A, not zero), no headless browser (R2 over R1) accepted
BDR-073 2026-07-17 Scoring: LLM judges findings+severity, engine does the arithmetic (deterministic /20) accepted
BDR-074 2026-07-17 Remove config-protection edit-block guardrail accepted
BDR-075 2026-07-17 Framework-wide 3-way adversarial plan-challenge phase accepted
BDR-076 2026-07-19 Dispatched judgment agents pinned OPUS; session model = orchestration + inline reflection ONLY accepted
BDR-077 2026-07-19 Model-tiering v2: 4-tier explicit routing, mode-based splits, no-inherit dispatches accepted
BDR-078 2026-07-20 ctx7 coverage: central fast-libs list + once-per-session reminder hook; every code path covered accepted
BDR-079 2026-07-20 profile set symmetric on managed externals + MCPs accepted
BDR-080 2026-07-21 Bug routing inverted: /bugfix primary, /investigate explicit-only accepted
BDR-081 2026-07-30 Config recalibrated for Claude 5 family (Opus 5 dispatch tier) accepted
BDR-082 2026-08-02 seo/geo analyzers de-prescribed for Opus 5 (C1) accepted
BDR-083 2026-08-24 Contract gates: deterministic floor (GATE 0) under the fresh verifier accepted
BDR-084 2026-08-24 /tour multi-project: parallel runners (LRN-083 derogation, bounded), runner inherits session model accepted
BDR-085 2026-08-25 User permanent rules: writing-style always-on in rules/, web build+security path-scoped accepted
BDR-086 2026-08-26 darwin: threshold gates full loops; verified defects fixed regardless of unit score (paired-validated, batched checkpoint) accepted
BDR-087 2026-09-03 Stop hook = attention signal only, never control flow; one script for Notification + Stop accepted
BDR-088 2026-09-15 gstack Playwright bump shared via lib, re-applied after submodule update; update helper never touches the submodule tree accepted
BDR-089 2026-09-15 No Playwright browser-cache pruner; read-only doctor report — .links proved 0 bytes reclaimable accepted
BDR-090 2026-09-15 Destructive shell work → autoMode soft_deny/hard_deny; ask tier abandoned (inert under auto) accepted
BDR-091 2026-09-16 Ask, don't guess: open-choice sweep (3 classes) at plan step + mid-run CLASS channel supersede "one question upfront" accepted
BDR-092 2026-09-16 docker + node framed by the classifier via autoMode.allow + soft_deny; ask entries retired accepted
BDR-093 2026-09-22 21st.dev: magic MCP → CLI + skill pack; staged install past the ~/.claude/skills symlink; CLI = gate's required-manual accepted
BDR-094 2026-09-22 impeccable: global-scope install through the repo symlinks, pin + @latest fallback, output-read failure check accepted
BDR-095 2026-09-22 Data-loss guardrails: static deny for transfer/destructive tools, push every commit, brief ≠ user authority accepted
BDR-096 2026-09-24 Branch deletion guard: lib-only delete after verified merge, main/develop undeletable at the ref layer accepted
BDR-097 2026-09-24 graphify from 200 tracked code files: the banner informs, the user decides accepted
BDR-098 2026-09-24 CLAUDE.global.md density pass 352 → 270: compression only, three name-obvious routing lines dropped accepted
BDR-099 2026-09-24 C2 coherence: 30 doctrine/skill tensions resolved, doctrine wins, BDR-068 kept as the written exception accepted
BDR-100 2026-09-24 Guardrail evasion and partial rule changes get mechanisms, not lessons: refusal ends the attempt, citers census in make test accepted
BDR-101 2026-09-25 full = default profile: no selection ⇒ full in force, reset applies it, install applies it accepted
BDR-102 2026-09-27 agent-skills: no plugin, vendor 3 skills + build floor-guard + routing census + rest-api rule accepted
BDR-103 2026-09-27 6-repo review: 5 verdicts, 3 criteria (grep-verified coverage, per-session cost, doctrine conflict); stars decided nothing accepted
BDR-104 2026-09-28 MengTo motion pack: vendor 5 scroll skills pinned via shared lib/vendor-skills.sh + build personal skill site-motion; 17 skipped accepted
BDR-105 2026-09-28 skill-catalog prune: 9 gstack out via GSTACK_REMOVED, full ⊇ every profile, max = everything, brightdata + frontend-design plugin off, security-guidance Stop review off, design gate asks 21st login and waits accepted
BDR-106 2026-09-28 superpowers: 7 wired skills vendored at v6.4.1 via lib/vendor-skills.sh (always_on lock class), plugin + marketplace dropped, citers by bare name, doctrine map for the 4 non-vendored refs accepted
BDR-107 2026-09-28 Effort tiering: session high, effort pins on 20 agents (BDR-077 second axis), entry level on 30 skills, five paired shifter skills, max at loop caps + ship-feature 4b accepted
BDR-108 2026-09-29 Effort round: level on every skill next to its model pin (3 repo + 25 vendored via lib/effort-pins.txt re-applied after the LAST vendoring step of install + resync), design stack ONE level (high), model pins stay tier aliases: quality/price trade-off = tier × effort, never version accepted
BDR-109 2026-09-30 Higgsfield pack: npm CLI latest + 8 upstream skills git-cloned into gitignored skills-external/higgsfield-*, OFF by default, in no profile; two toggles (higgsfield = allowlist of 7 media skills, higgsfield-websites = landing-page aid, never website create/deploy/publish); CLI presence by probe; routing on explicit ask accepted

BDR-001 — Uniform --help helper via session-start hook (option C)

  • Date: 2026-04-22
  • Status: accepted
  • Decision: every skill expose --help via shared snippet injected by session-start hook, not duplicate helper in each SKILL.md.
  • Why: 25+ skills — keep same helper synced across every file guarantees drift. Single injection point = single source of truth.
  • Alternatives rejected:
    • Option A (copy helper into each SKILL.md) — rejected: maintenance entropy.
    • Option B (external wrapper /help <skill>) — rejected: breaks "one command = one skill" experience.
  • Reference: commit 3968a29.
  • Won't-build (2026-06-30): accepted but never built. MEASURED before building — behavioral RED, 6 reps (/web-validate + /harden, no instruction): 6/6 already render rich help AND stop without dispatching (even /harden didn't start its audit). The intended behavior is already spontaneous (universal --help convention); the ONLY residual value of the global instruction = format CONSISTENCY across 6 divergent shapes — judged not worth ~5 lines in a BDR-031-compressed CLAUDE.md on a solo repo. Not "abandoned" — measured non-rentable. Per-skill option stays rejected (original Decision above). See LRN-080, LRN-075.

BDR-002 — Move tasks/ + introduce memory + audits under .claude/

  • Date: 2026-04-23
  • Status: accepted
  • Decision: migrate ./tasks/ to .claude/tasks/, create .claude/memory/ (5 registries BDR/LRN/BLK/journal/EVAL) and .claude/audits/ for AUDIT_* files. Adapt skills/agents/CLAUDE.md. Integrate CAPITALIZE step into completion skills (ship-feature, feat, bugfix, hotfix, commit-change), add /close skill for session-end ritual.
  • Why: group all meta-project state (AI config + tasks + memory + audits) under .claude/ isolate Claude governance from real code. Aligned with official Claude Code memory docs. Without integration in completion skills, registries stay empty (aspirational text).
  • Alternatives rejected:
    • Keep ./tasks/ at root — rejected: clutters repo, mixes code signal with governance signal.
    • Use .claude/agent-memory/ for everything — rejected: agent-memory/ has distinct role (already used by other tools).
    • Ritual as aspirational text only in CLAUDE.md — rejected: zero execution guarantee, registries stay empty.
    • Stop hook to ask 3 questions every turn — rejected: too noisy.

BDR-003 — Gitignore wildcard + negations pattern for .claude/

  • Date: 2026-04-23
  • Status: accepted
  • Decision: use .claude/* (wildcard match of immediate children) + negations !.claude/tasks/, !.claude/memory/, etc., not .claude/ (recursive ignore).
  • Why: when parent ignored via .claude/, git no descend (performance optimization) and negations on children ignored — documented in gitignore(5). With .claude/*, git matches each child individually, negations active.
  • Alternatives rejected:
    • .claude/ + !.claude/tasks/ (naive) — rejected: negations no effect, everything stays ignored.
    • Drop .claude/ from gitignore entirely — rejected: .claude/settings.local.json and .claude/agent-memory/ must stay ignored (per-machine).
    • Track paths via .gitattributes or external tool — rejected: over-engineering, git handles natively.
  • Reference: commit 499cd07, git check-ignore -v verified on 4 paths (2 tracked, 2 ignored).

BDR-004 — Adopt auto permission mode as default

  • Date: 2026-04-27
  • Status: accepted
  • Decision: set permissions.defaultMode to "auto" in user-scope settings.json, drop disableAutoMode: "disable". Auto mode runs classifier on every action, blocks risky operations (curl|bash, prod deploys, force push, IAM grants, mass deletes, exfiltration to external endpoints), auto-approves local edits, lockfile-declared dep installs, read-only HTTP.
  • Why: prompt fatigue under default mode big on multi-step autonomous work. Auto mode keeps safety net (classifier review) without per-tool friction. Classifier re-evaluates conversation-stated boundaries ("don't push", "wait for review") on every check, verbal constraints carry weight.
  • Alternatives rejected:
    • Keep default — too many prompts, breaks flow on long tasks.
    • acceptEdits — eliminates prompts but no classifier, blanket trust on Bash beyond filesystem helpers.
    • bypassPermissions — skips all checks, no prompt-injection guard. Only for isolated containers.
    • dontAsk — full denylist, breaks anything not pre-approved. Suited to CI, not interactive work.
  • Caveats: requires Claude Code v2.1.83+, plan ≠ Pro (Max/Team/Enterprise/API only), Sonnet 4.6 / Opus 4.6 / Opus 4.7, Anthropic API provider. On entering auto mode, blanket allow rules (Bash(*), Bash(python*), package-manager run, Agent) dropped, restored on exit.
  • Reference: commit 1421578.

BDR-005 — motion as default animation library; advisor stays read-only

  • Date: 2026-04-27
  • Status: accepted
  • Decision: when project stack supports it, framework installs motion (or motion-v for Vue 3 / Nuxt) as default animation library. Install automatic in /init-project STEP 5e (post-scaffold), opt-in in /onboard STEP 2.5 (existing projects). plugin-advisor only detects and reports status — never runs npm install itself. Detection logic in lib/animation-lib-check.sh (sourced by all three layers).
  • Why: framer-motion rebranded motion in November 2024 (single package supporting React motion/react, Svelte, vanilla JS; motion-v parallel package for Vue). Bake new name now to avoid legacy-import sprawl across new projects. Split init-vs-onboard behavior follows trust gradient: at init, user just validated entire scaffold so silent install fine; at onboard, touching existing package.json invasive without explicit consent. Plugin-advisor kept read-only to preserve "Never modify files" contract (PHASE 4 already mutates plugin state with confirmation; piling npm installs on top blurs responsibility).
  • Alternatives rejected:
    • Pin framer-motion (legacy name) — rejected: package in maintenance mode, every new project inherits old import path.
    • Auto-install during /onboard without asking — rejected: silently adds runtime dep + ~50 KB gzip to project user did not ask to modify.
    • Make plugin-advisor install missing libs — rejected: violates read-only spec, breaks separation of concerns (advisor advises; orchestrators mutate).
    • React-only scope — rejected: Vue/Svelte teams should benefit; motion-v makes Vue case clean.
  • Eligibility rules (helper output):
    • eligible|motion: React, Next.js, Remix, Astro+React, Svelte/SvelteKit
    • eligible|motion-v: Vue 3, Nuxt
    • no|-: backend, CLI, embedded, Flutter, static HTML, React Native (use react-native-reanimated), Astro without UI integration, no package.json
  • Reference: helper at lib/animation-lib-check.sh; integration in skills/init-project/SKILL.md STEP 5e, skills/onboard/SKILL.md STEP 2.5, agents/plugin-advisor.md PHASE 1/2/3, lib/design-gate.md.

BDR-006 — Caveman as 4th always-on plugin (output compression)

  • Date: 2026-05-03
  • Status: accepted
  • Decision: install JuliusBrussee/caveman in always-on tier alongside security-guidance, superpowers, rtk. "Full" install = plugin (/caveman + cavecrew agents + plugin-scoped SessionStart/UserPromptSubmit hooks) + standalone hooks (statusline + stats badge in ~/.claude/hooks/) + caveman-shrink MCP scaffold (NOT auto-registered — proxy needs upstream wrapper). install-plugins.sh STEP 5.5 calls enable_plugin "caveman" "caveman" to write into enabledPlugins. Hook paths in settings.json normalized to ~/.claude/hooks/... post-install so user home dir no leak across machines.
  • Why: caveman compresses Claude output ~75% via caveman-speak, preserves technical substance. Symmetrical with rtk (input compression hook) — rtk shrinks tool I/O, caveman shrinks model output. Both hooks pay zero passive cost in clean session, amortize across long runs. Always-on justified: plugin auto-deactivates with phrases like "stop caveman" / "normal mode", toggle would be friction without benefit.
  • Alternatives rejected:
    • Toggle plugin (start OFF) — rejected: misses by-default benefit; user need remember claude plugin enable caveman@caveman per session, negates auto-compression value.
    • --minimal install (plugin only) — rejected: loses standalone stats badge surfacing token-saving telemetry.
    • --all install (adds per-repo caveman-rules.md etc. into $PWD) — rejected: would litter THIS config repo (cwd at install time) with rule files meant for project repos. Let users opt in per-repo when wanted.
    • Auto-register caveman-shrink MCP — rejected: proxy errors with "missing upstream command" without upstream MCP to wrap, fails health checks. Print snippet instead, let user pick which upstream they want compressed (filesystem, github, …).
  • Caveats:
    • Caveman hooks/install.sh writes absolute paths ($HOME/.claude/hooks/caveman-*.js) into settings.json. settings.json symlinked into repo, absolute path commits username. STEP 5.5 runs Python post-process to rewrite to portable ~/.claude/hooks/... form (bash expands ~ before passing to node).
    • Caveman hook files materialize in hooks/ (repo dir, not ~/.claude/hooks/) because latter is symlink. Added to .gitignore to prevent accidental commit of user-scope state.
  • Reference: install-plugins.sh STEP 5.5, lib/detect-plugins.sh detect_caveman* + plugin_enabled, doctor.sh caveman block, commit 9b20b84.

BDR-007 — Skill profiles partition gstack by usage (design / dev / qa / audit / minimal)

  • Date: 2026-05-04
  • Status: accepted
  • Decision: ship lib/profile.sh + lib/profiles/*.profile to give user fine-grained, task-shaped activation of skills. Profile = plain-text file listing skill names + types (gstack, external, personal, plugin, mcp). profile set <name> enables listed skills, disables every gstack-origin skill not in profile, by moving symlinks between skills/ and skills-disabled/. profile reset re-enables all of gstack. Plugin/MCP entries advisory — script prints manual claude plugin enable / claude mcp add command but never runs it. Surface area: one CLI (bash lib/profile.sh), one slash command (/profile), four Makefile targets, section in agents/plugin-advisor.md.
  • Why: when user works on focused kind of task (design only, qa only, audit only) full gstack (~38 skills) injects irrelevant skill descriptions into every session. Existing lib/toggle-external.sh enable|disable gstack too coarse — disables whole gstack including infrastructure skills user does want (checkpoint, ship, learn). Profiles give curated middle ground: keep gstack repo installed, hide skills not relevant to this session.
  • Alternatives rejected:
    • Fork SKILL.md files to strip ~70-line gstack preamble — rejected: every gstack upgrade needs re-fork, preamble already degrades gracefully (|| true) when gstack/bin/ unavailable. Hiding skill cheaper than rewriting.
    • Per-skill toggle via claude plugin enable/disable — rejected: gstack skills not marketplace plugins, symlinks owned by skills-external/gstack/. CLI no reach them.
    • Disable via removing symlinks (rm + recreate on enable) — rejected: lossy if user has local edits, re-creation requires running gstack own setup. Move-based toggle preserves symlink intact.
    • Auto-toggle plugins (ui-ux-pro-max) and MCPs as part of set — rejected: those affect global Claude Code state, may carry API keys (magic). Keep advisory; user runs CLI command knowingly.
    • Build giant gstack-profile CLI wrapping gstack/bin/* directly — rejected: scope creep into gstack internals. Repo already has own toggle infra (lib/toggle-external.sh); profile.sh sits alongside as finer tool.
  • Caveats:
    • Profiles do NOT change gstack/bin/ infrastructure — preamble in disabled skills still references it, re-enabling restores normal behavior. No telemetry/learnings data touched.
    • cmd_set only auto-disables skills returned by gstack_skills() (those with SKILL.md under skills-external/gstack/*/). Personal skills (real dirs in skills/) never auto-disabled by set — only added back if listed in profile.
    • cmd_current returns "full" when nothing disabled, even if profile happens to be 100% covered by current state. Active-profile heuristic requires at least one gstack__* entry in skills-disabled/ so we no lie about profile being "set" when no set ever ran.
    • Personal skills use external-style move (no gstack__ prefix) so name-collision with gstack skills cannot happen during disable.
  • Reference: lib/profile.sh, lib/profiles/{design,dev,qa,audit,minimal}.profile, skills/profile/SKILL.md, agents/plugin-advisor.md (DETECT block + TOGGLING EXTERNAL TOOLS section), Makefile targets profile*, lib/toggle-external.sh header pointer.

BDR-008 — Profile system v2: extend to plugins + MCPs + CLIs (web/seo/web-full/backend)

  • Date: 2026-05-04
  • Status: accepted
  • Decision: extend profile.sh to actually toggle Claude plugins (claude plugin enable|disable <name>@<marketplace>) and MCP servers (delegated to lib/toggle-external.sh for magic MCP, advisory for others), add CLI status reporting. New profile syntax uses plugin@<marketplace> so script knows where to enable from. New profiles shipped: web (frontend website), seo (SEO/GEO/W3C audit), web-full (web + seo combined), backend (API/system dev — no design, no SEO). Reverted v1 decision (BDR-007 alternative #4 "advisory only for plugins/MCPs"): user explicitly asked for actual toggling so set web actively enables ui-ux-pro-max + magic, set seo actively disables ui-ux-pro-max. Always-on plugins (caveman, security-guidance, superpowers) protected by both allowlist (MANAGED_PLUGINS) and denylist (PROTECTED_PLUGINS).
  • Why: v1 profiles only managed skills (symlink toggle). User feedback: "active TOUT le splugins necessaire pour tel profile et desactive les autre". Pure-skill toggling left ui-ux-pro-max/magic always loaded regardless of profile, passive token cost no drop as much as expected when switching to non-design profile. Auto-toggling plugins shifts design from "show me right skills" to "set up right session" — closer to what user actually wants.
  • Alternatives rejected:
    • Keep plugins advisory + add --apply-plugins flag — rejected: user has to type flag every time, defeats "switch profile to switch context" workflow.
    • Disable ALL non-listed plugins (including third-party user-installed ones) — rejected: too aggressive. Profile system has no business touching plugins user installed for own reasons. Solution: explicit MANAGED_PLUGINS allowlist (currently 3 entries) — script touches only those.
    • Treat MCPs identically to plugins (auto-toggle any MCP) — rejected: MCPs typically need env vars / API keys / specific commands. Auto-registering with wrong config produces broken MCPs (LRN-006). Compromise: auto-toggle ONLY magic because already have its config in lib/toggle-external.sh. Other MCPs stay advisory.
    • Track plugin state across set/reset cycles, restore on reset — rejected: complexity not worth it. reset re-enables gstack skills only. To re-enable managed plugin, user runs apply <profile> or explicit claude plugin enable command. Documented in info line printed at end of reset.
  • Caveats:
    • MANAGED_PLUGINS hardcoded — adding new toggle-managed plugin requires editing profile.sh. Acceptable for now (3 entries, rarely changes); revisit if grows.
    • claude plugin enable returns success even for already-enabled plugins, parser greps for "enabled|already" in stdout/stderr. Works on current Claude CLI; brittle if CLI rewords messages. Acceptable risk.
    • current heuristic now counts installed (CLI status) as available. Without that, profiles listing CLIs would never reach 100% match. Tiebreaker: when two profiles tie on %, larger total wins (web-full > web > design when all are 100%).
    • cmd_show widened TYPE column to 30 chars to fit plugin@ui-ux-pro-max-skill without breaking alignment.
    • mcp magic toggle delegates to lib/toggle-external.sh enable magic which requires MAGIC_API_KEY in .env. If key missing, profile.sh prints info line and continues — rest of profile still applies.
  • Reference: lib/profile.sh (MANAGED_PLUGINS/PROTECTED_PLUGINS arrays, skill_status plugin@/cli/mcp branches, enable_skill/disable_skill plugin@ + mcp branches, cmd_set plugin disable loop, cmd_current available-counting), lib/profiles/{web,seo,web-full,backend}.profile, refined lib/profiles/{design,dev,qa,audit}.profile (use plugin@<marketplace> syntax + cli entries), skills/profile/SKILL.md (updated profile table + mechanism table), agents/plugin-advisor.md (extended profile recommendation table).

BDR-009 — Mandate caveman format on .claude/memory/ registries

  • Date: 2026-05-05
  • Status: accepted
  • Decision: all writes to .claude/memory/*.md (decisions, learnings, blockers, journal, evals) MUST use caveman style — drop articles (a/an/the), drop filler (just/really/basically/actually/simply), fragments OK, short synonyms (big not extensive, fix not "implement a solution for"). Keep technical terms exact, code blocks unchanged, error messages quoted exact, IDs (BDR-XXX, LRN-XXX, BLK-XXX, EVAL-XXX) and dates unchanged. Pattern: [thing] [action] [reason]. [next step]. Rule added to CLAUDE.md "Memory registries" section. Applied retroactively to existing 5 registries via /caveman:compress. Pre-compression backups saved as *.original.md (gitignored).
  • Why: registries loaded every session start (per CLAUDE.md "Session start" step 1) — every token compressed cuts permanent input cost. Measured ~40% input-token reduction across 5 files (164→97 lines on average per registry). Caveman style preserves all technical substance (code, IDs, error strings, refs) while dropping prose padding that no engineer needs at re-read. Rule mirrors English-only rule that already governs registries — both about read-efficiency, not aesthetics.
  • Alternatives rejected:
    • Compress only on new entries, leave existing prose untouched — rejected: every session-start still pays 40% prose tax on legacy entries (largest part of file). Mixed-style file harder to scan than uniform compressed file.
    • Use lighter compression (drop only fillers, keep articles) — rejected: half-measure. Caveman lite saves ~15%, full saves ~40%. Cost identical (one /caveman:compress run).
    • Move registries to JSON/YAML for max density — rejected: registries are narrative (BDR rationale, LRN context). YAML/JSON would lose nuance, force schema rigidity. Caveman keeps prose readable, just compressed.
    • Skip rule, rely on writers to compress organically — rejected: untested writers (skills, future agents) revert to verbose prose. Explicit rule + caveman-mode-active hook ensures consistency without per-skill enforcement.
  • Caveats:
    • Code blocks, error strings, commit refs, IDs, dates, file paths MUST stay byte-exact — caveman applies to prose only.
    • User-facing CAPITALIZE prompts may stay verbose / mirror user language; rule applies only to written entry.
    • *.original.md backups gitignored (BDR-009 commit 639486a) — recoverable via git history of pre-compression commit.
    • Existing registries entries compressed in commit e4a9259; new entries written caveman from start (BDR-009 itself is first such entry).
  • Reference: CLAUDE.md "Format — registries ALWAYS caveman" section, commits 520188a (rule added), e4a9259 (5 registries compressed), 639486a (gitignore backups).

BDR-010 — Gate GEO independently at ≥17/20 in client-handover pipeline

  • Date: 2026-05-07
  • Status: accepted
  • Decision: client-handover gates SEO classique AND GEO (IA) independently — both must reach ≥17/20. Was: combined display only, gate fired on first /20 line found (de facto SEO classique alone). Now: ALL_PASS = (SEO_AFTER ≥ 17) AND (GEO_AFTER ≥ 17) AND (HARDEN_AFTER ≥ 17) AND (VALIDATE_AFTER ≥ 17 OR SKIPPED). SEO subagent re-dispatched if either axis below threshold (same agent fixes both). Score table + roadmap + client doc §4 split rows accordingly.
  • Why: handover deliverable claims "site ready" — bar must hold on classical search (Google/Bing) AND AI search (ChatGPT/Perplexity) given AI traffic growth. Combined gate (e.g. global pondéré ≥17) lets GEO stay weak while combined passes — false confidence shipped to client. Independent gates close gap.
  • Alternatives rejected:
    • Gate on Score global pondéré ≥17 only — rejected: SEO=20 + GEO=10 → global=18 → passes despite GEO=10. Same false-confidence issue.
    • Keep GEO informational (Phase A initial design) — rejected: breaks "every gated audit ≥17 or stop" rule. Two-tier system (gated vs informational) confuses client + breaks score-table semantics.
    • Lower GEO threshold to ≥15 — rejected by user: weakens signal. Real fix is optimize GEO, not lower bar.
    • Split into two parallel subagents (one SEO, one GEO) — rejected: /seo skill runs both inside one envelope-merge dispatcher. Splitting at handover layer duplicates context discovery (STEP 0) + doubles wall-clock.
  • Caveats:
    • GEO ≥17 hard on existing sites — most lack llms.txt, Speakable/QAPage Schema, entity SEO (sameAs/Wikidata @id), TL;DR/Q→A content shape. Expect more fix-loop iterations on GEO than SEO. Override option C still per-axis with explicit user consent.
    • SCORE_GEO_AFTER = "UNKNOWN" treated as fail — legacy single-score SEO.md triggers re-dispatch with explicit demand for both labeled lines (Score SEO (classique) : X.X / 20 + Score GEO (IA) : X.X / 20).
    • Backward compat split: extract_score_labeled SEO uses allow_fallback=yes (legacy single-score parses as SEO classique); GEO uses allow_fallback=no (no silent duplicate of SEO score).
    • Loop logic axis-aware: while (SEO < 17 OR GEO < 17) AND iter ≤ MAX. Re-dispatch prompt labels both scores with PASS/FAIL + lists axis-specific fixes (SEO: meta/canonical/sitemap; GEO: llms.txt/Schema AI/entity SEO).
  • Reference: commit 5569a80, agents/client-handover-writer.md (STEP 3 extract_score_labeled, STEP 4 axis-aware loop + re-dispatch prompt, STEP 8 gate rule + score table + threshold strictness, STEP 12 §4 client doc table), skills/client-handover/SKILL.md.

BDR-011 — Client handover deliverable: 4-chapter structure + ZenQuality branded HTML/PDF

  • Date: 2026-05-07
  • Status: superseded by BDR-013
  • Decision: client handover doc restructured to 4 chapters — §1 Ce qu'il fallait faire (et pourquoi) (briefing+motivation, 100–180 words), §2 Ce qui a été fait (lay summary, ≤300 words hard cap, zero jargon, no internal tool/skill names), §3 Ce qui vous reste à faire (action-only checklist with cadences), §4 Détails techniques (pour les curieux) (scores, key choices, phases, optional glossary — internal labels allowed here only). Plus optional §5 (external platforms, web), §6 (build & deploy). Replaces old 9-section structure. Output now triple: LIVRAISON.md (editable source) + LIVRAISON.html (always, branded) + LIVRAISON.pdf (when PDF engine on host). HTML/PDF use ZenQuality identity — green palette #1A3A25 / #2D5A3D / #4A7C59 / #87A878, cream BG #F5F0EB, fonts Inter (body) + Playfair Display (headings), cover page with logo + tagline "La sérénité numérique, la qualité en plus", running header (project name) + footer (page N/M, ZenQuality — zenquality.fr). Renderer cascade: MD→HTML via pandoc > python markdown > npx marked; HTML→PDF via weasyprint > wkhtmltopdf > chromium > headless Chrome. STEP 15 enforces gates before render: chapter 2 word count ≤300 (wc -w) AND forbidden-token grep on chapters 1–3 (no /seo, /harden, /validate, /cso, seo-analyzer, SEO.md, SCORE_*, etc.).
  • Why: client reads top-down, may stop after §2 — old 9 sections diluted the read. Bare markdown unreadable by non-tech client. Branded PDF = professional deliverable matching company identity (ZenQuality), suitable to email/print/sign. Per-section gates prevent regression to skill-name leaks or jargon bloat.
  • Alternatives rejected:
    • Keep 9-chapter structure + bolt PDF wrapper on top — rejected: doesn't fix dilution + leak risk; client still scans through "Lessons learned (optional)" / "Pour aller plus loin" before useful actions.
    • Render PDF only (no HTML intermediate) — rejected: no fallback if engine missing; HTML doubles as browser preview + manual print-to-PDF route. Triple output (md + html + pdf) covers all cases.
    • Single PDF engine (e.g. weasyprint only) — rejected: assumes Python installed; cascade gives graceful degradation. Order chosen: weasyprint (best CSS), wkhtmltopdf (mature), chromium (always-bundled on dev hosts).
    • Pandoc with custom template only — rejected: pandoc often not installed (was missing on this host); shell cascade with multiple converters more portable.
    • Soft 300-word target — rejected: without hard wc -w gate, drift inevitable. Cap+gate forces rewrite when over.
  • Caveats:
    • Word-count + leak gates run at STEP 15 after synthesis, not during. Worst case: re-write step needed. Acceptable trade-off vs in-flight enforcement (would require word counter inside agent prompt — fragile).
    • ZenQuality logo URL hardcoded as https://zenquality.fr/logo-horizontal.svg; LOGO_URL env var allows local file override (bake into PDF for offline robustness if branding changes / SVG breaks).
    • PDF cascade detects via command -v only — assumes engines on $PATH. Custom installs need $PATH adjusted before invocation.
    • Bash heredoc + stdin-pipe collision bug in v1 (silent empty output) — fixed via env-var pass-through (LRN-012).
    • Renderer always outputs HTML + tries PDF; on PDF failure exits 2, prints install hints. STEP 16 reports PDF: NOT GENERATED with hints in final report.
    • Optional glossary in §4.4 listed terms (HSTS, CSP, WCAG, Schema.org, llms.txt, SEO/GEO) — only renders if ≥4 of these appear in §4 body.
  • Reference: commit e06b52a, agents/client-handover-writer.md (STEP 12 4-chapter doc structure + tone rules, STEP 15 word-count + leak gates, STEP 16 RENDER pipeline, STEP 17 final report), skills/client-handover/scripts/handover-to-pdf.sh (cascade renderer), skills/client-handover/resources/branding/zenquality.css (ZenQuality print stylesheet), skills/client-handover/resources/branding/zenquality-template.html (HTML wrapper with placeholders).

BDR-012 — client-handover cover: white bg + green accents + PNG logo default

  • Date: 2026-05-07
  • Context: original .cover CSS used cream --white-cream (#F5F0EB) bg + 8mm green stripe top. Washed out. SVG logo logo-horizontal.svg blended into cream bg = low contrast. User feedback: "couleur du fond n'est pas bon", "utiliser une icone non white". Tried green-dark bg first (rejected — too heavy for client-facing doc, hurt readability of long meta block).
  • Choice: .cover bg → --white-pure with two subtle radial tints (sage top-right rgba(135,168,120,0.18), green-forest bottom-left rgba(45,90,61,0.06)). Body text → --black-deep. Title --black-deep. Eyebrow/meta labels/footer → --green-forest (medium green). Meta border-left + meta-strong → --green-forest. Removed .cover::before 8mm stripe. Default LOGO_URL → https://zenquality.fr/assets/logo-horizontal-1024.png.
  • Alt rejected: (a) cream #F5F0EB bg — washed-out, original problem. (b) solid green-dark bg — too heavy, hurt long-text readability, felt like marketing brochure not deliverable. (c) generic white + black — no brand signal.
  • Why: light theme with green accents matches zenquality.fr without overpowering. White bg keeps long client-facing text readable. Green-forest on white = WCAG AA contrast + brand cue. Subtle radial gradients add depth without weight.
  • Status: shipped.
  • How to apply: ZenQuality client-facing print docs default to white bg + green-forest accents. Body interior keeps cream --white-cream as accent (code blocks, blockquote bg) — not as page bg. Solid green-dark reserved for marketing covers, not deliverables.
  • Reference: skills/client-handover/resources/branding/zenquality.css .cover block (line 71-86 bg, 119-149 typography); scripts/handover-to-pdf.sh line 107 (LOGO_URL default); agents/client-handover-writer.md line 1218-1222 (doc updated).

BDR-013 — client-handover: 6-chapter doc — promote scores §2 + NAP §4

  • Date: 2026-05-11
  • Status: accepted (supersedes BDR-011 4-chapter structure)
  • Decision: deliverable restructured 4→6 chapters. §1 brief+why (100–180 words). §2 NEW = score table (avant/après) promoted from old §4 technical annex to top of doc. §3 = lay summary ≤300 words zero jargon (formerly §2). §4 NEW = NAP single-source-of-truth table (Nom/Adresse/Téléphone/Email/Catégorie/Description courte/Horaires) promoted from §7 annex. §5 = action checklist by cadence (formerly §3). §6 = tech details for curious (formerly §4, score table removed — now in §2). §7/§8 still optional annexes (external platforms, build+deploy).
  • Why: local-business client opens deliverable, scans first 30s — needs immediate visual proof of impact (scores §2) before reading prose. Tested with handover clients: scores up-front converts "what did I pay for?" doubt within 30 seconds. NAP §4 prerequisite chapter before §5 todo list — client's todos reference NAP values constantly ("create Google Business with these values"); if NAP buried in §7 annex, client scrolls deep mid-todo, types inconsistent values across platforms, degrades Google NAP-consistency signal.
  • Alternatives rejected:
    • Keep 4-chapter + add score sentence inside §2 prose — rejected: lost the visual proof-of-impact moment, table reads stronger than prose.
    • Keep NAP in §7 external-platforms annex — rejected: client types 10 different descriptions/addresses while working through §5 todos before reaching §7.
    • Compact 3-chapter doc with scores+NAP inline — rejected: too dense, kills lay-summary flow + chapter 3 word-count gate harder to enforce.
    • Two-doc deliverable (summary PDF + NAP/todos PDF) — rejected: doubles attachments, client opens only one.
  • Caveats:
    • Forbidden-token grep gate at STEP 15 now covers §1–§3 (was §1–§3 already, no change). §4 NAP table contains only client input placeholders [À COMPLÉTER] — no tool/skill leak risk.
    • Pandoc requires gfm+gfm_auto_identifiers extension to resolve [§4](#nap) internal links (LRN-014).
    • §2 score lecture-rapide must stay plain French — numbers OK but no internal labels (Score SEO classique allowed because vulgarized; seo-analyzer/SEO.md forbidden).
  • Reference: commit b15b275, agents/client-handover-writer.md (chapter list lines 20–60, prose framing §2 + §4 rationale lines 30–55, STEP 12 template), skills/client-handover/scripts/handover-to-pdf.sh line 121 (pandoc gfm_auto_identifiers).

BDR-014 — Personal SKILL.md descriptions: "Use when [triggers]…" pattern + 1024-char spec limit

  • Date: 2026-05-11
  • Status: accepted
  • Decision: all personal SKILL.md descriptions must follow Use when [specific triggering conditions and symptoms] pattern. Workflow summaries forbidden in description (e.g. Ship feature: design → plan → implement → review). Frontmatter total ≤1024 chars per agentskills.io spec. Workflow detail belongs in SKILL.md body, not description. Triggers list compressed and deduped.
  • Why: superpowers:writing-skills documented (with test evidence) that workflow summaries in description create shortcut risk — Claude reads description, treats it as the skill, and skips reading the body. Test case: description "code review between tasks" caused Claude to do ONE review when skill flowchart had TWO. Removing workflow summary → Claude read flowchart, did 2 reviews. Description's job is to TRIGGER retrieval, not to BE the skill. 1024-char cap is the platform spec limit (agentskills.io/specification); 5 personal skills (client-handover, doc, seo, geo, validate) were 1050–1920 chars — non-compliant.
  • Alternatives rejected:
    • Free-form descriptions (status quo) — rejected: drift + shortcut risk, 5 skills already spec-violating.
    • Hard cap ≤500 chars per writing-skills target — preferred for new skills but hard retrofit on multi-language trigger lists (FR+EN keywords blow past 500). Use 1024 as enforced ceiling, ≤500 as aspirational.
    • Per-skill judgment with no rule — rejected: inconsistent, no enforcement gate.
    • Move trigger keywords to body — rejected: triggers in description is what Claude uses for routing; body content doesn't help routing.
  • Caveats:
    • Orchestrators still describe orchestration role explicitly (e.g. client-handover: "Multi-agent orchestrator: dispatches the client-handover-writer agent which spawns parallel /seo + /harden subagents") — that's role identification, not workflow summary.
    • Other 10 personal skills (analyze, bugfix, code-clean, commit-change, feat, hotfix, plugin-check, refactor, status, skills-perso) still partially summarize workflow but stay under 1024 chars. Not retrofitted in this pass — flagged for follow-up only if shortcut symptoms observed.
  • Reference: commit 1da6a31, 8 SKILL.md files (client-handover, doc, geo, seo, validate, ship-feature, init-project, onboard), superpowers:writing-skills "CSO" section, agentskills.io/specification.

  • Date: 2026-05-12
  • Status: accepted
  • Decision: 5 dirs in ~/Documents/claude/skills/ whose SKILL.md symlinks point to non-existent gstack paths (skills-external/gstack/<name>/SKILL.md missing) — benchmark-models, context-restore, context-save, make-pdf, plan-tune — are excluded from /darwin-skill baseline + optimization. Marked status=error in results.tsv with note broken gstack symlink — out of scope. NOT scored, NOT optimized, NOT deleted.
  • Why: darwin-skill constraint #1 forbids changing a skill's core function — implies external/gstack-owned skills are out of scope. Symlinks resolve to skills-external/gstack which is third-party submodule. Plus the targets are broken — gstack's actual layout (benchmark/, health/, qa/, etc.) doesn't include these 5 names, suggesting upstream rename or removal. Repairing them is a separate triage task, not darwin's concern.
  • Alternatives rejected:
    • Fix symlinks first then darwin-optimize → out of scope, blocks the optimization queue on gstack archaeology.
    • Score them with FILE_NOT_FOUND and include in averages → biases stats, mixes signal with infrastructure issue.
    • Optimize the gstack source files directly → external ownership, never modify.
    • Delete the broken symlinks → would obscure that the user once expected these to exist; leave for triage.
  • Caveats:
    • If/when symlinks are repaired (real gstack target exists), re-run baseline to bring them in scope.
    • Bigger picture: benchmark-models looks like a deliberate rename of gstack's benchmark to disambiguate from the gstack-skill called /benchmark. Could be a planned migration that stalled. Worth a one-line ticket separate from darwin.

BDR-016 — doc-syncer: README AUTO+unconditional, DEPLOY.md prod-only + 14-section VPS template

  • Date: 2026-05-15
  • Status: accepted
  • Decision: agents/doc-syncer.md STEP 5/6/8/A4 rewritten. README creation now AUTO + unconditional in both interactive and AUTO modes. Validation gate offers only yes or edit for README — no skip/no. Any project-level "no README" opt-out (e.g. CLAUDE.md "Exceptions: No README at scaffold") gets struck through during same patch. README template expanded: features, Stack, Quick start (dev), Verifying a change, Build & deploy, Documentation, License — all rendered from real manifest/.env.example/scripts data, no placeholders. DEPLOY.md becomes prod-only, expanded into 14-section VPS-deploy structure (topology table, env vars, VPS provisioning, two-layer firewall, Docker tuning, first-time setup, routine deploys, persistence, backups, TLS, observability, hardening, rollback, runbook). Dev quick-start lives ONLY in README "Quick start (dev)" section; mixed dev/prod DEPLOY.md flagged as drift, dev content proposed for move to README during same patch round.
  • Why: README opt-out makes repo look abandoned to anyone landing on it — universal "always render" beats opt-in. Mixed dev/prod DEPLOY.md was drift source: devs read DEPLOY for local setup, ops read DEPLOY for prod, both edit independently, conflicts pile up. Clean audience split — README = dev + features audience, DEPLOY = ops + SRE audience — gives each doc one owner mental model. 14-section VPS template mirrors real Scaleway/Hetzner/OVH/DO/Vultr deploy shape (reference: Scaleway DEV1-S walkthrough) so the doc maps 1:1 to the runbook ops actually execute.
  • Alternatives rejected:
    • Keep README gated on HUMAN approval (status quo) — rejected: opt-outs proliferated, repos shipped with no README. Friction wins.
    • Single ARCHITECTURE+DEPLOY doc — rejected: mixed-audience doc is the drift source we're fixing. Don't recombine.
    • Trim DEPLOY.md to single "Production" section — rejected: real VPS deploys need topology + firewall + Docker tuning + backups + TLS + observability. Single section becomes wall-of-text nobody reads.
    • Optional README in AUTO mode (default skip) — rejected: AUTO mode purpose is friction removal. README is most-missed doc; auto-render it.
    • Auto-write the README without surfacing draft — rejected: rendered draft still shown at validation gate so user can edit before write. "No skip" ≠ "no review".
  • Caveats:
    • Real-project-data rule still binds — license = "Not specified — set one before public release" (explicit gap, not fabricated SPDX).
    • 14-section DEPLOY template drops sections that don't apply (e.g. "Managed DB" if no DB). Template = ceiling not floor.
    • If DEPLOY_COMPLEXITY == TRIVIAL, no DEPLOY.md created — deploy stays in README. Threshold = no Docker + no compose + no fly.toml + no k8s + no scripts/deploy.* → trivial.
    • Existing DEPLOY.md with Local development / Dev setup section → surfaced as drift, content moved to README, section removed from DEPLOY. Not a silent rewrite.
  • Reference: commit 7ee9b42, agents/doc-syncer.md STEP 5 (README mandatory clause + template lines 223–335), STEP 6 (14-section DEPLOY.md template lines 338–541), STEP 8 (validation gate yes/edit for README, yes/no/edit for HUMAN), STEP A4 (AUTO MODE README-missing → SIGNIFICANT). Linked to doc-syncer-two-doc-split (LRN-019).

BDR-017 — full profile = web-full + plan + dev superset for /init-project MVP

  • Date: 2026-05-18
  • Status: accepted
  • Decision: New lib/profiles/full.profile covers brainstorm → design → architecture review → scaffold → implement → ship → audit pipeline in one profile. Superset of web-full (design + dev + seo/geo/validate/harden + perf) plus plan-mode reviews (office-hours, plan-ceo/eng/design/devex-review, autoplan), full dev stack (investigate, code-clean, land-and-deploy, setup-deploy, codex), full audit (cso), full QA (qa), docs (doc, document-release), session hygiene (close, prune-memory, status, learn, retro, careful/freeze/unfreeze/guard), and pr-review-toolkit plugin + gsd CLI. Sentinel "full" in cmd_current renamed to "none" to avoid collision with profile name.
  • Why: /init-project orchestrates 13 steps that touch nearly every skill family — brainstorm, plan, design, scaffold, implement, ship, audit. Existing profiles only cover a slice (web-full = website end-to-end but no plan/dev breadth, dev = code only, audit = audit only). Without a maximal profile, init-project users must either run reset (everything on, noisy) or piece together apply web-full && apply dev && apply audit (3 commands). One named profile = one command = right tool for MVP scaffolding sessions.
  • Alternatives rejected:
    • Extend web-full to absorb plan + dev — rejected: web-full is "production website end-to-end"; init-project covers non-website projects too (CLI, library, backend MVP). Different semantic.
    • Make init-project profile-agnostic (just run with all skills enabled) — rejected: noise. /profile reset exists for that; named profile gives explicit signal "this session is MVP-scale".
    • Multiple sub-profiles chained — rejected: 3 apply commands less ergonomic than one set full; profile-of-profiles not supported by current schema.
  • Caveats:
    • full excludes a few rarely-used gstack skills (devex-review, pair-agent, gstack-upgrade, skills-perso). set full will disable those; user can apply <profile> after to add back.
    • Sentinel rename "full" → "none" is breaking for any tooling that grepped cmd_current output for literal "full". No known consumers in this repo.
  • Reference: commit message references lib/profiles/full.profile (new), lib/profile.sh:421 sentinel, skills/profile/SKILL.md table row. Linked to profile-sentinel-collision (LRN-020).

BDR-018 — profile gstack on|off verb keeps active-profile label

  • Date: 2026-06-02
  • Status: accepted
  • Decision: New cmd_gstack() in lib/profile.sh. gstack on = re-enable all parked gstack (move skills-disabled/gstack__* back), DON'T touch .active-profile. gstack off = disable gstack skills not in active profile (errors if active=none). Wired into main() dispatch + usage() + header block + skills/profile/SKILL.md (argument-hint + examples + output-policy).
  • Why: User wanted central command for "enable all gstack" + "disable gstack not needed by profile". Both ops existed (reset, set) but reset clobbers .active-profile to "none" — loses profile context in statusline. New verb does same skill-toggle WITHOUT clearing label, so user layers full gstack on top of current profile (e.g. dev) and statusline still reads dev.
  • Alternatives rejected:
    • 3 new profiles (current+gstack, current+gsd, current+gsd+gstack) — rejected: gsd = standalone CLI (not profile-toggleable, always-on, 0 passive token), so 2 of 3 meaningless. full already = current+gstack+gsd advisory. apply already additive.
    • Just document reset/set — rejected: user wanted clearer centralized verb + label preservation.
  • Impl note: extracted 3 shared helpers (enable_all_gstack, disable_gstack_not_in, parked_gstack_count); cmd_reset+cmd_set refactored to reuse (behavior preserved exact, verified by test). See dry-helper-extract-sibling-command (LRN-024).
  • Reference: lib/profile.sh cmd_gstack + helpers, skills/profile/SKILL.md. Linked to full-profile-superset-init-project (BDR-017), gstack-source-only-skills-unlinked (BLK-007).

BDR-019 — Remove disable-model-invocation repo-wide, align skills with CLAUDE.md routing

  • Date: 2026-06-09
  • Status: accepted
  • Decision: Stripped disable-model-invocation frontmatter key from all 19 editable skills/*/SKILL.md. Absent key = default = model invocation ENABLED. 8 were true (blocked model + orchestrator routing: status, plugin-check, analyze, onboard, refactor, init-project, pdf-translate, ship-feature); 11 were false (already enabled, line was noise).
  • Why: true blocked model AND orchestrator from self-routing to those skills — contradicted CLAUDE.md "Skill routing" intent (e.g. "multi-file feature → ship-feature", "refactor → /refactor"). User hit it live: model detected feature intent, wanted ship-feature, couldn't fire. Setting binary (no per-caller granularity) → enabling orchestrator-chaining also enables model auto-fire; accepted as the cost.
  • Alternatives rejected:
    • Keep true on 4 heavy orchestrators (init-project, ship-feature, onboard, refactor) — rejected: "destructive" framing wrong. ship-feature only commits + pushes a feature branch + opens PR (reversible, gated by internal STEPs); no prod deploy (that's land-and-deploy/canary). Real destructive ops (rm -rf, force-push, prod deploy) guarded by careful/guard hooks INDEPENDENT of this flag — verified live (rm -rf blocked this session). Flag bought ~0 data-safety, only suppressed auto-fire (token/time cost) while breaking routing.
    • Remove only the 8 true ones — rejected: leaves 11 noise false lines; uniform removal cleaner.
  • Durability: all 8 ex-true skills are repo-only files (not gstack submodule) → edits not clobbered on gstack upgrade.
  • Reference: 18 skills/*/SKILL.md modified + skills/capitalize/ new. Linked to disable-model-invocation-false-not-blocking (LRN-026).

BDR-020 — /audit-delta design: per-axis SHA markers, always-on fix gate, unreachable-first-run = full report-only

  • Date: 2026-06-11
  • Status: accepted
  • Decision: New skill skills/audit-delta/SKILL.md — recurring multi-axis audit (conformity/errors/deadcode/security) scoped to delta since last run. 3 design choices: (1) state = .claude/audits/audit-delta-state.json, SHA marker PER AXIS (partial runs would desync single marker); (2) approval gate per axis ALWAYS fires — advance pre-auth ("fix what you find") never skips it, findings unknown at request time; user unreachable → audit + report only, no fix, marker still advances; (3) first-run axis + unreachable user → default full codebase report-only, never "from HEAD" (would skip entire existing codebase silently). Axis order fixed security→errors→conformity→deadcode (critical first, session-death safe). Re-verify = same-axis re-audit on modified files + project checks, lint alone insufficient. Built via superpowers:writing-skills TDD (RED 7 gaps / GREEN pass under pressure / REFACTOR 1 hole patched + re-tested).
  • Alternatives rejected:
    • Extend /code-clean or /health — rejected: no recurrence state (health re-scans all, tracks scores not scope; code-clean one-shot), no multi-axis checkbox selection, cost not proportional to delta.
    • 4 separate skills (1 per axis) — rejected: user wants checkbox combo in one run; shared marker protocol + gate + re-verify loop would quadruplicate.
    • Single global marker — rejected: run "security only" then "conformity" → conformity range wrong.
    • Date-based boundary — rejected: drifts on rebase/timezone/amend (baseline agent failure, see LRN-027).
  • Reference: skills/audit-delta/SKILL.md. Linked to periodic-skill-state-file (LRN-027), capitalize-skill (skill TDD precedent, BDR-019 era).

BDR-021 — CLAUDE.md restructure: contradiction purge, project-specific sections labeled, critical sections never compressed

  • Date: 2026-06-12
  • Status: accepted
  • Decision: Full refactor global CLAUDE.md (commit e7e9dac), Fable 5 audit. 4 contradictions resolved (2 graphify sections merged conditional on graph.json existing; "in doubt skip plan" no longer cancels plan mandate — borderline = single-file small obvious change; deviations minor/justified→after vs significant/shaky→before; append-only reconciled with /prune-memory). 3 dead refs fixed (/caveman-compress, design-gate → ~/.claude/lib/ portable, LESSONS note). Structure: Tooling & skills + "This repo only" top-level sections — Health Stack/routing/graphify no longer nested under Communication mode. Routing +8 skills + explicit gstack-OFF rule. Compression caveman on workflow/memory/routing ONLY: Architecture decisions + Security stay verbatim — ambiguity there costs more than tokens saved. Net -1471 chars despite added content.
  • Alternatives rejected:
    • Compress whole file incl. Security/Architecture — rejected: precision > tokens on non-negotiable rules; misread security default = real damage.
    • Split global vs repo-specific into 2 files — rejected: symlink setup (~/.claude/CLAUDE.md → repo) means 1 file everywhere; "This repo only" section header cheaper than 2-file sync.
    • Delete graphify section (graph.json absent) — rejected: conditional phrasing keeps rules dormant-but-ready; regenerating graph re-activates without doc edit.
  • Durability: heading "Design work — full toolchain (tiered by scope)" preserved verbatim — design-toolchain-reminder.sh quotes it. Norms 25/80/5/5 unchanged — audit-delta conformity axis cites them.
  • Reference: CLAUDE.md, commit e7e9dac. Linked to audit-delta-design (BDR-020), LRN-029 (exception edits need blanket-rule cross-ref — applied here).

BDR-022 — doc-syncer scoped to public docs only; .claude/ + CLAUDE.md read-only context

  • Date: 2026-06-18
  • Status: accepted
  • Decision: Rewrote agents/doc-syncer.md. Sync targets = PUBLIC docs ONLY: README, INSTALL, CONFIGURE, USAGE, DEPLOY, CONTRIBUTING, CHANGELOG, SECURITY, ARCHITECTURE, LICENSE, docs/**. .claude/** + CLAUDE.md = read-only context: agent MAY read them to understand archi/features/constraints, NEVER modifies them, NEVER lists as targets, NEVER copies their content into a public doc. Removed STEP 4 blocks auditing TODO.md / audits/*.md / decisions-learnings-blockers. Added normative CONVENTIONS (Standard-Readme, Diátaxis doc-type split, Keep a Changelog + SemVer, Conventional Commits). README lean — dropped Status + Project layout, forbids roadmap/todo/internal-state, links to Diátaxis docs not duplicate. Added CLEAN mode (clean arg) → propose removal of out-of-convention sections + copied-.claude/ content from existing public docs, HUMAN-gated. Conserved: stack detection, 14-section DEPLOY gate, validation gate, AUTO/HUMAN tagging, never-invent, AUTO MODE (input auto-mode scope: unchanged → callers unaffected).
  • Alternatives rejected:
    • Keep .claude/ + CLAUDE.md as sync targets (old behavior) — rejected: leaked internal state (TODO/roadmap/decisions) into public-facing docs; doc-syncer writing under .claude/ blurred the read-only registry boundary (registries are /prune-memory-curated, not doc-synced).
    • Drop only .claude/, keep CLAUDE.md writable (old strike-through README opt-out) — rejected: CLAUDE.md = agent config not public doc, absent from the modifiable-targets list; uniform read-only treatment cleaner.
    • Inline config table in README — rejected: violates Diátaxis (CONFIGURE.md = single config reference); README must link, not duplicate.
  • Reference: agents/doc-syncer.md, commit edff761. Extends doc-syncer-readme-deploy-policy (BDR-016, README-AUTO + DEPLOY 14-section — conserved, not superseded).

BDR-023 — Merge /close into /capitalize — 2 modes + TODO reconcile; /close alias

  • Date: 2026-06-19
  • Status: accepted (supersedes /close-creation part of BDR-002)
  • Decision: /close merged into /capitalize. capitalize 2 modes: default (pre-wipe flush) + --ritual (adds 3-question end-of-session reflection; trigger = --ritual flag OR "close"/"ritual" in $ARGUMENTS, OR /close). Both modes dedup (STEP 2) + reconcile .claude/tasks/TODO.md (new STEP 2B). STEP 2B: PASS A done-detection = restraint rule only (flip [ ]→[x] only on clean task↔commit map; partial/umbrella/vague stay unchecked, never guess); PASS B explicit-capture + anti-noise filter (never track commit/deploy/push/release/tag) + orientation-directive→decisions.md (BDR) routing. Ritual answers go thru dedup, footer shows existing ID — unlike legacy /close (wrote fresh). STEP 3 gate gains separate TODO block; journal+handoff report TODO ops. TODO stays plain prose (caveman = registries only). /close kept = thin alias → /capitalize --ritual, zero duplicated logic.
  • Why: /close + /capitalize overlapped (both flush session memory), /close never deduped → re-logged on re-run. 1 skill 2 modes kills dup + gives /close dedup; TODO reconcile = new capability. Alias file (not merged-triggers-only) because /close resolves by directory name — deleting dir breaks literal /close command.
  • Alternatives rejected:
    • Merged-triggers-only (drop close dir, fold triggers into capitalize desc) — breaks literal /close command (dir-name resolution).
    • Keep 2 separate skills — duplication persists + /close never dedups.
  • TDD: built via superpowers:writing-skills. RED v1 baseline too easy (passed). RED v2 (pressured fixture: semantic dup + ambiguous umbrella task + parasite-as-task + orientation directive + rushed prompt) failed on anti-noise (folded push/tag into TODO) + invented subtask + no approval stop. GREEN passed. Gate STOP itself UNTESTED (non-interactive harness printed gate then proceeded "all approved") — flagged in skill Red flag + TDD note; verify on first real use.
  • Reference: skills/capitalize/SKILL.md, skills/close/SKILL.md, commits 9dc2b83 (skill) + be0f047 (docs routing) + 765e9d7 (PASS A trim). Linked to BDR-002 (close created), BDR-019 (capitalize created), LRN-031 (skill-value lesson).

BDR-024 — profile show --plain = claude-free parse contract for the design gate

  • Date: 2026-06-19
  • Status: accepted
  • Decision: added profile.sh show <name> --plain → one type<TAB>name per line, grouped by type (gstack/external/personal/plugin/mcp/cli order), NO status, ZERO claude calls, derived purely from the .profile. Bare show keeps runtime status (human value) + grouped layout; --plain = machine path. Canonical names verbatim (magic stays magic; plugin marketplace plugin@<mp> collapsed to category plugin).
  • Why: upcoming design gate must derive "which profile contains tool X". Needs fast + hook-safe parse. Bare show calls claude plugin list/claude mcp list per plugin/mcp entry → slow + fails in non-terminal/hook context (degrades to "disabled").
  • Alternatives rejected:
    • Gate re-reads .profile directly — duplicates read_profile parsing in the gate; two parsers drift.
    • Gate parses full show output — pays claude calls per plugin/mcp, fragile in hook context.
  • Reference: lib/profile.sh cmd_show (+ --plain branch), skills/profile/SKILL.md, commit 5776195. Linked to BDR-018 (prior profile.sh command addition).

BDR-025 — Design gate = profile-based; remedy always /profile design; magic required-but-manual; unknown → fail-visible; claude resolved via PATH-repair

  • Date: 2026-06-21
  • Status: accepted
  • Decision: design-tool-gate.sh checks whether the design profile's design-core tools are active and, if not, points at ONE command — /profile design, never an atomic per-tool toggle. tier = profil: every non-trivial tier (Build / design-system / review) draws from the one design profile (a superset of all tiers) → the gate checks that profile, so ZERO hardcoded tier→tools list. Gate scope = the # GATE-BLOCK: allowlist in design.profile (only real design tools trip; bundled browser/plan/shotgun/graphify ignored). Structure + types from profile.sh show design --plain (BDR-024 contract); per-tool state per channel (skill symlink / claude plugin list / claude mcp list / command -v), mirroring profile.sh:skill_status(). Three outcomes: blocking/manual → INCOMPLETE exit 10; unknown-only → READY-BUT-UNVERIFIED exit 11 (fail-visible); else READY exit 0. magic = required-but-manual class: TRIPS the gate (NOT advisory), names the MAGIC_API_KEY step. claude resolved via ensure_claude_on_path() (probe known dirs + nvm glob sort -V | tail -1 = newest, prepend the bin dir carrying claude AND node) — because command -v claude depends on PATH carrying the nvm bin, absent in a sanitized subshell/hook; integral to the final gate design, not a detail.
  • Why: single source of truth = profile system → no CLAUDE.md tier→tools dup (P3/P5 just removed it), no tool→profile map to drift. Credible gate: trips only on real design tools, not bundled infra → not ignored by reflex. magic is the load-bearing design tool, so silence on it would defeat the gate's purpose. Gate runs from hooks/skills where PATH may be sanitized → robust claude resolution is required for it to verify at all.
  • Alternatives rejected:
    • Hardcoded tier→tools list — reintroduces the CLAUDE.md dup just removed; drifts when a tool is added to a profile.
    • magic advisory (mention, don't trip) — fail-OPEN on the very tool the gate exists to catch.
    • Strict fail-closed on unknown — false blocks when claude merely slow/unreachable → gate gets ignored. fail-VISIBLE (exit 11) chosen.
    • Depend on command -v claude alone — fails in sanitized-PATH hook → unknown. Proven by PATH=/usr/bin:/bin test (magic-on → READY/0, magic-off → INCOMPLETE/10).
  • Reference: lib/design-tool-gate.sh, lib/design-gate.md, lib/profiles/design.profile (# GATE-BLOCK:), commits 3eefb8a / 4d19135 / f963318. Linked to BDR-024 (the --plain parse contract this consumes), LRN-036 (command -v PATH dependence, the real cause), LRN-037 (proven on the real subject in real context).

  • Date: 2026-06-21
  • Status: accepted
  • Decision: real secret lives in ~/.claude/.env (outside git tree); repo/.env = symlink → it. source "$REPO/.env" follows symlink → ZERO change to any read path (toggle-external.sh load_env, install-plugins.sh check, gate). link.sh link_env() defensive: links only when repo/.env absent or already the right link; a residual REAL repo/.env is left untouched with a migrate hint — never clobbered, so the secret can't be destroyed. Idempotent. .gitignore hardened to .env + .env.* + !.env.example. Messages point at ~/.claude/.env (canonical edit location).
  • Why: secret never enters the git tree — not as content (it's a link) nor by accident (gitignored). Even a stray git add . can't stage the real key. Repo stays usable: symlink visible/editable from repo. Read paths follow the link → no script logic changed.
  • Alternatives rejected:
    • Secret in repo/.env, gitignored (status quo) — one git add -f or .gitignore slip leaks it; secret sits in tree.
    • Scripts read ~/.claude/.env directly — symlink redundant, rewrites every read path, loses repo-local visibility.
  • Reference: link.sh link_env(), .gitignore, lib/toggle-external.sh, install-plugins.sh, .env.example, commits 131d0bc / f9cc866. Linked to BDR-025 (magic's MAGIC_API_KEY, consumed by the gate's required-but-manual class).
  • Update 2026-07-02 (incident — copies of secrets): claude mcp add --env MATERIALIZES the key into ~/.claude.json (mcpServers.magic.env) — 2nd live copy OUTSIDE ~/.claude/.env canonical and outside repo deny rules' reach. Audit query printed it into a session transcript → key rotated (21st.dev). Rule: secrets have COPIES (tool configs, transcripts, caches) — protect/audit the copies, not just the canonical; when inspecting MCP config, filter env fields (jq 'del(.. | .env?)'). Same audit: ~/.claude/.env hardened 0664→0600.
  • Update 2026-07-07 (job7 — backup vector closed): ~/.claude.json copy from 2026-07-02 kept re-leaking into ~/.claude/backups/.claude.json.backup.* (native auto-backup, ring-buffer of 5, plaintext) — every backup taken while live file held the value = fresh copy; scrubbing backups alone would recur forever. Closed at the source instead (BDR-057): ~/.claude.json's mcpServers.magic.env.API_KEY rewritten to "${MAGIC_API_KEY}" (Claude Code ${VAR} expansion, confirmed supported at user scope), lib/toggle-external.sh writes the reference form for future enable magic runs, var reaches claude only via a scoped ~/.bashrc wrapper (never the ambient shell). New backups taken after the fix carry the reference, not the value — confirmed empirically (2 of 5 rotating backups mid-fix still had the old value; scrubbed once, not expected to recur). MAGIC_API_KEY itself still needs rotation (this closes the storage vector, not the already-exposed value).

BDR-027 — Minimal npm-via-nvm bootstrap over centralized prereq lib (reverses the reverted approach)

  • Date: 2026-06-23
  • Status: accepted (supersedes the reverted lib/install-prereqs.sh centralization, commit 1ddeed1 removed from history)
  • Decision: the only real bootstrap blocker = npm absent on fresh machine. install.sh now installs current LTS via nvm (v0.39.7 → nvm install --lts) ONLY when node/npm missing (install_node_via_nvm). Keep the inline per-tool prereq blocks in install-plugins.sh (no shared ensure_* lib). Re-add jq inline (Step 1) + doctor.sh fail-level — jq is an active-hook dep that was never installed.
  • Why: a 1-function fallback fixes the actual blocker. Folding 9 prereqs into a 245-line lib was scope-creep for "npm missing"; user reverted it. Inline blocks stay readable + co-located with their step.
  • Alternatives rejected: centralized lib/install-prereqs.sh (commit 1ddeed1 — over-engineered for the real blocker, reverted); leave npm as a hard err (the original bug — aborts before the CLI install).
  • Reference: install.sh install_node_via_nvm, install-plugins.sh Step 1 jq, doctor.sh, commits b6cc8b1 / 2194b11. Linked to BLK-008 (the chromium half of the same fresh-Ubuntu-26.04 session).

BDR-028 — Hand-curated config is install-immutable (auto-revert guard) + de-vendor installer-managed skills

  • Date: 2026-06-23
  • Status: accepted
  • Decision: install-plugins.sh snapshots CLAUDE.md + settings.json + .claude/settings.json at start, restores them on EXIT (trap) → installer never mutates hand-curated config. frontend-design un-tracked (git rm --cached + gitignore skills-external/frontend-design/) — re-synced from the example-skills plugin cache every run, so vendoring = pure churn. npx-skills pollution (/.agents/, /skills-lock.json) gitignored, anchored so our agents/ stays tracked.
  • Why: a fresh make install drifted all 4: graphify clobbered CLAUDE.md (deleted the # This repo only header) + injected aggressive MANDATORY pre-tool hooks; claude plugin install flipped example-skills→true + added plugin-dev; frontend-design diffed on every upstream update; darwin-skill polluted repo .agents/ at project scope. Guard = these files maintained by hand+commit only; gitignore = generated artifacts never tracked.
  • Caveat: guard makes the 3 config files install-immutable — anything the installer SHOULD add must be committed by hand. Safe today: committed settings.json already carries the rtk hook (install skips init). update-all.sh needs no guard (only claude plugin update, no enable flips, no graphify reconfig).
  • Alternatives rejected: git checkout post-install (nukes legit uncommitted edits, depends on git state); surgical JSON/markdown patching (fragile); accept graphify's generic CLAUDE.md (loses curation).
  • Reference: install-plugins.sh guard block + restore_curated_configs trap, .gitignore, commits 51afe9b / 7de8761. Linked to LRN-039.

BDR-029 — Installer auto-fixes gstack browser on OS newer than its pinned Playwright supports

  • Date: 2026-06-23
  • Status: accepted
  • Decision: install-plugins.sh makes gstack's browser work on too-new distros without manual steps. (1) gstack_bump_playwright_if_unsupported() runs before ./setup: if the pinned Playwright's support list lacks the running distro (grep node_modules/playwright-core/lib for the ubuntuXX.04 tag), bun add playwright@latest in the submodule, then ./setup's frozen-lockfile install picks it up + rebuilds the browse binary. Idempotent (skips when already supported). (2) Persist GSTACK_CHROMIUM_NO_SANDBOX=1 to the shell profile, gated on sysctl kernel.apparmor_restrict_unprivileged_userns=1.
  • Why: fresh make install on Ubuntu 26.04 must yield a working gstack browser. Submodule pins Playwright 1.58.2; upstream hasn't bumped; can't wait. Local bump in the installer = "just works" + self-heals after a git submodule update (re-applies next run).
  • Caveats: the installer EDITS the submodule (goes dirty each run on a too-new OS) — invasive, but the user chose it over waiting upstream. bun add playwright@latest could pull a Playwright that breaks gstack's build → non-fatal (./setup fail warns, install continues). The local bump is reset by git submodule update. The .bashrc env can be wiped if the user restores a hand-managed .bashrc (theirs is managed — the first install's lines were already lost that way).
  • Alternatives rejected: PLAYWRIGHT_HOST_PLATFORM_OVERRIDE (fallback build HANGS at extraction — BLK-008); wait for gstack upstream Playwright bump (no ETA); leave browser unavailable (user wanted it); system chromium + executablePath (needs gstack code change).
  • Reference: install-plugins.sh gstack_bump_playwright_if_unsupported() + Step 9 sysctl-gated env, commit 3b8ffb1. Linked to LRN-040, BLK-008.

BDR-030 — gstack skills activated ON-DEMAND per profile, not pre-installed; OFF by default stays

  • Date: 2026-06-23
  • Status: accepted
  • Decision: gstack stays OFF by default (no per-skill symlink in skills/, zero context cost) — but profile.sh set <profile> that LISTS a gstack skill activates it for that profile. enable_skill gstack gained a branch: skill not in skills/ and not parked in skills-disabled/ but present in the skills-external/gstack/<name> submodule → ln -s it into skills/. disable_gstack_not_in() parks it again when an unrelated profile is set. The gstack/bin + browse/dist infra those skills need is created independently by link.sh.
  • Why: user wanted make install self-sufficient AND set full (lists 35 gstack skills) to work without 35 missing — try: bash link.sh warnings, WITHOUT abandoning gstack's OFF-by-default context-cost policy (BDR-029 install comment). On-demand-per-profile threads both: gstack invisible until a profile needs it, then auto-on for exactly that profile. Source of truth = the submodule (gstack_skills() already reads skills-external/gstack/*/SKILL.md), so activation needs no gstack ./setup skill-registration (which this gstack version writes to the WRONG dir anyway — LRN-042).
  • Caveats: the symlink form (skills/<name> -> skills-external/gstack/<name>) differs from what gstack ./setup would create (real dir + symlinked SKILL.md) — fine here because ./setup never populates skills/ in this layout, so no mixed-form collision. Browse RUNTIME still needs the built binary + sandbox env (BDR-029) — on-demand makes the skill DISCOVERABLE, not the browser functional on an unsupported OS. The old "try: bash link.sh" message was wrong (link.sh never creates gstack skills) → replaced with submodule-aware messages.
  • Alternatives rejected: full gstack integration (make ./setup install into skills/) — user picked option 1, too invasive/version-fragile; leave full broken with honest 1-line warning — worse UX; pre-symlink all gstack at install — violates OFF-by-default context policy.
  • Reference: lib/profile.sh GSTACK_SRC + enable_skill gstack branch. Verified: set full → 0 missing, 35 on-demand; minimal↔full cycle re-parks/restores; git clean (gstack symlinks gitignored, LRN-025). Linked to LRN-042, LRN-022, BDR-018 (gstack on/off verb).

BDR-031 — global CLAUDE.md lightening = COMPRESSION, not path-scope / externalization

  • Date: 2026-06-25
  • Status: accepted
  • Decision: lighten the universal global CLAUDE.md (~/.claude/CLAUDE.md, loaded every session in every project) by COMPRESSION only — denser prose, drop name-obvious routing lines, trim decorative whitespace. NOT by path-scoping rules under ~/.claude/rules/, NOT by externalizing sections to on-demand files. Result: 317 → 275 lines.
  • Why: user-level path-scoped rules (paths: frontmatter under ~/.claude/rules/) do NOT load in CC 2.1.190 (#21858, BLK-009) — proven by probe. Conditional/scoped loading is therefore an unreliable lever for this user; compression is the only mechanism that actually cuts every-session token cost without depending on the broken feature.
  • Caveats: future GLOBAL memory must stay tiny — with conditional loading broken, anything global loads in EVERY project unconditionally; fold that constraint into the global-memory design once a backup exists. Caveman pass to ~250 was explicitly DECLINED: marginal ~25-line gain vs real risk (changes the nature of instructions-to-follow; no evidence caveman is followed better than prose; CLAUDE.md is the most-edited file → caveman = painful to re-read/amend). 275 readable > 250 caveman.
  • Alternatives rejected: path-scoped ~/.claude/rules/ (broken, BLK-009); externalize sections to on-demand-loaded files (same conditional-load dependency); caveman to ~250 (readability + instruction-fidelity risk).
  • Reference: ~/.claude/CLAUDE.md (symlink → ~/Documents/claude/CLAUDE.md), commits ba743cf (compress routing+design+graphify) + 990318c (trim separators/blanks). Linked to BLK-009, LRN-043, LRN-044.

BDR-032 — skill /validate → /web-validate (rename user surface, keep internals)

  • Date: 2026-06-25
  • Status: accepted (shipped e5e673a)
  • Decision: rename W3C+WCAG skill /validate → /web-validate (clearer scoped name, less generic). Renamed the USER-FACING surface ONLY: folder (git mv), frontmatter name, H1, command refs, CLAUDE.md routing line, 6 lib/profiles/*.profile entries (FUNCTIONAL — profiles activate skills by folder name, a miss = broken activation), cross-refs (harden/seo/depth-matrix/client-handover), agent dispatch refs, README + USAGE tables. Leak-guard regex extended to web-validate|validate (LRN-045).
  • Why — 4 deliberate KEEPs:
    • agent validator-analyzer name KEPT — internal, lockstep with subagent_type= + harness registry; rename = wider blast radius, zero discoverability gain.
    • .validate-cache/ + VALIDATE.md KEPT — names derive from the AUDIT TYPE, family {SEO,GEO,HARDEN,CSO,VALIDATE}.md; renaming makes VALIDATE the odd one out + orphans already-generated reports (MIGRATION.md cleanup loop hardcodes the name). Same logic kept the dispatch label description="validate — ...".
    • .claude/ history KEPT (memory + completed TODO block) — append-only, true at the time. The forward-pointing OPEN TODO item was ANNOTATED additively (désormais /web-validate), not rewritten — append-only protects history, not pointers to future actions.
    • CHANGELOG old entry KEPT, new "renamed" entry ADDED (Keep-a-Changelog: don't rewrite the past).
    • NL trigger keywords ("validate"/"validation") KEPT in the description so "validate my site" still routes here.
  • Alternatives rejected: rename agent + artifacts too (cosmetic symmetry, ~45 extra edits, breaks audit-file family + report back-compat); blind sed s/validate/web-validate/ (breaks third-party html-validate, validator.nu, English-verb prose — discrimination must be at the /validate token, proven by .validate-cache/html-validate.json staying intact).
  • Reference: commit e5e673a (18 files). Verified complete: /validate = 0 in active code (only .claude/ history + CHANGELOG), html-validate = 15 intact, regex client-handover-writer.md:1462 shows both names. Linked to LRN-045, BDR-031 (CLAUDE.md routing), LRN-043 (validate routing line).

BDR-033 — design-gate §4: anim-lib suggestion — suggest-only, non-blocking, stateless 1-line

  • Date: 2026-06-25
  • Status: accepted
  • Decision: lib/design-gate.md gains §4. When a non-trivial design task hits a MOTION signal (animation/transition/hover/motion/animate, added to §DETECTION) AND detect_anim_eligibility=eligible AND is_anim_lib_installed finds none → surface ONE line suggesting the recommended motion pkg. Suggest-only (install ONLY on explicit consent, never auto), non-blocking (sole STOP stays §3 exit 10), stateless (ALWAYS the single line, no marker). Calls the helper — no 3rd copy of the lib list.
  • Why: gate runs mid-build; a 2nd blocking stop on an OPTIONAL dep = friction. Dedup goal is not "prevent re-fire" but "make the surface minimal enough that re-fire is never noise" → deterministic by construction (nothing to remember → no fragile behavioral guard, cf LRN-046/LRN-047). Conditional to stakes: the deduped thing here is a NON-DESTRUCTIVE 1-line cosmetic note → re-fire is annoyance, not risk, so importing marker-grade infra (file + gitignore + permanent state) is not justified. On a DESTRUCTIVE op a deterministic marker IS worth its cost — that is where LRN-046/LRN-047 were forged. Same determinism bar, opposite cost/benefit; pick by stake. Self-heal: condition-3 (is_anim_lib_installed, 10 libs incl gsap/react-spring/lottie) kills it the instant any anim lib lands → re-fire only ever hits "eligible + pure-CSS + actively declined".
  • Alternatives rejected:
    • File marker .design-anim-suggested (once-forever) — "session"→"forever-per-project" (1 decline = permanent silence, no cleanup but manual rm); adds write + gitignore mgmt to a non-mutating doctrine; .claude/ tracked here → suppression leaks via git.
    • Blocking yes/skip prompt à la /onboard STEP 2.5 — a 2nd STOP mid-build on an optional dep.
    • Prose "agent remembers not to re-suggest" — fragile behavioral guard, contradicts LRN-046/LRN-047.
  • Reference: commit 11792cc, lib/design-gate.md §4 + §DETECTION (+motion/+animate). Helper lib/animation-lib-check.sh unchanged. Live via symlink (~/.claude/lib/→repo). Builds on BDR-005. See LRN-049.

BDR-034 — Coupled-capitalize invariant v1 — memory commit auto per dev flow (Frame 2)

  • Date: 2026-06-26
  • Status: accepted
  • Decision: Dev flows committing code now auto-commit memory same breath, via include lib/capitalize-commit.md + helper lib/memory-commit.sh (surgical: stages+commits .claude/memory+.claude/tasks only, pathspec, never git add -A). 4 inline flows (feat/hotfix/bugfix/commit-change) ref the include at their capitalize step; ship-feature reordered (CAPITALIZE STEP 7 before FINISH STEP 8 — fixes memory committed after push/PR + stranded outside it); init-project gains STEP 10b founding-decisions capitalize. 1 memory commit/flow (F3). Capitalize CONTENT keeps its approval gate — only the COMMIT of approved entries is automated.
  • Why: Real pain = the 2nd (memory) commit forgotten/manual — ~42% of recent history (17/40 commits) was emergent chore(memory). Frame chosen = "couplé après-code" not "avant commit": keeps hash-anchoring (>50% entries carry Reference: commit) + code/memory concern separation; attacks the forgetting, not the ordering. "Capitalize before commit" rejected — inverts a deliberate property AND can't anchor the code hash (hash exists only post-commit).
  • Alternatives rejected:
    • (a) each orchestrator calls capitalize-before-commit — duplicated across 5+ flows (each has bespoke inline capitalize), breaks hash-anchoring, forgettable on next skill added.
    • (b) commit-change as the single gate — not on the path of feat/hotfix/bugfix/ship-feature/init-project (they commit inline or via external superpowers); can't detect "pending capitalize".
    • (c) single commit chokepoint — doesn't exist; 3 distinct commit mechanisms, one external/unmodifiable (superpowers:finishing-a-development-branch).
    • Frame 3 (single unified commit, drop hash) — sacrifices >50% entries' anchoring for history aesthetics.
  • Reference: commits 58cb91d (helper+tests) · bbef41c (hash/stdout + T6/T7) · b44791b (include) · 2763678 (4 flows) · e8eff7e (ship-feature reorder) · df60df6 (init-project). Hook (v2, Stop-hook non-blocking BDR-033-style) + doc-sync twin chantier (same PR bug, reorder before FINISH) deferred. See LRN-051, LRN-052, EVAL-007.

BDR-035 — Analyze-before-plan invariant v1 — read-before bookend of coupled-capitalize

  • Date: 2026-06-26
  • Status: accepted
  • Decision: Dev flows now READ related memory before planning (ship-feature also reads related code), mirroring how BDR-034 made them WRITE memory after. Shared include lib/analyze-before-plan.md (tête-bêche of lib/capitalize-commit.md). Invariant = DISPOSITION, not reading: the plan must NAME each surfaced ID (in-force / already-seen / non-binding) — a verifiable trace in the artifact, not "did it look". Two-pass: grep ## <PREFIX>- body headings → select on titles → full-read only the selected bodies. Wiring: ship-feature STEP 0d (analyzer subagent code+memory, fed to brainstorm/plan by INPUT INJECTION + STEP 3 reconciliation gate); bugfix STEP 2.5 (blockers-first); feat STEP 0.6 (decisions-first, MINI-PLAN names in-force or states none); hotfix opt-in blockers-only; init-project + onboard = no-op exceptions. Guarded no-op ([ -d .claude/memory ]).
  • Why: coupled-capitalize gave every flow a write-after; NO flow read the memory it feeds — bookend half-open. A bugfix wrote BLK at the end but never checked blockers.md for the same root cause already solved. Closes Gap B (memory, universal) + Gap A (code, ship-feature — the lone cold-planner).
  • Alternatives rejected:
    • Index two-pass — ## Index drifted on this mature repo (decisions 11/34, learnings 21/52, blockers 2/9 missing) in scattered blocks → an Index-based selector silently misses a large unpredictable fraction. Body headings drift-immune (100% coverage). See LRN-055.
    • Extend analyzer only — inline flows (feat/bugfix/hotfix) never call analyzer pre-plan → would close Gap B for none. Needed both: include + analyzer RELATED MEMORY section.
    • PASS-2 skip-if-already-in-context — no deterministic oracle for "in context"; reintroduces the behavioral guard. See LRN-054.
  • Reference: commit 67c6a81, lib/analyze-before-plan.md, agents/analyzer.md. Bookend of BDR-034. See LRN-053, LRN-054, LRN-055, LRN-056, LRN-057.

BDR-036 — Doc-sync coupled invariant — commit the docs doc-syncer patches (twin of BDR-034, BUILT not reordered)

  • Date: 2026-06-27
  • Status: accepted
  • Decision: doc-sync flows now COMMIT the public docs doc-syncer patches, via new lib/doc-commit.sh (helper) + lib/doc-commit.md (include) — mirror of memory-commit/capitalize-commit, 4 DELTAS: (Δ1) dynamic scope = patched files as argv, not a fixed pathspec; (Δ2) INVERSE exclusion = fail-closed + loud guard rejecting .claude/**+CLAUDE.md (dedicated exit 4), opposite of memory-commit which TARGETS .claude/; (Δ3) no hash anchoring (docs carry no SHA, LRN-052); (Δ4) docs: msg. doc-syncer emits PATCHED_FILES (one path/line) → agent splits on newline → each as DISTINCT argv (space-safe, LRN-060). 2 orchestrators reordered DOC SYNC before FINISH (ship-feature STEP 9→8, init-project STEP 12→10c, GSD 13→12); 3 inline flows wired (feat/bugfix/hotfix DOC SYNC). Consumption MECHANICAL (LRN-057 case a, = BDR-034).
  • Why: doc-syncer PATCHED docs but COMMITTED nothing (grep-proven, zero git commit) → push/PR path = docs stranded outside PR (orchestrators); inline = docs left dirty. Twin of BDR-034 but NOT same fix: memory ALREADY had a commit helper (only mis-timed); doc-sync had NONE → had to BUILD the mechanism, not just reorder. "Reorder alone" (the deferred note's framing) REFUTED in read-phase (LRN-058).
  • Honest scope/choices (engraved, not glossed):
    • (a) MINOR doc content stays NON-gated yet auto-committed — CONSCIOUS, not memory's always-gated content; the VISIBLE surface (files + AGENT-composed change summary, not a bare count) REPLACES the gate as the review surface. Strengthening the MINOR gate = separate doc-syncer chantier.
    • (b) init-project PARTIAL — scaffold + bootstrap-README commit gap (BLK-010, unborn HEAD + worktree) + GSD ROADMAP post-FINISH (BLK-011) deferred = NEW work, not replication.
    • (c) scope EXPANDED mid-chantier via the ref-sweep to 3 inline flows — asymmetry vs memory (BDR-034 wired ALL flows) was the decider.
  • Alternatives rejected:
    • Reorder-only (the deferred note) — refuted: doc-syncer commits nothing, reordering uncommitted docs still misses the merge.
    • Static-glob scope (*.md/docs/) — over-reach onto a user-edited doc / MIGRATION.md; chose touched-files argv (in-thread list already in hand).
    • Silent-filter the forbidden path — masks an upstream BDR-022 bug; guard must REFUSE-ALL loudly (LRN-060).
  • Reference: commits ae1f218 (helper+tests) · 4a54a65 (include) · fb1f359 (doc-syncer PATCHED_FILES) · 636b491 (ship-feature reorder) · e81f629 (init-project reorder) · 1b01b95 (3 inline flows). See BDR-034, LRN-058, LRN-059, LRN-060, BLK-010, BLK-011, EVAL-008.

BDR-037 — v2 capitalize Stop-hook REJECTED → wire /capitalize+/close to the include

  • Date: 2026-06-27
  • Status: accepted
  • Decision: Deferred "v2 hook" (BDR-033-style non-blocking stateless Stop-hook nagging dirty .claude/memory) REJECTED — no code written. No Claude Code event supports an end-of-session nag: Stop fires PER-TURN (self-defeating — would nag during /capitalize's own write→commit window, LRN-047); SessionEnd is debug-log-only (can't nag) + would bypass the content gate (half-written entries). Real gap ≠ forgetting → CÂBLAGE manquant: /capitalize + /close never call lib/capitalize-commit.md. Redirect: wire the include into /capitalize STEP 5B (/close = thin alias, follows free), same one-liner as the 6 dev flows. Content gate (STEP 3) first → commit of approved entries automated (BDR-034 contract). Deterministic, zero-noise, at source.
  • Why: 3 git proofs — (1) memory already committed by hand 35× as pure chore(memory) (zero code mixed); (2) orphans self-heal — commit_memory stages whole .claude/memory dir, next flow sweeps up; (3) cost on common path (per-turn noise / non-gated commit), benefit marginal (residual self-healing) → a hook polices a discipline already in evidence. OUBLI not choice: include + helper born 2026-06-26; /capitalize last edit 2026-06-19 (created 2026-06-09), /close 2026-04-23 — skills predate the machinery 7-60d; wiring commits (2763678/e8eff7e/df60df6) touched neither. No control removed: the commit was never gated, just done by hand in the exact chore(memory): <IDs> — <ctx> style the include reproduces.
  • Alternatives rejected:
    • (a) Stop per-turn memory-only wrapper — fires during the very flush it nags about → LRN-047 self-defeat (frequent ignored nag = risk, not annoyance).
    • (b) SessionEnd auto-commit (FAIT) — bypasses STEP 3 content gate, embarks half-written entries, can't report actionably.
    • (c) abandon with no redirect — leaves the real wiring gap open; fix for an unwired skill = wire it.
  • Reference: read-phase analysis (no hook code ever written); wiring commit (capitalize STEP 5B) follows. Completes BDR-034 rollout; applies BDR-033 doctrine to REJECT (not all nudges — the determinism split is LRN-061). See LRN-061, LRN-047, LRN-049, LRN-054.

BDR-038 — deploy skill: per-project learning runbook, two-moment cold-resume

  • date: 2026-06-27
  • status: accepted
  • decision: New /deploy skill = per-project runbook in .claude/deploy/. 5 artifacts: PROCEDURE.md (runbook, in-place edits), INCIDENTS.md (DEP-NNN ledger, append-only), STATE.json (deploy oracle, committed), PENDING.json (cold-resume bridge, gitignored), NEXT.sh (instantiated checklist, gitignored, hand-run). Two-moment spine: BEFORE (delta-instantiate NEXT.sh + hand back) → user deploys OUT-OF-BAND → AFTER (react: MARK success or LEARN from failure). Cold cross-session resume via PENDING.json (disk = only memory across the gap). Learn = atomic patch+incident (one deploy-commit.sh call, both files). New helper lib/deploy-commit.sh (allowlist .claude/deploy/). Built via subagent-driven-development (4 tasks).
  • why: deployment memory that LEARNS (runbook patched in place per failure) beats a frozen runbook; disk-bridge so a resume survives session loss.
  • alternatives: tag-oracle (rejected — lightweight-tag date unreliable, rebase-fragile, LRN-063); separate append-only ERRORS log (rejected — git history of PROCEDURE.md+INCIDENTS.md suffices, no resolved-by field); NEXT.sh-as-bridge (rejected — ephemeral ≠ persistent → separate PENDING.json); reuse doc/memory-commit (rejected — neither can commit .claude/deploy/, LRN-064).
  • reference: skills/deploy/SKILL.md, lib/deploy-commit.sh, templates/deploy/; branch feat/deploy-skill (b210e8d..79741e3, kept un-merged); spec docs/specs/2026-06-27-deploy-skill-design.md, plan docs/plans/2026-06-27-deploy-skill.md.

BDR-039 — Gitea branch protection = Option-1 owner-pushable, not require-PR

  • date: 2026-06-29
  • status: accepted
  • decision: Protect main + develop on every gitflow-migrated Gitea repo with Option 1 (owner-pushable): enable_push=true + enable_push_whitelist=true + push_whitelist_usernames=[owner]. Blocks force-push, branch deletion, and pushes by non-owners — while letting the owner push their LOCAL gitflow merges directly. NOT require-PR / required-review.
  • why: gitflow integrates by local directed merges — gitflow finish runs git merge --no-ff on the owner's machine then pushes the merge commit. require-PR would REJECT those pushes: every feature/bugfix/release merge would need a manual PR, and the hotfix fan-out (hotfix → main + develop + each open release/*) becomes 3+ manual PRs per hotfix. For a solo-owner Gitea, required reviews add zero review value, only friction. Owner-pushable keeps the protection's real teeth (no force-push, no deletion, no non-owner push) without breaking the local-merge workflow. Protection is a BACKSTOP — the per-repo pre-commit hook + the "finish only on an explicit human signal" rule are the primary controls.
  • alternatives: require-PR + required reviews (rejected — breaks gitflow finish's local merges; the 3-way hotfix fan-out becomes manual PRs; no review value for a solo owner, pure friction); no protection (rejected — leaves force-push + branch deletion + accidental non-owner push open; it is the deterministic backstop the advisory rules can't guarantee); protect main only (rejected — develop is equally a protected base in the model, needs the same force-push/deletion guard).
  • reference: lib/gitflow-migrate.sh _protect() (POST /repos/{o}/{r}/branch_protections, owner whitelist); applied to all 6 repos 2026-06-29 (journal). Hook backstop in lib/gitflow.sh (pre-commit); CLAUDE.md "Version control — gitflow (universal)". Pairs with LRN-069 (the git push ASK gate at the tool-call layer).

BDR-040 — doc-syncer MINOR-shape oracle: deterministic floor under LLM's MINOR call

  • date: 2026-06-29
  • status: accepted
  • Problem: doc-syncer AUTO MODE classifies drift NONE/MINOR/SIGNIFICANT by LLM judgment, no deterministic backstop. SIGNIFICANT mislabeled MINOR → silent auto-patch + auto-commit, skips the SIGNIFICANT gate (RISK-1). Follow-up BDR-036 flagged.
  • Decision: lib/doc-shape.sh re-checks SHAPE of each MINOR patch BEFORE the silent auto-commit. Envelope (per path, git diff HEAD): adds ATX heading | added > DOC_SHAPE_MAX_ADDED (def 20) | removed > MAX_REMOVED (def 20) | new/untracked file | non-doc → EXCEEDS. Aggregate: ANY path exceeds → whole set escalates to the EXISTING SIGNIFICANT gate (STEP A4 Apply? yes/no/select; no=revert all, select=keep subset). Thresholds env-overridable.
  • Oracle NOT a blocking gate (B rejected): BDR-036 graved MINOR-non-gated as CONSCIOUS (visible surface replaces gate; blocking gate = friction disproportionate). Oracle does NOT gate genuine MINOR (auto-commit untouched, zero friction) — only re-routes shape-suspect patches. Tightens the DEFINITION of MINOR deterministically (LRN-046 oracle > judge), adds no gate. Option B (human gate on every MINOR) REJECTED — contradicts BDR-036, rejects the premise the reading refuted.
  • ENGRAVED LIMIT — do not over-read the guarantee: oracle catches STRUCTURAL/size significance, NOT semantic. A 3-line edit that CHANGES MEANING, no heading, small → still reads MINOR (rc 0) and auto-commits. Deterministic FLOOR under LLM judgment = REDUCTION of RISK-1's gross cases, NOT elimination. LLM owns the semantic call above the floor; the visible surface (BDR-036) stays the content backstop.
  • Scope tranché: ① oracle + ② LRN-071 masked-commit fix built. ③ branch-guard (doc-commit refusing main/develop) DEFERRED — duplicates the protected-base predicate a 3rd time (lib + gitflow hook + here); migrated repos have the hook → ③ guards a state that shouldn't exist. Reconsider only for repos outside gitflow init.
  • Build: TDD RED→GREEN. run-doc-shape.sh 19/19 (incl. threshold boundary + env-override) + behavioral Scenario D. Wired doc-syncer STEP A4 + doc-commit.md ACKNOWLEDGMENTS coherence. shellcheck clean.

BDR-041 — /reconcile = deterministic declared-vs-real engine + thin gated skill (reconciler, not lister)

  • Date: 2026-06-30
  • Status: accepted
  • Decision: /reconcile answers "what work is REALLY open?" by confronting DECLARATIVE sources (TODO [x]/[ ]/[~], registry statuses, ## Index) against REAL state (git/fs, registry BODY). Split: lib/reconcile.sh (deterministic engine — body enumeration, reconcile_oracle_*, BLK last-block-wins, lexical deferral sweep, contradiction candidates, pure reconcile_verdict kernel) + skills/reconcile/SKILL.md (thin orchestration + A/B/C write-back gate). Founding principle = RECURSIVE COHERENCE: never use a declarative source as oracle (Index/checkbox/status/path-name) — enumerate from BODY ## ID headings, decide done/stale from git/fs. TESTED: run-reconcile.sh T1 reds if engine reads ## Index (shim → 51≠72, canary LRN-020 dropped). Registries READ-ONLY (curation = /prune-memory).
  • Why: RED proved a capable agent reconciles when GUIDED ("use git + justify") but MIRRORS the TODO when not (false positives + missed contradiction), and even guided hits the compound-status trap (BLK-008). Value = determinism + cheapness + gate, NOT teaching (LRN-031, LRN-075). Mechanical 80% → script; judgment 20% → thin skill (writing-skills: automate mechanical, document judgment).
  • Alternatives rejected: monolithic teaching/discipline skill (agents reconcile when guided → no teaching value); grep '[ ]' lister (reproduces the lie); trust Index (drift, LRN-055); blocking write-back gate (friction — A/B/C surface chosen).
  • Honest limits (graven): deferral detection LEXICAL (marked-only; unmarked "à reprendre quand X" missed); contradictions = CANDIDATES (token overlap), surfaced not asserted; cross-repo "not verifiable here"; cross-ref verdicts ("[~] done because chantier X below complete") surfaced, not auto-resolved.
  • Reference: lib/reconcile.sh, lib/tests/run-reconcile.sh (20/20) + lib/tests/fixtures/ (neutral-named, LRN-077), skills/reconcile/SKILL.md, CLAUDE.md "Skill routing". Born of the 2026-06-29 manual inventory (its known-good oracle). Built via superpowers:writing-skills. See LRN-075, LRN-076, EVAL-011.

BDR-042 — /release-candidate = thin orchestrator over gitflow release; the tag lives in the skill

  • Date: 2026-06-30
  • Status: accepted
  • Decision: /release-candidate <X.Y.Z> cuts a release by ORCHESTRATING the existing lib/gitflow.sh mechanic, NOT rewriting it. Flow: preconditions (clean tree, identity, develop ahead of main) → gitflow start release → prep (version.txt + CHANGELOG, breaking documented) → run-tests gate → HUMAN "WHEN to release" gate → gitflow finish (fan-out main+develop+delete, lib L108-111) → git tag -a vX.Y.Z main → push gated. The git tag lives in the SKILL, lib UNTOUCHED — the tag is release-specific (version + message + human call), the lib's fan-out is generic. Scheme vX.Y.Z, CONTINUES the version.txt/CHANGELOG lineage (never restart at v1.0.0 — desyncs from a CHANGELOG already at 3.x).
  • Why: gitflow release was wired (start base=develop L49, finish fan-out main+develop L108-111) but had NO tag step (grep-confirmed: zero git tag in gitflow.sh). The tag is the only gap; an orchestrator supplies it without touching the tested mechanic.
  • Consequence (accepted): a release cut by calling gitflow finish directly, bypassing the skill, fans out but is NOT tagged → /release-candidate is the CANONICAL sole release path. Acceptable for a solo repo; revisit (tag in lib) only if direct-lib releases become a need.
  • Alternatives rejected: tag inside gitflow_finish (atomic but modifies the tested generic mechanic for a release-specific concern — lib=mechanic/skill=judgment); restart tags at v1.0.0 (desyncs tag↔CHANGELOG lineage).
  • Reference: skills/release-candidate/SKILL.md, lib/tests/run-release-candidate.sh (RED no-tag → GREEN 5/5), CLAUDE.md routing. Built via writing-skills TDD. Consumes the gitflow model BDR-039. See LRN-078, LRN-079, EVAL-012.
  • Date: 2026-06-30
  • Status: accepted (requalifies BDR-015 — append-only, BDR-015 left intact)
  • Decision: the 5 dirs BDR-015 excluded from /darwin-skill (benchmark-models, context-restore, context-save, make-pdf, plan-tune) are no longer broken. gstack now ships those skills — all GENERATED by gen-skill-docs in the make plugin run → real submodule targets exist, symlinks resolve. VÉRIF audit 2026-06-30 = 0 broken among 83 symlinks (skills/ 41 + skills-disabled/ 33 + nested 5 + top-level 4). Per BDR-015's own caveat ("if/when symlinks repaired → re-run baseline to bring them in scope"), the 5 RETURN to darwin scope → re-baseline UNBLOCKED.
  • Why: BDR-015's exclusion was CONDITIONAL on the targets being broken (external-ownership + missing-target). Precondition gone → exclusion no longer applies to these 5.
  • Action (NOT done): verify ~/.agents/skills/darwin-skill/results.tsv still marks these 5 status=error ("broken gstack symlink — out of scope"); if so, re-run darwin baseline to bring them in. Status = UNBLOCKED, execution PENDING — do NOT read as "re-baselined".
  • Distinct from BLK-007: BLK-007/f928a53 (2026-06-02) = a DIFFERENT symlink episode (spec + 5 iOS device-farm skills, source-only after a submodule bump; fixed by linking spec, skipping iOS). NOT the 5 of BDR-015 — kept separate to avoid a false causal link.
  • Reference: VÉRIF audit (subagent, filesystem-only, 2026-06-30). BDR-015 caveat. darwin eval log results.tsv.

BDR-044 — auto-skill-dispatch won't-build: under→over reframe, measured — model already discriminates

  • Date: 2026-06-30
  • Status: accepted · won't-build
  • Decision: do NOT add L2 routing prose to CLAUDE.md for "auto-trigger skills on intent". Chantier retired won't-build — 3rd measured moot of the session (after BDR-001 --help + BDR-043/LRN-082 darwin re-baseline).
  • Why — the dependent variable inverted: the initial fear was UNDER-routing (model ignores skills, does the task by hand). Cartography refuted it — routing is a STACK and L1 (superpowers "1% chance → you MUST invoke") already SUR-determines invocation → "does it route?" = "already yes". The real open question became DISCERNMENT (clear→route, ambiguous→ASK, trivial→abstain), and the real hazard inverted to OVER-routing. Measured in REAL fresh main-loop sessions (8 prompts, 3 classes): CLEAR→routes ✓, AMBIGUOUS→asks (refuses to guess, investigates to ask a USEFUL question) ✓, TRIVIAL→abstains ✓. The L1-vs-Workflow-rules textual tension ("1% → MUST invoke" vs "ask one question if needed / pragmatic on trivial") is resolved well in behavior — the model balances. Adding L2 bounding prose = phantom value AND risks DEGRADING an already-good discernment.
  • Alternatives rejected:
    • Add a routing-reinforcement instruction (original intent) → phantom value: L1 already over-determines routing; more mandate worsens the only real risk (over-routing).
    • Add an over-routing bound (clear→route / ambiguous→ask / trivial→abstain) at L2 → measurement shows the model ALREADY does this; codifying it risks perturbing it, zero upside.
    • Keyword hook on intent verbs → too noisy — the design-hook mis-fired on "design" in "auto-skill-dispatch" 3× this session; intent verbs (corrige/crée) are everywhere.
  • Reference: cartography L0–L4 + discernment-RED (user-run, fresh sessions). Subagent under-routing RED RETIRED as non-discriminating (LRN-083). LRN-080 (measure-first), LRN-049 (bound noise). TODO "auto-skill-dispatch" → won't-build.

BDR-045 — Standalone memory/doc skills branch to chore/* via the aiguillage (hook exemption kept)

  • Date: 2026-07-01
  • Status: accepted
  • Decision: Standalone memory/doc skills (/capitalize /close /prune-memory /reconcile) run the gitflow aiguillage BEFORE writing: on a protected base they gitflow start chore <name> off develop → commit lands on chore/*, not direct on main/develop. New chore type in lib/gitflow.sh (base_for→develop, branch_type, finish→develop like feature/bugfix); hook UNCHANGED (chore/* non-protected; the .claude/**-on-main exemption KEPT — T3 still green). gitflow-aiguillage.md broadened (caller→type map); 3 skills wired (capitalize covers /close via alias, prune-memory, reconcile); tests +T1 chore predicates +T6b finish chore→develop +T10 coherence chore/m → 64/64. Reused the EXISTING aiguillage include, not a new mechanism. Commit e8807a7.
  • Why: the .claude/** exemption is scoped to the SIDE-CAR (BDR-034: memory following a code branch). When memory IS the work (standalone reconcile/prune/capitalize) there is no branch to follow → it fell back to main. A multi-repo raccord committed 5 chore(memory) direct on main and nothing flagged it — the exemption worked as designed, masking the divergence with the "all via branch" rule (LRN-084). The aiguillage closes the SKILL path without taxing the side-car. The hook can NEVER enforce "from develop" (only "not on a protected base") → that half lives ONLY in gitflow_start.
  • Alternatives rejected:
    • (A) remove the .claude/** exemption — breaks standalone /capitalize+/close on main/develop (commit in place, no branch of their own — memory-commit.sh has no protected-base guard) AND every side-car commit; over-reaches the leak.
    • (C) codify exemption + human habit — enforces NOTHING mechanically; goal was automatic.
    • (D) narrow the exemption by size/scope in the hook — fuzzy, false positives.
  • Honest residual: a MANUAL git commit of .claude/** on main still passes — B covers the skill path only. Non-blocking hook WARN on manual .claude/**-on-main = DEFERRED. See BDR-034, BDR-039, LRN-084.

BDR-046 — Claude Code installs via the official native installer, not npm

  • Date: 2026-07-01
  • Decision: install.sh fresh-machine branch installs Claude Code via curl -fsSL https://claude.ai/install.sh | bash (official native installer), not npm install -g @anthropic-ai/claude-code. Skip-if-present guard unchanged. update-all.sh stays channel-aware (native → claude update, legacy npm → npm).
  • Why: official quickstart (code.claude.com/docs) lists Native (recommended) / Homebrew / WinGet / apt only — npm is NO longer a documented channel. npm collided with the native symlink ~/.local/bin/claude → EEXIST (BLK-014), and npm bypasses native background auto-update. install-plugins.sh already pointed to code.claude.com (native) — install.sh was the npm outlier; this aligns them.
  • Alternatives rejected:
    • (A) keep npm on fresh install — deprecated channel, re-introduces the EEXIST class on any machine with a prior native install, no auto-update.
    • (B) claude install subcommand — needs claude already present (chicken-and-egg on fresh machine); curl bootstrap is the documented first-time path.
    • (C) Homebrew/apt — platform-specific; curl covers macOS/Linux/WSL uniformly and matches the doc's "recommended".
  • Honest residual: curl | bash = pipe-to-remote-bash (accepted: official Anthropic domain, same pattern already used for nvm at install.sh:29). node/npm still installed as prereqs — needed by the plugins step (gsd-pi), not by claude. PATH export added so the auth step finds the freshly-installed binary. See BLK-014, LRN-085.
  • Status: accepted. Commits 8dc4027 + 6be627e, branch bugfix/install-claude-idempotent, pending merge.
  • Update 2026-07-01: MERGED 2393ca5 → develop, pushed — supersedes "pending merge".

BDR-047 — ECC audit → zero import; local config ahead of reference

  • Date: 2026-07-01
  • Status: accepted
  • Decision: audited affaan-m/ECC (legit original, NOT the arabicapp malware clone) read-only for value vs this config. Result: ZERO import. Nothing taken. Clean measure-first outcome — analysis closed.
  • Safety (durable, avoids re-audit): ECC = genuine original — 2232 commits, ~1480 by Affaan Mustafa, real contributor long-tail, sequential PRs. No payload: postinstall = echo, install.sh runs only its 3 reputable deps (@iarna/toml, ajv, sql.js), ships own supply-chain IOC scanner. Zero injection flags across ALL categories. NOTE: ECC install.sh auto-runs npm install → never run their installer casually; this analysis stayed read-only.
  • Why zero import (each intuition CHALLENGED, not confirmed):
    • RULES (122 files, by-language): ~80% redundant w/ CLAUDE.md, rest dormant reference. INERT at ECC — nothing reads rules/, their README admits "plugins cannot distribute rules automatically", paths: frontmatter aspirational (no auto-routing exists). "take all" refuted.
    • CONTEXTS (dev/research/review, 3 tiny files): least load-bearing. Delivery via claude --system-prompt "$(cat)" would OVERWRITE global CLAUDE.md. Harmful as-shipped. "important" refuted.
    • GUIDELINES: ECC itself demoted to docs/example. Per-project CLAUDE.md (git-tracked) superior.
    • INSTRUCTION FILES (AGENTS/RULES/SOUL/WORKING-CONTEXT): redundant or ECC-specific. AGENTS.md "proactive delegation" already mandated here.
    • MEMORY/learning: auto hook-capture → confidence-scored instincts. CONFLICTS measure-first (observe-first vs approve-first). Instinct schema parked (gated only).
    • eval-harness (the spike): DOCS-ONLY — 271-line SKILL.md, no runner, /eval define|check|report exist NOWHERE. Same "belle méthodo / câblage vaporware" pattern as rules. Executable-eval ALREADY covered locally: lib/tests/run-*.sh (code graders) + darwin dim8 (with/without-baseline sub-agent effect testing + git ratchet) + RED-before-GREEN discipline. evals.md = ledger of REAL runs (EVAL-011 ran 20/20, dogfooded) — spike premise "descriptif pas exécuté" was FALSE, corrected.
  • Lesson: external repo — even prestigious / "d'un boss" — judged on REAL added value to THIS config's axes (typed memory, real harness, gitflow), NOT author reputation. Measuring it revealed local config AHEAD on those axes. Taking a thing "since we analyzed" = sunk-cost. Zero is the honest conclusion. Don't re-propose auditing ECC expecting treasure.
  • 2 real gaps FOUND (not rejected — the only concrete fruit of the audit):
    1. pass@k / reliability-under-repetition — local harness proves PRESENCE (guard fires, often N=1), not RELIABILITY (right output 9/10 under repetition). Blind spot for non-deterministic skill/agent behavior (EVAL-006 flagged "N=6 fleet NOT exhausted").
    2. re-runnable regression battery indexed on model upgrades — bespoke per-chantier tests, no one-command "re-run behavioral evals for load-bearing skills" when model changes. darwin optimizes on-demand, not a standing gate.
    • Both = home-grown ~10-line bash over darwin's test-prompts.json if ever wanted — NOT ECC imports. eval-harness delivers neither (no runner). Separate later decision.
  • Alternatives rejected:
    • Import eval-harness anyway (sunk-cost "we analyzed it") — rejected: docs-only, capability already covered, adds vocabulary not machinery.
    • Import rules by-language + build wiring hook — parked: low ROI (bash/md, not polyglot); hookify-rules would be the mechanism, someday-if-polyglotte.
    • Adopt instinct auto-capture — rejected: conflicts measure-first.
  • Optional zero-cost nicety (not now): tag evals.md entries w/ grader-type + k (e.g. method: code-grader, pass^3) — writing convention, not an import.
  • Reference: read-only clone (scratchpad), 4 parallel analyzer agents + eval-harness spike, this session. No branch on ECC, no import. See BDR-045 (chore/ aiguillage), BDR-009 (caveman registries).
  • Corroboration 2026-07-03 (Opus 4.8 re-audit; repo UNCHANGED — HEAD 81af407 2026-06-29, 2232 commits identical, zero commits since 01/07): 6 parallel analyzer agents re-verified every BDR-047 fact w/ fresh file:line. rules/ inert (paths: 0 consumers, rules/README.md:333 "cannot distribute rules automatically"); contexts/ overwrite (the-longform-guide.md:68-74 --system-prompt); eval-harness no runner (/eval absent; gan-harness.sh + skill-improvement/evaluate.js exist but hors-scope, deliver NEITHER pass@k nor model-upgrade battery); memory auto-capture conflicts approve-first (continuous-learning-v2 observer-loop.sh:160-164 "Do NOT ask for permission"); distribution = product scaffolding, N/A. ZERO factual divergence. ONE scope gap: BDR-047 never opened hooks/ — ECC's only WIRED subsystem. Fruit: config-protection hook (own idiom, NOT ECC import), shipped feature/config-protection-hook. Lesson holds + refined by LRN-090.

BDR-048 — Deterministic security gate: pinned engine + pinned rulesets (semgrep)

  • Date: 2026-07-03
  • Decision: semgrep = BLOCKING gate (verify-loops chantier) → engine version PINNED in plugins.lock.json (gsd-pin pattern; update-all.sh honors pin + displays jump cur→pin before pipx install --force). Rulesets PINNED in-agent: p/security-audit + p/secrets. Never --config auto (registry telemetry + ruleset resolved per-run = non-deterministic gate, LRN-077 class). Never auto semgrep login — Pro rules optional, guide-only (ctx7 pattern).
  • Rationale: gate blocks HIGH/CRITICAL only (LRN-047); silent engine/rule upgrade = new BLOCKs on unchanged code w/o human decision → gate crying false → ignored. Version jump must be deliberate + visible (bump pin, then make update shows the jump).
  • Alternatives rejected: latest (pipx house default, graphifyy-style) — fine for comfort tools, wrong for a blocking gate; --config auto — telemetry + non-determinism.
  • Reference: plugins.lock.json semgrep entry, install-plugins.sh STEP 7.5, update-all.sh step 6.2 — branch feature/semgrep-install ccfecc9. Conditions LRN-047, LRN-085. Coverage caveat of the community rulesets: LRN-092.
  • Addendum 2026-07-03 (lot 3, measured): rulesets = p/security-audit + p/secrets + p/owasp-top-ten. owasp-top-ten is REQUIRED not optional — measured on realistic Flask code, the 2-ruleset baseline missed SQL injection + path traversal ENTIRELY (0 findings); owasp's taint rules catch them. Severity map: secrets ERROR→CRITICAL, other ERROR→HIGH (block), WARNING/INFO→reported. Blocking threshold = ERROR (per-RULE, not per-vuln — same class can straddle ERROR/WARNING; blocking WARNING too floods FP). FP measured shell/md only (faunosteo, game: sole added blocking ERROR = Dockerfile missing-user hygiene, contained by gate-mode diff-scoping). Re-evaluate owasp FP at the first real web/python app project (shell/md repos don't represent where the gate runs). See LRN-094, agents/security-auditor.md branch feature/security-auditor 2b297bd.

BDR-049 — Verifier doctrine: fresh + blind + disk-contract + proof-or-fail

  • Date: 2026-07-03
  • Decision: conformity verdict comes ONLY from a FRESH verifier subagent per iteration. Input = contract PATH (read from disk — dev restatement structurally unable to interpose) + diff range + optional test cmd. NEVER iteration history: blind, complete verification every time (cost bounded by the main-loop max-3 cap, LRN-083: loops decided in main loop). CONFORME ⇔ all criteria MET + zero out-of-scope. PROOF line mandatory (LRN-048). Mute/unparsable verifier NEVER a PASS: 1 fresh retry, 2nd structural failure = human escalation. Dev-justified out-of-scope enters FILE SCOPE only via a human micro-gate ([gated] marker) — else the dev justifies everything and scope constrains nothing. Contract on DISK at creation (.claude/tasks/contracts/<date>-<slug>-<HHMM>.md, committed; aborted run → deleted or status: aborted, never left dirty).
  • Rationale: dev self-score is always confident → not a gate. Verifier fed history anchors on prior verdicts → telescopic drift. Context-only contract dies at compaction, the verbatim with it.
  • Alternatives rejected: dev self-assessment as gate; cumulative verifier context ("cheaper" but anchored); gitignored run files (lose escalation reference + session-death survival).
  • Reference: lib/contract-interview.md + agents/verifier.md + lib/tests/contract-verifier.test.sh (31 locks) — branch feature/contract-verifier 6aed5ee. Behavioral GREEN: planted-gap → ECARTS(2) exact; conform-under-injected-history → CONFORME (blindness held). Twin of BDR-048 (security gate). Conditions LRN-048, LRN-083.

BDR-050 — Universal verify+secure pipeline, weighted per flow (loops in the main loop)

  • Date: 2026-07-03
  • Decision: every dev flow = contract (verbatim, on disk) → dev INLINE → fresh verifier (request conformity) → fresh security-auditor (MODE: gate) → commit. Loops BOUNDED at 3 and decided in the ORCHESTRATOR MAIN LOOP (LRN-083), never in a subagent. Order invariant: on any security re-loop, re-verify the REQUEST before re-scanning security. Per-flow weight: feat/bugfix = both gates, both loop (nominal 2 dispatches); hotfix = NO fresh verifier (its smoke-check verifies the trivial autofill contract), security gate whose FAILURE REVERTS (git restore + escalate to /bugfix), never loops — the 1-attempt model preserved (nominal 1 dispatch). Shared include lib/verify-secure-loop.md for feat/bugfix; hotfix inline variant.
  • Rationale: the value is the INDEPENDENCE of the gate (fresh subagent vs a rich contract), NOT delegating the dev — so dev stays inline in light flows and weighting lives on loops+questions, never on skipping a gate. hotfix reverts because a 3× loop would reintroduce the weight its identity excludes.
  • Alternatives rejected: dispatch the dev too (turns feat into ship-feature-bis); one merged "quality" gate (see LRN-095 — orthogonal gates degrade if fused); hotfix loops like feat (breaks its 1-attempt identity).
  • Reference: lib/verify-secure-loop.md + wired feater/bugfixer/hotfixer + lib/tests/loops-light.test.sh (27 locks) — feature/verify-loops 0f0162d. Behavioral GREEN (feat fixture): CONFORME→BLOCK(1) SQLi→fix→re-verify CONFORME→re-scan PASS, order invariant held. Builds on BDR-048 BDR-049. Conditions LRN-083 LRN-095.

BDR-051 — Contract enrich-at-gate: the contract grows only at a human micro-gate

  • Date: 2026-07-04
  • Decision: CONTRACT's REQUEST immutable; ACCEPTANCE CRITERIA + FILE SCOPE may GROW — exclusively at a human gate, each added entry tagged [gated <date>]. Heavy flows (ship-feature STEP 3, init-project GATE #1): approved DESIGN appends design-derived criteria to the contract; the fresh verifier then judges the diff against the ENRICHED contract, never the seed. Same mechanism as the out-of-scope micro-gate (BDR-049) — a dev never enriches; only the human validating a gate does.
  • Rationale: raw request underspecifies (one-line "add validation" hides the schema-rejection requirement the design surfaces); verifier judging only the seed → every design decision unverified. Gating the growth keeps the contract honest (no silent scope creep) AND complete (design criteria are verified). Only flow where the contract is mutable mid-run — bounded to gate moments.
  • Alternatives rejected: freeze contract at creation (design criteria unverified — seed too thin); let dev enrich (BDR-049 failure mode — dev justifies everything, scope constrains nothing); second contract per design (loses single-reference property).
  • Reference: ship-feature STEP 0e+3, init-project STEP 1+4, feature/verify-loops 1c69de2. Behavioral GREEN: a [gated 2026-07-04] design criterion (reject unknown config keys) was read + judged NOT-MET by a fresh verifier across 3 rounds (dogfood). Builds on BDR-049 BDR-050.

BDR-052 — /tour auto mode: branch-as-gate, declared state read-only

  • Date: 2026-07-05
  • Decision: /tour (grouped sweep clean+security+reconcile+doc, 1..N projects) runs auto, NO mid-run approval gates. Compensations: (1) fixes on chore/tour-<date> via gitflow lib, skill NEVER finish/merge/push — unmerged branch + per-project append-only .claude/audits/TOUR.md = the human gate, deferred not deleted; (2) reconcile phase REPORT-ONLY even in auto — target TODO + registries read-only, gaps = suggested rows applied later via /reconcile; (3) convergence loop bounded 3× (LRN-083), residuals reported honestly; (4) security floor = security-auditor (pinned semgrep, LRN-047 BLOCK HIGH/CRITICAL) every iteration + cso posture once (gstack ON); CRITICAL/HIGH contract-changing fix applied but tagged BREAKING in report+summary; (5) dirty tree / no develop / no lib → report-only, never stash, never hand-branch.
  • Rationale: mid-run gates defeat the skill's point (hands-off grouped sweep, user away). Auto-checking TODO reproduces the exact lie /reconcile catches — RED-proven, baseline did it. Branch+report = same approval semantics as audit-delta's 3c gate, moved after the fact where a headless run can afford it.
  • Alternatives rejected: per-phase AskUserQuestion gates (audit-delta model — blocks headless); one consolidated pre-fix gate (still blocks); auto-edit TODO on oracle proof (inference ≠ approval); plain-branch fallback on non-gitflow repos (violates lib-only doctrine → report-only instead).
  • Reference: skills/tour/SKILL.md + CLAUDE.md routing (feature/tour-skill 73e6a1c). TDD trail LRN-099 LRN-100 EVAL-014.

BDR-053 — ctx7 single surface: keep find-docs skill, kill context7.md rule

  • Date: 2026-07-06
  • Decision: ctx7 gets ONE session surface = skills/find-docs (lazy body, description-only cost). rules/context7.md deleted + install-plugins.sh STEP ctx7 purges it unconditionally post-setup (rm -f, generator has no skip-rule flag — --claude/--cli = target/mode only). darwin-skill entry dropped from skills-lock.json same pass (F8: lock stale 6bbcda37… vs disk c3220018…, no re-pin verb in npx skills — unpinned rather than hand-edit undocumented hash).
  • Rationale: rule = ~490 tok/session session-start duplicate of the skill (job1 F10 + job2); skill self-suffices (876-char description carries the triggers, body has full CLI flow). Purge-in-installer beats one-shot rm: survives re-runs + manual ctx7 setup.
  • Alternatives rejected: kill skill keep rule (rule always-on, costs every session even non-lib work; skill lazy — wrong direction); hand-trim generated files (fight the generator, LRN-039 class); hand-edit lock hash (algo undocumented).
  • Reference: chore/ctx7-single-surface; job1 F10, job2 F8/F13. User decision 2026-07-06.

BDR-054 — supersede BDR-038: NEXT.sh file + AskUserQuestion hand-back removed from /deploy

  • Date: 2026-07-06
  • Status: accepted (supersedes BDR-038 on 2 points: NEXT.sh artifact, hand-back mechanism)
  • Decision: /deploy ships WITHOUT NEXT.sh file (checklist display-only, conversation-only) and WITHOUT AskUserQuestion hand-back (plain final-text print, turn ends, no tool call after). BDR-038's original 5-artifact list (PROCEDURE.md, INCIDENTS.md, STATE.json, PENDING.json, NEXT.sh) shrinks to 4 committed/bridge artifacts — NEXT.sh no longer written. Two-moment spine (BEFORE/AFTER), PENDING.json bridge, deploy-commit.sh atomic patch+incident — all unchanged, still current per BDR-038.
  • Why: LRN-102 — deliverable text printed before a tool call may never render (harness guarantees only the turn's FINAL text); AskUserQuestion after the checklist swallowed it silently, live run 2026-07-05 (bchanot-cv). NEXT.sh-to-disk also useless in practice (user: throwaway once deployed) — display-only kills a stale-file-drift class for free.
  • Alternatives rejected: keep NEXT.sh, fix hand-back only (leaves ephemeral-file-nobody-reads problem); keep AskUserQuestion, cram checklist into its options text (char-limited, brittle); revert to file+question (reproduces the exact LRN-102 bug).
  • Reference: commits 31443ba (inline hand-back print), 52f6678 (checklist display-only, no NEXT.sh); skills/deploy/SKILL.md:74-77,295-297,313-318,440-441; LRN-102; job3 docs-drift audit D6/D7/D9 (.audit/job3-report.md).

BDR-055 — job5: delete pending verbs, close J4-17 MOOT

  • Date: 2026-07-07
  • Status: accepted
  • Decision: memory_pending() + docs_pending() + pending dispatcher arms deleted from lib/memory-commit.sh / lib/doc-commit.sh, plus stale "for the v2 hook" header mentions. commit/commit <message> <file>... = only verb left. J4-17 (job4 backlog: "extend run-deterministic.sh to test pending") closed MOOT — its premise gone with the verb.
  • Why: headers earmarked both funcs "for the v2 hook" — BDR-037 REJECTED v2 hook, no code ever written. J4-17 queued TEST not DELETE, but deferred to the newer/wrong branch — v2 hook dead means nothing left to test toward. Zero prod/test callers confirmed (job5 audit) before delete.
  • Alternatives rejected: keep+test per J4-17 (tests a dead-end, BDR-037 already closed that door); keep unused (dead code, no consumer).
  • Reference: commit da3abf9; .audit/job5-report.md J5-13/§3b; supersedes J4-17 (.audit/job4-report.md:35). Same supersession-trace discipline BDR-054 had to backfill for BDR-038/job3 D6-D9 — written here at delete time, not reconstructed later.

BDR-056 — job6: deps policy = latest gated by integration, not KEEP-PINNED by default

  • Date: 2026-07-07
  • Status: accepted (reverses job6-batch-3 KEEP-PINNED-unless-CVE default)
  • Decision: default posture = pull latest, gated per-dep by real integration checks (make test + named smoke), not "keep pinned unless a CVE forces the hand". Sequenced by risk, one upgrade = one commit = one gate, immediate rollback on red. Applied job6: ctx7 0.5.3→0.5.4, gsd-pi 2.64.0→3.0.0, gstack 070722a→11de390 (v1.52.1.0→v1.58.5.0), graphifyy binary 0.9.6→0.9.8 (hook-adoption declined separately, see below).
  • Why: job6-batch-3's expected verdict for gstack was KEEP-PINNED sauf CVE; user overrode it — a fail-open security-guard fix (#1911, no formal CVE) counts as the CVE clause in substance, and staying pinned to avoid work means carrying live-vulnerable tooling. Gating on integration tests (not on "did upstream file a CVE") catches the real risk (format/behavior breaks) that pin-forever also fails to prevent — gsd-pi 3.0.0 broke status-reporter's ROADMAP.md parser silently (0/0 instead of an error); the gate caught it before merge, KEEP-PINNED would have avoided the break but also frozen out #1688 (gsd-pi data-loss fix) and the gstack #1911 guards indefinitely.
  • Alternatives rejected: KEEP-PINNED unless CVE (job6-batch-3 default) — optimizes for zero-gate-work, pays for it by sitting on fail-open security guards and data-loss bugs with no formal CVE filed; blanket "always latest, no gate" — the gsd-pi break shows why the gate stays mandatory, this is not a license to skip it.
  • Caveats: not every dep took the full pull — graphifyy's hook-guard rewrite (a config-protected file) was surfaced with a diff and the user declined to adopt it this round (binary upgraded, hook install skipped); MCP magic version pin was declined by user call. Policy is "latest, gated", not "latest, no exceptions".
  • Reference: .audit/job6-report.md; commits b4896c9 (gsd-pi), 2813e55 (gstack), 00c97bc (docs); LRN-107 (secrets-subagent value-copy ban, same job's incident).

BDR-057 — job7: secrets by reference not by value; redact at capture, not just at rest

  • Date: 2026-07-07
  • Status: accepted
  • Decision: two-part posture from the job7 triage (.audit/job7/ALL-REDACTED.json, 5+ leak classes across ~/.claude and repos). (1) Wherever the consuming tool supports it, wire secrets BY REFERENCE (${VAR} expansion), not by value — closed the concrete case: lib/toggle-external.sh's claude mcp add magic --env API_KEY="$MAGIC_API_KEY" materialized the key as plaintext into ~/.claude.json (a 2nd copy outside the ~/.claude/.env canonical); fixed to --env 'API_KEY=${MAGIC_API_KEY}', with the var reaching claude only via a scoped ~/.bashrc wrapper function (subshell + exec — never the ambient shell). (2) Redact AT THE CAPTURE POINT, not just after the fact: hooks/rtk-rewrite.sh now appends a redaction pipe to bare printenv/env dumps before they can reach stdout/the transcript (the GITEA leak's actual vector), instead of relying solely on scrubbing artifacts after the fact.
  • Why: the job6 incident (LRN-107) and the GITEA leak both trace back to a secret VALUE existing somewhere it didn't strictly need to (a config field, a raw env dump) rather than a reference/redacted form. Fixing storage-at-rest (scrub backups) treats the symptom and must be redone every time a new copy appears (5 rotating .claude.json.backup.* files, 2 of 5 still had it live mid-job7 despite the canonical fix already applied) — fixing the SOURCE (don't materialize the value; redact before the dump leaves the process) is the only version that doesn't need repeating.
  • Alternatives rejected: scrub-only (job7's own fallback if reference support were absent) — verified Claude Code DOES support ${VAR} expansion in mcpServers config (user + project scope, env/command/args/url/headers fields — code.claude.com/docs/en/mcp.md) → reference form available, preferred; global export MAGIC_API_KEY in ~/.bashrc — works but exposes the secret to every subprocess of every shell, defeats the redaction hook (user rejected, scoped wrapper kept).
  • Reference: lib/toggle-external.sh:191-192, hooks/rtk-rewrite.sh, README.md "Adding an MCP server that needs a secret", .gitleaks.toml, lib/gitflow.sh _gitflow_emit_pre_commit, Makefile scan-secrets; commits b9300c3/3340c7d/17bdd08/5d5b386. Linked to BDR-026 (canonical vault this closes a leak vector against), LRN-108 (the claude mcp add --env trap).
  • Caveat — contradicts job6's own finding same day: job6 journal (2026-07-07, earlier same day): "${VAR} env-expansion confirmed unsupported at ~/.claude.json user scope after 2 rounds of sourced doc lookup". job7 lookup (claude-code-guide agent, same day): IS supported at user scope, citing code.claude.com/docs/en/mcp.md + a v2.1.161 changelog entry. Not reconciled — could be a version bump between the two lookups, or job6's research being wrong. ${MAGIC_API_KEY} rewrite live (claude mcp list recognizes the reference, reports the var missing → CLI PARSED the ${...} syntax); end-to-end confirmation (restart terminal + Claude Code, magic MCP reconnects) still a user residual — see BDR-057's own commit message.

BDR-058 — job8: darwin-skill reinstall full pinned tree, detached HEAD

  • Date: 2026-07-07
  • Status: accepted
  • Decision: darwin-skill non-functional past SKILL.md text — references/, scripts/, templates/ absent, referenced but never fetched. Root cause: ~/.agents/.skill-lock.json skillPath: "SKILL.md" — installer (skills CLI, vercel-labs/skills) fetches ONLY that one file, not sibling dirs. Upstream repo HEAD (7c7b7909b630dc3b5cbb91bd4bcb1b10bfb1f894) matches lockfile hash exactly — zero drift, zero tamper, SKILL.md byte-identical old vs new. Fix: cloned upstream at that SHA, copied full tree into ~/.agents/skills/darwin-skill/, verified all 5 referenced paths present, HEAD detached (no branch tracking, no silent advance on a stray git pull). Old single-file dir backed up to ~/.agents/skills/.job8-backups/darwin-skill.single-file.<ts> first.
  • Why: user picked reinstall-pinned over remove/keep-broken (job8 audit §4 item 4, 3-way choice). Unverifiable skill can't be trusted; user wants the optimizer kept, not removed.
  • Alternatives rejected: remove entry (kills wanted function); keep as-is (fails job8's own audit bar — unverifiable); flat-copy without .git (matches other 34 dormant skills' convention but drops verifiable pin — kept .git detached instead, darwin-skill now 2nd real SHA-pin in the whole trust chain after gstack, job8 report §5).
  • Reference: ~/.agents/skills/darwin-skill/ (detached HEAD 7c7b790), ~/.agents/.skill-lock.json (untouched, hash still accurate), backup at ~/.agents/skills/.job8-backups/. Outside this repo — no commit here covers the file placement itself, this entry is the record. Git-commit whole-.claude/skills-tree scope (job8 C.2, SKILL.md:115/201) NOT restricted — 3rd-party pinned code, patching it breaks the pin; accepted as documented risk, human-checkpoint-gated per job8 report. Linked to LRN-109.

BDR-059 — job8: explicit ask-gate for all 4 magic MCP tools, empty allow stays empty

  • Date: 2026-07-07
  • Status: accepted
  • Decision: settings.json permissions.ask now explicitly lists all 4 mcp__magic__* tools (21st_magic_component_builder, 21st_magic_component_refiner, 21st_magic_component_inspiration, logo_search). permissions.allow gets ZERO magic entries — no allowlist tightening, the job8 report's "frictionless" diff (allowlist logo_search + inspiration) was explicitly rejected. Confirmation required on every magic call, no exceptions, no auto-exec ever, no wildcard.
  • Why: job8 §3/§4 found zero real mcp__magic__* invocations ever (transcript census) and one SUSPECT finding (21st_magic_component_builder unauthenticated callback-injection channel, LRN-110). Prior state relied on undocumented absence-means-ask fallthrough — user wants the gate EXPLICIT so it can't silently regress if permissions.allow ever gets a careless wildcard or the default-mode semantics change.
  • Alternatives rejected: leave everything absent (report's own recommended default) — works today but is silent/undocumented, exactly the posture the user wanted to close; allowlist logo_search + 21st_magic_component_inspiration for frictionless design work (job8 report §3 "frictionless" diff) — explicitly declined, real usage is zero so friction costs nothing.
  • Reference: settings.json permissions.ask, commit bb7f25a. Linked to LRN-110 (component_builder risk), LRN-111 (empty-allowlist validity when usage is zero).

BDR-060 — job9: CC orchestration floor = v2.1.172 (nested dispatch), supersedes implicit v2.1.83 whole-system floor

  • Date: 2026-07-08
  • Status: accepted
  • Supersedes: implicit "v2.1.83 = whole-system floor" premise (a misread of BDR-004's decisions.md:133 auto-mode caveat).
  • Decision: orchestration floor for any NESTED subagent dispatch = Claude Code v2.1.172 (nesting stabilized: "let subagents spawn their own subagents", hard cap 5 levels, Agent must be in the subagent's tools: to nest). Live env confirmed v2.1.203 (user, nesting supported, cap 5). BDR-004:133 stays UNCHANGED — its v2.1.83+ is correct for AUTO MODE specifically; the nesting floor is a distinct, higher constraint recorded here (registry is append-only, and BDR-004 is factually right for its scope).
  • Why: the whole job1-9 audit series operated on the premise "CC flattens to 1 level → a 2-level subagent design is silently broken." That describes the pre-2.1.172 regime. Corrected in job9 via claude-code-guide (official docs code.claude.com/docs/en/agent-sdk/subagents.md) + user confirmation of live v2.1.203 → depth findings are VERSION-CONTINGENT, not broken. Path b (BDR-061) removes the seo/geo analyzers' dependence on nesting, but client-handover's general-purpose → /seo → seo-analyzer chain still nests (L1→L2), so the floor stands for the orchestration design.
  • Alternatives rejected: keep the implicit v2.1.83 floor — predates nesting, mislabels version-contingent flows as "BROKEN"; hard-gate CC version in doctor.sh — deferred (path b de-risks the analyzers; a doctor warn-gate is an optional follow-up, and doctor.sh is config-guarded → sentinel cost not justified now); raise BDR-004:133 to v2.1.172 — WRONG, that caveat is auto-mode-specific (auto mode works from 2.1.83) and rewriting it would violate append-only + inject a factual error.
  • Reference: .audit/job9-report.md §Premise + §6 D-version-floor; decisions.md:133 (BDR-004 auto-mode caveat, unchanged). Linked to BDR-061 (path-b), LRN-112 (nesting mechanics).

BDR-061 — job9: seo/geo analyzers emit a fix-bundle applied at L1 by doctrine (validator-analyzer pattern)

  • Date: 2026-07-08
  • Status: accepted
  • Decision: seo-analyzer + geo-analyzer re-architected to the validator-analyzer contract — they AUDIT and EMIT a machine-parseable ## FIX BUNDLE terminated by the verbatim READY TO APPLY — awaiting dispatcher confirmation sentinel; they NEVER edit code and NEVER dispatch a sub-agent (Agent dropped from both tools:). The DISPATCHER applies at L1 from its own main loop: /seo (new STEP 1.5) + /geo (rewritten to dispatch+apply, mirrors /web-validate) dispatch hotfixer/feater at L1; /harden keeps its existing direct-Edit STEP 3 (already end-to-end path-b); /onboard stays audit-only (bundle produced, deferred to backlog STEP 9). AUTO tier applies unconfirmed; GATED tier (seo D/E · geo G5) requires explicit accord; USER ACTIONS → report §11.
  • Why: by DOCTRINE, not version constraint. Before: analyzer STEP 12/13 dispatched hotfixer/feater; when the analyzer was itself a subagent (/seo → analyzer at L1), that dispatch was L2 nesting → silent no-op on CC<2.1.172, and both analyzers forbade direct edits → the reported bug: report produced, ZERO fix applied. The bundle→L1 pattern (a) lands fixes on ANY CC version (single dispatch level), (b) gives fresh-context specialist fixes without depth risk, (c) dissolves the /seo parallel-edit race (fixes now applied serially by the dispatcher, by file ownership). /harden already proved the pattern in-repo. Chosen even though BDR-060 confirms live nesting works — version-robust by design beats version-contingent.
  • Alternatives rejected: only raise the version floor (BDR-060 alone) — leaves the analyzers version-contingent, and the /seo nested-fix design fragile; keep analyzers self-applying but require CC≥2.1.172 — works on current env but not robust and keeps the parallel-edit race; make the dispatcher apply via direct Edit everywhere (like /harden) instead of hotfixer/feater — loses the fresh-context specialist fix; kept direct-Edit only for /harden's tiny scope.
  • Verification: make test green + 4 real smokes — analyzer emits bundle + edits nothing (md5 unchanged); AUTO fix lands on disk via L1 hotfixer with no confirmation (the exact previously-broken path); GATED withheld pre-approval then applied post-accord; /onboard writes only the report, zero source files.
  • Reference: agents/seo-analyzer.md STEP 12, agents/geo-analyzer.md STEP 13, skills/seo/SKILL.md STEP 1.5, skills/geo/SKILL.md, agents/validator-analyzer.md (reference contract), .audit/job9-report.md §6 option (b); commits a5a7b54/6df42e4/c498b93/70fb3b4. Linked to BDR-060 (nesting floor), LRN-112 (nesting mechanics).

BDR-062 — supersede BDR-031's 275-line CLAUDE.md target: 305 is the assumed reality

  • Date: 2026-07-08
  • Status: accepted (supersedes the 275-line density TARGET of BDR-031 only; BDR-031's core principle — lightening = compression, not path-scope/externalization — stands unchanged)
  • Decision: global CLAUDE.md sits at 305 lines, stays there. job1's density pass took it 319→305 and no later job re-inflated it; the extraction BDR-031 called for is done. Old 275 target (or the 280 guard) now costs clarity more than it saves tokens. The hooks/session-start.sh guard threshold is realigned 280→320: still catches genuine regression (real bloat past 320) but stops firing a permanent "density pass requis" warning on an assumed-final 305.
  • Why: the review (.audit/review-release-1.0.0.md A6) found the guard had warned every session since job1 without the target ever being met — a self-inflicted permanent warning, not an actionable signal. A gate that never goes green trains you to ignore it. Realign to reality; 15-line margin keeps real regressions visible.
  • Alternatives rejected: (a) finish the compression 305→≤275 — the remaining lines are load-bearing constraints, not filler; further squeeze loses clarity for a marginal token gain on a solo repo. (b) leave the guard at 280 and accept the permanent warning — a permanently-red non-blocking gate is noise. (c) rewrite BDR-031 — registries append-only; supersede the target, keep the principle.
  • Reference: hooks/session-start.sh:202-211; supersedes the 275 target in BDR-031 (principle kept). Review remediation A6, 2026-07-08.

BDR-063 — GSC multi-account: OAuth2 installed-app flow + label-keyed token store

  • Date: 2026-07-10
  • Status: accepted (shipped bb1fbb2, develop)
  • Decision: /seo FULL pulls real Search Console + CrUX via a lib/seo-data/ engine. Auth = OAuth2 installed-app flow (one-time interactive consent, make seo-connect), scope webmasters.readonly ONLY (least priv). Refresh tokens in per-label store ~/.claude/seo-data/tokens.json (0600 file / 0700 dir, atomic tmp→fsync→rename under fcntl lock, tokens redacted from listing, gitleaks-allowlisted). (account, property) explicit args on every call — NO global mutable "current account" → two concurrent site audits never conflict.
  • 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.
  • 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.

BDR-065 — Transient planning artifacts: committed during run, deleted post-merge

  • Date: 2026-07-14
  • Status: accepted
  • Decision: superpowers spec/plan docs (docs/superpowers/{specs,plans}/) = run-time artifacts. Lifecycle: committed as feature branch's first commit (subagent briefs extracted from plan on disk; verifier + final review reference them; survive compaction + foreign worktrees) → DELETED in post-merge cleanup chore. Git history at the feature commits = the archive (git show <sha>:docs/... recovers them). Durable knowledge lives in .claude/memory/ registries + contract files, never in spec/plan. Codified in project CLAUDE.md §Transient planning artifacts.
  • Why: user call 2026-07-14 — registries already capture decisions; a stale plan describes a superseded intermediate state and misleads future readers; accumulation pollutes the repo. Precedent: gsc-crux cleanup (8a1fac0, 2026-07-10) did the same — this makes it law, not habit.
  • Alternatives rejected: never-commit (gitignore docs/superpowers) — breaks mid-run: briefs, reviewers, other-machine checkouts need the files; superpowers brainstorming commits the spec by convention. Keep-forever — the drift + pollution complained about.
  • Reference: project CLAUDE.md; cleanup commit this chore; precedent 8a1fac0. Linked BDR-064, LRN-124.
  • Amendment (2026-07-22): DELETE side now AUTOMATED — lib/gitflow.sh _gitflow_purge_transient at gitflow finish (feature/bugfix, pre-merge, on HEAD) git-rm's docs/superpowers/{specs,plans} + scoped commit → develop TIP clean, feature commits stay reachable (git show <sha>:… archive intact). Best-effort: NEVER aborts finish (nothing-tracked no-op / dirty-path skip / commit-fail index+tree restore). Opt-out GITFLOW_PURGE_TRANSIENT=0. Retires the manual chore that slipped (655e364). Universal via ~/.claude/lib→repo symlink (ship-feature STEP 9 + init-project STEP 11 both finish through it). gitignore STILL rejected — unchanged: breaks superpowers' git add of the spec (silently skipped, no travel to SDD worktree). .claude/tasks/{contracts,plans} kept versioned (user call — durable, referenced by decisions.md). Tests: gitflow-test.sh T17 a-d. LRN-138.

BDR-066 — Model routing: reflection inline (session big model), executors pinned sonnet, blocking gate

  • Date: 2026-07-15
  • Status: accepted (partial supersede of BDR-050: /feat dev no longer inline; bugfix/hotfix dev-inline CONSERVED)
  • Decision: reflection (brainstorm, plan, contract, audit judgment, loop decisions) runs on session model (Fable; Opus fallback) — inline or inherit subagents, never pinned down. Execution (code from closed plan, fix-bundle application) runs sonnet-pinned subagents: feater + hotfixer pinned sonnet; SDD implementation+review subagents dispatched model: "sonnet" (ship-feature/init-project); web-validate fixes via hotfixer L1 (was inline Edit). analyzer haiku pin REMOVED (digest feeds plan = reflection tier). verifier + security-auditor STAY sonnet (job9 confirmed — procedural gates, ≤3×/loop). Blocking gate lib/model-gate.md (self-check + witness lib/model-check.sh) wired in 12 reflection orchestrators; small → STOP, unknown → fail-visible; census guard lib/tests/model-routing.test.sh flip-tested.
  • Why: big-model quota burned on mechanical execution (Fable exhausted mid-job8); plan closed at dispatch → executor needs obedience not judgment; fresh sonnet gates catch executor drift.
  • Alternatives rejected: opus pins on audit agents (session-independent) — rejected: session assumed big + blocking gate as backstop, one tier fewer; advisory gate — rejected by user, blocking; split bugfix/hotfix too — rejected: bugfix investigation interleaved w/ fix, hotfix gain marginal vs dispatch overhead.
  • Caveats: client-handover-writer conversion (inline-load → sonnet dispatch, 11 human-gate sites to relocate) DEFERRED to own plan — its opus pin stays inert meanwhile; feater cannot ask → NEED-DECISION report = escalation valve, plan must close decisions; witness reads settings.json — lags --model-launched sessions (self-check compensates).
  • Caveat (execution): /feat re-arch broke 5 stale assertions in lib/tests/loops-light.test.sh (locked OLD feater architecture) — repointed to skills/feat/SKILL.md (FSK, mirrors HOT/HSK split) + new dispatch lock + 1-line reflow in feat SKILL for single-line grep lock (LRN-093 class).
  • Wave 2 (2026-07-15, user directive): wave-1 exclusion list left execution running on the big session model = the waste this split kills. REVERSES the "split hotfix rejected" alternative above (reason held for bugfix — investigation interleaved w/ fix — but NOT hotfix: LOCATE→apply is linear/separable). Changes: /hotfix split like /feat (LOCATE reflection inline + MODEL GATE, hotfixer sonnet EXECUTOR — rewritten dual-use: also the seo/geo/web-validate L1 applier; revert-not-loop preserved) → hotfix JOINS gated group, census 12→13. /commit-change dispatches sonnet commit-changer (propose→dispatcher gates→apply; grouping ON sonnet so NO model gate; AskUserQuestion dropped from agent). /release-candidate dispatches new sonnet release-executor (2 spans prep/finish; when-to-release + push + version-number decision STAY in dispatcher). /doc → doc-syncer (sonnet) dispatch; /status → status-reporter (kept HAIKU — right tier for read-only collection; win = off big model, not the tier). Gate exclusion list now = commit-change/doc/status/release-candidate. Consumer-staleness swept (LRN-113): feat Rule 1 DOWNGRADE + feat commit-split both repointed off the bare executor agents to the /hotfix + /commit-change skills.
  • Wave 3 (2026-07-15/16, user directive): split the last two inline execution-carrying agents like /feat. /bugfix: investigation+diagnosis+contract inline behind the gate; bugfixer = sonnet EXECUTOR (fix + regression test from a closed FIX PLAN; no Agent/AskUserQuestion; BUGFIX-EXEC REPORT). verify+secure loop stays in main loop, executor = its re-dispatched dev (verify-secure-loop.md intro now: BOTH consumers dispatched, no inline branch). FINISHES reversing the "split bugfix rejected" carve-out (hotfix went wave 2, bugfix now) — investigation↔fix coupling accepted, mitigated by structured DIAGNOSIS + verify loop. /code-clean: PHASE-1 audit + validation gate inline (reflection); code-cleaner = sonnet PHASE-2 EXECUTOR (delete approved dead code, inline-load refactorer, re-audit) — refactor NOW on sonnet (inline-load pin was inert on big model). exported-symbol per-item consent stays AT THE GATE. Consumer-staleness swept: hotfix deeper-bug escalation → /bugfix skill (not bare agent); onboard STEP 6 + tour Phase B read-only-audit → general-purpose/analyzer (big model, NEVER the sonnet executor — audit stays big). Both skills STAY gated. Also: Explore built-in kept inheriting session (search feeds reflection = big deserved; custom sonnet override created then reverted — built-in already inherits + no owned prompt). census 36→42, loops-light repointed 35/0.
  • Wave 4 (2026-07-16): client-handover doc-gen → sonnet, REDACTION-ONLY (user flipped from whole-writer after the full read). Key finding: nested audits (/seo,/harden,/web-validate — gated wave 1) must run BIG either way → whole-writer = ~7 extra gate-yields + resumable state machine on a CLIENT deliverable for ~0 extra sonnet work. Design: client-handover-writer TRIMMED to ship pipeline (STEP 1-8, all interactive gates native on big, nested audits inherit big) + doc-gen orchestration (resolve questions/NAP/precheck/overwrite/client-name inline → PACKAGE) → dispatches NEW sonnet handover-doc-writer (STEP 9-16: reads memory+git, synthesizes 6-chapter doc, word-count/skill-leak/anchor gates, renders HTML+PDF; GATE-FREE, no AskUserQuestion/Agent). client-handover JOINS gated group (orchestrates audits = reflection); its opus pin dropped (inherits big via inline-load). census 42→46. Branch feature/client-handover-dispatch (off develop, waves 1-3 merged first).
  • Reference: spec docs/superpowers/specs/2026-07-15-model-routing-design.md + plan docs/superpowers/plans/2026-07-15-model-routing.md (transient, BDR-065 lifecycle), branches feature/model-routing (waves 1-3, merged), feature/client-handover-dispatch (wave 4).

BDR-067 — first public release: versioning reset to v1.0.0 (override "never restart at v1.0.0") — 2026-07-16

  • Decision: first PUBLIC release cut as v1.0.0, treating internal v1.0.0→v4.0.0 as pre-release history. version.txt 4.0.0→1.0.0; CHANGELOG: new [1.0.0] — Initial public release on top (= former [Unreleased] content), old 1.0-4.0 lineage moved UNCHANGED under a ## Pre-release (internal history) banner (provenance). Tag v4.0.0 DELETED (local+origin), v1.0.0 tagged on main. Repo goes public on THIS Gitea (user flips visibility separately — not a git op).
  • Why: launching publicly at v4 misrepresents (implies missed v1-3 to newcomers); v1-4 were private dev. First public impression should be v1.0.0. User directive.
  • Overrides: BDR-055-era release-candidate rule "never restart at v1.0.0 — desyncs tag↔CHANGELOG lineage". That guards ACCIDENTAL mid-lineage restart; a DELIBERATE public-launch reset is the sanctioned exception. CONSEQUENCE for next release: continue from public 1.0.0 (→ 1.0.1 / 1.1.0 / 2.0.0), NEVER back to the old 4.x. The [Unreleased]-BREAKING(CLAUDE.global.md) folds into 1.0.0 harmlessly (first release = breaking vs nothing).
  • Safety (git cherry): found a STALE abandoned release/1.0.0 (July-4 prep, 227 commits behind develop, pushed to origin). git cherry develop release/1.0.0 + content checks confirmed all its real changes (rtk PATH fix, drop-AI-attribution settings backstop, find-skills drop, BLK-016/LRN-098/LRN-101/EVAL-015, all features) ALREADY in develop → nothing orphaned → deleted it (local+origin). Cut fresh v1.0.0 from CURRENT develop, not the stale branch.
  • Method: release-candidate skill gates honored (when-to-release, push) but PREP done manually — backward version (4.0.0→1.0.0) + CHANGELOG restructure exceed the sonnet release-executor's forward-bump assumption (reflection, stays big). LRN candidate: a version RESET is editorial, not mechanical — don't dispatch the forward-only executor for it.
  • Status: SHIPPED. origin main=dc4f78b, develop=6c23d6f, tag v1.0.0 sole tag; v4.0.0 + stale release/1.0.0 removed from origin.

BDR-068 — /capitalize + /close auto-persist memory (finish→develop + push); scoped LRN-069 exception — 2026-07-16

  • Decision: when /capitalize (or /close = --ritual) writes entries AND the aiguillage branched a chore/<name> off develop THIS run, new STEP 5C auto-finishes that branch → develop + pushes origin/develop. Default ON. --no-push holds it on the chore branch (pre-BDR-068 behavior). WORKING branch (memory rides feature/bugfix) or rc-3 commit-fail → 5C skips. push-fail → merge already local, report + manual push (no retry/reset).
  • Why: memory's value = cross-session persistence; a ritual commit stranded on an unmerged chore branch is INVISIBLE to the next session on develop → the ritual defeats itself (user-identified gap). Memory = append-only/low-risk; the human-gated MERGE (aiguillage) is a CODE safeguard, and LRN-069's push-gate guards surprise CODE/release pushes — neither applies to an end-of-session memory persist.
  • Scope: /capitalize + /close ONLY. /prune-memory + /reconcile stay fully human-gated (curation/report may want review before landing). NEVER auto-finish a branch the run did not create.
  • Amends: LRN-069 (push needs explicit go) — scoped exception for memory-only ritual persist; gitflow-aiguillage.md "never gitflow finish" — carved for capitalize/close.
  • Files: skills/capitalize/SKILL.md (STEP 5C + aiguillage branch-capture + STEP 6 outcomes + Rules + arg-hint --no-push), lib/gitflow-aiguillage.md (exception note). Tests unaffected (run-deterministic covers memory-commit.sh surgical scope, not the persist step).
  • Status: implemented on feature/close-auto-persist, UNMERGED (human gate).

BDR-069 — permissions deny: keep broad .env.* glob, keep .env.example name (option A) — 2026-07-16

  • Decision: Write(path) deny rules inert (Claude Code matches Edit(path) only) → 5 secret-write bans converted to Edit(). Mirrored 9 secret patterns Read denied but Edit did not → Read/Edit parity 14/14. New read-allowed/write-denied class: lockfiles (*.lock, package-lock.json, pnpm-lock.yaml, go.sum) + node_modules/**. Kept Edit(**/.env.*) BROAD despite matching .env.example (mandated by CLAUDE.global.md:206). No rename.
  • Why: deny glob = absolute, no exemption mechanism (LRN-130). Only lever = glob shape. Narrowing to .env*.local fails open on .env.production/.staging — real secrets outside Next.js convention.
  • Cost accepted: scaffolder/doc-syncer degraded on .env.example — Edit/Write/Read/Grep/Glob blocked; Bash heredoc still works (Bash(cat *) allowed). Ergonomic tax on /init-project, not a hard block.
  • Alternatives rejected: (B) narrow glob → weakens .env.production; blocked by auto-mode classifier as unauthorized self-modification (EVAL-024). (C) rename → env.example sidesteps glob at zero security cost, but ~30 refs (scaffolder, doc-syncer, init-project, deploy, 3 archetypes, link.sh, install-plugins.sh, toggle-external.sh) + repo's own root .env.example + seo-data.test.sh + gitignore !.env.example (BDR-030) → refactor, user declined.
  • Files: settings.json, templates/settings/SETTINGS.md (taught the broken Write() pattern → fixed at source so /onboard stops propagating it).
  • Status: implemented on chore/fix-inert-write-deny-rules (07ca738), UNMERGED (human gate).

BDR-070 — claude-seo (github.com/AgriciDaniel): cherry-pick, never install — 2026-07-17

  • Decision: adapt useful scripts into our tree, /seo stays sole entry. Do NOT run install.sh / plugin install.
  • Why: their CODE is real (326 tests, render_page.py 428l Playwright, url_safety.py 622l SSRF) — their INSTALLERS destroy our work. install.sh:49 cp -r skills/seo/* overwrites our SKILL.md. uninstall.sh:45 globs ~/.claude/agents/seo-*.md → deletes our seo-analyzer.md (42K) it never installed (verified dry-run). extensions/*/install.sh:42 replaces settings.json with {"env":{...}} on parse error. skills/seo/SKILL.md:119 injects Skool upsell footer into deliverables (leaks to /client-handover client PDFs). hooks.json registers global PostToolUse exit-2 → blocks our dispatcher mid-bundle.
  • Alternatives rejected: (plugin install) → both /seo coexist namespaced → non-deterministic dispatch, silently loses our FR-legal axis on an unpredictable fraction of runs. (install nothing) → forgoes render_page/url_safety/unlighthouse we lack.
  • Verdict on parity: their README lies (dual JSON-LD validator = 2 hyperlinks, zero .py calls; "zero-network"/"fully offline" false). Our system is more honest; we keep FR-legal (their whole repo: 2 hits), fix-bundle+ownership, trajectory-17/20, NAP anti-seed.
  • Files: none installed. Findings drove the whole seo-geo-integrity branch (21 commits).
  • Decision: I1's narrowed Off-page axis (brand mentions from STEP 6 only, backlinks+authority declared §14-unauditable) is the FINAL state, not a placeholder awaiting data.
  • Why: measured, not assumed. GSC has no links endpoint (API = Search Analytics/Sitemaps/Sites/URL-Inspection only; links report UI-only). Common Crawl hyperlinkgraph domain-edges = 17.3 GB gzipped (+879MB vertices, +2.3GB ranks), HEAD-measured live. Scanning it per-audit is non-viable + abusive to a nonprofit. The reference impl (claude-seo commoncrawl_graph.py:169) caps download at 500 MiB = 2.9% of edges, sorted by source ID → arbitrary slice reported as a backlink profile, "70/100 health". A random sample dressed as a measurement — the exact failure class the branch removes.
  • Consequence: B1/B2/B3 all killed. Weight (10-15%) unchanged — re-deriving for an axis that won't widen churns historical scores for nothing.
  • Only free viable source: Bing GetUrlLinks — first-party only (never a competitor), blocked on client's Bing account → raises W2's value (BLK-017), does not unblock it.

BDR-072 — SPA: honest refuse, no headless browser (R2 chosen over R1) — 2026-07-17

  • Decision: rendercheck verdict client-rendered → On-page axis N/A, excluded from weighted global, NEVER scored zero. No Playwright, no Chromium. User-arbitrated.
  • Why: a zero says "your on-page is bad"; N/A says "we couldn't see it" — only one is true, and /client-handover gates on 17/20. curl on a shell returns "missing" for every meta/H1/JSON-LD → a page of FALSE findings + a bundle that "fixes" tags that already exist. STEP 2 recorded RENDERING: SPA since forever and NOTHING acted on it. Verdict from what the server SENT (package.json can't tell React-SPA from Next-SSR).
  • GEO angle (sharper): AI crawlers (GPTBot/PerplexityBot/ClaudeBot) are WORSE at JS than Googlebot — fetch HTML, largely don't execute. A client-rendered site is near-invisible to the engines the audit serves → §0 alert + SSR/SSG top user action, aligns CLAUDE.global "public sites never SPA".
  • Alternatives rejected: R1 Playwright (~300MB Chromium, breaks bash+curl purity) — user chose refusal. Refusing IS the finding.
  • Files: lib/seo-data/render_check.py, seo/geo STEP-5 gates (20d3082).

BDR-073 — deterministic scoring: split LLM judgement from arithmetic — 2026-07-17

  • Decision: LLM emits WHICH findings + severity (irreducible judgement); engine computes the /20. Reuses /harden's scale (-15/-8/-3/-1, clamp, /5 into /20) → one vocabulary across the family.
  • Why: /harden had a real scale (SKILL.md:435), /seo had NONE → every axis felt → two runs over identical code diverged, while /client-handover gates on 17/20. H2 sharpened it: once drift reports real change, a self-moving score is visibly noise. Same principle as engine-side cannibalisation grouping — never hand a model 1000 rows to add.
  • Makes computable (was prose): "N/A is not a zero" (R2 on-page, I1 off-page) → axis excluded + weights renormalised, verified all-20 with 2 N/A → global 20.0. Prevalence: affected/sampled shift severity ONE step (≥50% escalate, single de-escalate).
  • Files: lib/seo-data/score.py (4818c61).

BDR-074 — Remove config-protection edit-block guardrail [accepted] (2026-07-17)

Deleted hooks/config-protection.sh + its settings.json PreToolUse registration + lib/tests/config-protection.test.sh. Hook blocked model Edit/Write on quality-gate files (settings.json, gitflow.sh, .githooks, doctor.sh, hooks, lib/tests, lint) via one-shot .claude/.config-edit-ok sentinel. Removed per user req — friction editing own config > guardrail value; user = human operator. Residual: gitflow pre-commit guard + Gitea branch protection still block direct code commits main/develop; only edit-time block gone. Alts rejected: warn-only (exit0+log), targeted relaxation. Supersedes any prior config-protection decision.

BDR-075 — Framework-wide 3-way adversarial plan-challenge phase [accepted] (2026-07-17)

After a plan/reflection elaborated + before execution, 3 fresh blind sub-agents (correctness/robustness/simplicity) attack it; main loop RE-THINKS every aspect a BLOCKER lands (named change or [deferred]) + re-challenges once if plan materially changed. Reusable lib/challenge-plan.md + new agents/plan-challenger.md (read-only, big-model per BDR-066 — audit judgment, NOT sonnet). Fail-safe (never fail open: mute→retry→escalate), severity-driven (any single-lens BLOCKER=must-address, NOT consensus — lenses orthogonal), advisory into existing human gate. KIND tunes lenses: build-plan/proposals/fix-bundle. Wired 11 orchestrators: ship-feature/init-project/feat/bugfix + onboard/audit-delta/code-clean + seo/geo/harden/web-validate. Excluded (no real plan): hotfix/tour/analyze/client-handover/release-candidate/spec. Audit found 0 repo-owned plan-challengers pre-existing (only vendored gstack autoplan, sequential+unwired). See EVAL-026.

BDR-075 amendment (2026-07-18) — hotfix INCLUDED via logic-only guard

Supersedes the "Excluded: hotfix" clause of BDR-075. hotfix now wired (STEP 1.8, Option B): GUARD skips purely cosmetic fixes (CSS/copy/typo), fires the 3-lens challenge ONLY when the fix touches control flow/behaviour (off-by-one, wrong operator, behaviour-changing config, execution-altering import); a BLOCKER → escalate to /bugfix (its STEP 3b runs the full phase). 12 orchestrators wired. Still excluded (no forward plan): tour/analyze/client-handover/release-candidate/spec. Per user (Option B). Branch feature/hotfix-challenge-guard, unmerged.

BDR-076 — Dispatched judgment agents pinned OPUS; session model = orchestration + inline reflection ONLY [accepted] (2026-07-19)

Reverses the BDR-066 rejected alternative "opus pins on audit agents (session-independent)". Context changed: session default now Fable (Mythos tier, /model 2026-07-19) — inherit meant every dispatched audit/challenge burned Fable quota, exactly the waste BDR-066 killed for executors. New rule: Fable does ONLY main-loop orchestration + reflection (brainstorm, plan, contract, synthesis, gates); EVERY dispatched subagent pinned. Pinned model: opus (big tier, session-independent; NEVER sonnet — silent audit downgrade, the thing old §F5 guarded): analyzer, plan-challenger, seo-analyzer, geo-analyzer, validator-analyzer + onboard's 6 general-purpose audit dispatches (model="opus") + tour Phase B. NOT pinned (justified deviation from approved "7 agents"): interviewer + client-handover-writer — inline-load only, never dispatched → frontmatter pin inert + misleading (BDR-066 wave-4 precedent: its inert opus pin was dropped); they ARE the main loop = Fable per the rule. Explore built-in stays inherit (wave-3 decision conserved: no owned prompt, search feeds inline reflection). Local session pin opus-4-8[1m] dropped from .claude/settings.local.json (gitignored) — Fable default from settings.json now applies in this repo too. model-gate.md unchanged (still guards inline reflection, Fable-or-Opus = big). Census: model-routing.test.sh §3 flip + §11 (61 pass), loops-light 35 pass, full make test green. User directives via gate: "Opus partout" + "Supprimer le pin". Branch feature/opus-pin-audit-agents, unmerged.

BDR-077 — Model-tiering v2: 4-tier explicit routing, mode-based splits, no-inherit dispatches [accepted] (2026-07-19)

Supersedes BDR-076 scope + amends BDR-066. Doctrine: session model (Fable) = main-loop reflection/orchestration/planning/logic ONLY; main-loop retention criteria = interactive | conversation-context access | orchestration decision | dispatch overhead > step cost. NOTHING dispatched inherits: typed agents = frontmatter pin, built-ins = model= at every call site (fable for skill-runner reflection children, else complexity tier). Spike+smoke proven: model:"fable" resolves claude-fable-5 (enum-validated, loud fail, no silent fallback); call-site override BEATS a typed pin (sonnet-pinned verifier ran haiku). Fail-safe pin rule: mixed-mode agents keep the HIGH tier as pin, overrides go DOWN — forgotten override over-tiers (cost), never downgrades judgment. Mode-based splits (commit-changer precedent generalized; file splits rejected): doc-syncer audit(opus)/patch(sonnet) — ALSO fixed a latent defect: /doc dispatched an agent whose STEP 8 interactive gate could never fire; gates hoisted to a DISPATCHER PROTOCOL section; handover-doc-writer synthesize(opus)/render(sonnet) via run-scoped .audit/handover-draft-<RUNID>.md + DRAFT COMPLETE sentinel; seo/geo collect(sonnet)/judge(OPUS PIN)/template(sonnet) via .audit/*-signals-<RUNID>.md + COLLECTION COMPLETE + fail-closed judge + dispatcher ERROR contract (mute/ERROR judge NEVER carried into templating; retry once, escalate). File split only for a genuinely new role: plugin-probe (sonnet, facts-only) + plugin-advisor repinned opus reasoner (fail-closed on missing PROBE REPORT) + lib/plugin-gate.md (checkpoint + apply gate, doc-commit ×N include pattern). Inline→dispatch conversions: scaffolder, onboarder, doc-commit steps ×5 flows — their sonnet pins were INERT since creation, now live; CHANGE SUMMARY crosses the doc dispatch into doc-commit (LRN-126 wire). Tier moves: validator-analyzer opus→sonnet (deterministic runner); commit-changer propose=opus/apply=pin; ship-feature/init-project code-review dispatches = opus explicit (WAS an inherit leak); client-handover-writer's 7 skill-runners = model:"fable". Every wave shipped an IN-WAVE planted-input smoke as its merge gate — all PASSED disk-verified. Census §12-18 (125 pass; one vacuous line-wrapped lock self-caught = LRN-093 live). 6 waves, branches feature/model-tiering-w1..w6, merged on user standing signal. Plan: challenged 3 blind lenses + 1 confirmation (1 BLOCKER closed by spike, 8 MAJORs + 8 MINORs closed by named changes, 0 deferred). Refs: .claude/tasks/plans/2026-07-19-model-tiering-v2-{analysis,plan}.md.

BDR-078 — ctx7 coverage: central fast-libs list + once-per-session reminder hook; every code path covered [accepted] (2026-07-20)

Refines BDR-053 (single surface). Audit 2026-07-20: coverage PARTIAL — find-docs fired on user doc-questions only; ship-feature 0c / init-project 5c pre-fetched; /feat //bugfix executors + ad-hoc coding NEVER consulted ctx7; fast-libs list hardcoded 3× (drift risk). 4 closures shipped: (a) find-docs description += BEFORE-writing-code trigger (fast-moving lib, even without doc question, unless fresh cache) + cache-first rule in body (tee fetched docs to .ctx7-cache/); (b) feater+bugfixer briefs += fast-lib docs rule — read fresh .ctx7-cache/<lib>*.md, else npx ctx7@latest fetch max 2 topics, else ctx7 cache miss: <lib> in NOTES + proceed (executors lack Skill tool → Bash path); (c) hooks/ctx7-reminder.sh UserPromptSubmit — ONE fire/session (sentinel on session_id), only when project manifest carries fast-libs; reports cache state; skips turns; always exit 0; (d) lib/fast-libs.sh = SINGLE SOURCE (detect / cache-status verbs, JS package.json anchored full-key match + Python requirements/pyproject, 7-day freshness, LC_ALL=C sort locale-independent) consumed by hook + 3 pipeline skills + 2 briefs. 2nd session surface DELIBERATE, not a BDR-053 reversal: 053 killed a 490-tok ALWAYS-ON rule duplicate; hook costs ~0 quiet, 1 line once when fast-libs present. Alternatives rejected: PreToolUse Edit/Write gate (fires per-edit = noise); description-only fix (probabilistic, executors unreachable). Tests: lib/tests/fast-libs.test.sh 11 checks (anchored/near-miss/py/none, cache fresh/stale/missing, hook fire/sentinel/quiet×2); shellcheck + full make test green. Branch feature/ctx7-coverage, unmerged (human gate). Amendment (same session): skills/find-docs = machine-owned dist (gitignored, ctx7 regenerates on fresh clone) → durable copy of closure (a) lives in install-plugins.sh STEP ctx7 (idempotent grep-guarded python patch, fixture-verified); live SKILL.md carries the same edit uncommitted by design.

BDR-079 — profile set symmetric on managed externals + MCPs [accepted] (2026-07-20)

Audit (user ask "profile toggles externals both ways?"): ASYMMETRIC. Enable side OK — gstack on-demand from submodule when pack off (shared skills-disabled/gstack__* convention with toggle-external.sh, interoperable), externals restored from parked, magic delegated to toggle-external. Disable side MISSING: cmd_set trimmed only gstack + MANAGED_PLUGINS → set backend left emil/frontend-design/design-motion/impeccable active + magic registered; SKILL.md claimed both-ways toggle (true only at enable). Shipped: (1) MANAGED_EXTERNALS (emil-design-eng, frontend-design, design-motion-principles, impeccable = exact union of profile external usage; darwin-skill excluded — not task-type-driven) + MANAGED_MCPS (magic) allowlists, same doctrine as MANAGED_PLUGINS; (2) cmd_set refactored to 4 trim helpers (disable_{gstack,plugins,externals,mcps}_not_in) — symmetric, nothing outside allowlists ever auto-touched; (3) enable_skill external += from-source fallback (ln -sf skills-external/<name>, mirrors toggle-external) — closes the "missing symlink" warn; (4) stale usage() NOTE ("NOT toggled automatically") + SKILL.md fixed. Hermetic test profile-set-managed.test.sh 16 checks: fixture repo (both *_REPO_OVERRIDE), fake claude shim on PATH logging calls + flat-file MCP registry — gstack on-demand, external from-source, park/restore round-trip, magic add/remove calls, non-managed untouched. shellcheck + make test green. Branch feature/profile-managed-externals, unmerged (human gate).

BDR-080 — bug routing inverted: /bugfix primary, /investigate explicit-only [accepted] (2026-07-21)

Old routing "Bug → investigate (bugfix if gstack off)" + gstack ON by default → every bug took path bypassing own quality pipeline (gitflow aiguillage, contract, fresh verifier + security gates, doc-sync, .claude/memory registries) — /bugfix relegated to near-never fallback. Skill comparison: same core doctrine (root-cause iron law, hypothesis loop, regression test, 3-strike stop, >5-files alert) but incompatible wrappers — investigate monolithic (same context investigates+fixes+verifies, ~1075-line SKILL.md w/ gstack preamble/telemetry/onboarding, capitalizes to ~/.gstack learnings.jsonl framework never reads at session start); bugfix orchestrator (reflection inline, sonnet bugfixer executor, fresh gates — BDR-066, LRN-083). Composition rejected: skills superpose in context, don't compose — invoking investigate inside bugfix = two full workflows, two completion protocols, two memory systems loaded at once. Decision: CLAUDE.global.md routing line inverted — bugfix primary; investigate ONLY on explicit ask for gstack ecosystem (cross-project learnings, /freeze scope lock, long no-commit investigation). Alternatives rejected: keep investigate primary (bypasses framework), embed investigate inside bugfix (context conflict, dual memory). Known drift noted at write time: Index table rows BDR-074..079 missing (pre-existing, /prune-memory scope).

BDR-081 — Config recalibrated for Claude 5 family (Opus 5 dispatch tier) [accepted] (2026-07-30)

Opus 5 (released 2026-07-24) now backs every model: opus pin (BDR-076/077) + any /model opus session. Research (official migration guide + web + registries): Opus 5 OVER-delegates (inverts LRN-030 Opus 4.8 trait that CLAUDE.global.md:43-47 compensated), self-verifies (explicit verify instructions → over-verification, "removing them reduces wasted tokens with no loss in quality"), literal following (conservative-reporting clauses depress recall; MUST/CRITICAL over-triggers), scope expansion = named regression, written deliverables +30-40%. Claude Code injects Opus-5-only anti-delegation prompt sections (heron_brook + subagent_steer_delegation, issue #80988, server-gated, no opt-out) — prose caps would triple-stack. Shipped: delegation block → model-neutral WHEN-guidance + explicit gates carve-out (verifier/security/challenge still dispatch as written); "staff engineer" self-check bar dropped; finish-whole-task clause folded into Deviations (gone-WRONG→STOP still wins); deliverable-length rule; design hook \bux\b dropped (\bui\b KEPT — 0 FP, 1 logged TP, lock-tested); plan-challenger grounded-doubt→[MINOR] in-place reword (grammar byte-identical). Plan challenged by 3 blind Opus 5 plan-challengers: correctness CONCERNS(4) / robustness FATAL(5, BLOCKER: all surfaces symlink-deployed LIVE — gates fire post-deployment) / simplicity CONCERNS(4); every fix adopted as prescribed (scratch-validation before live hook write, minimal diffs, ux-only, MINOR-routing). Alternatives rejected: leave as-is (nudge actively counter-productive); hard spawn caps in prose (harness injects one); confidence axis on challenger grammar (consumer unwired); dropping \bui\b (no evidence). NOT touched: verify-secure-loop + fresh gates (harness architecture BDR-049/050, ≠ model self-check prose); Security/Architecture sections (BDR-021); settings effortLevel xhigh (user pref — Opus 5 carry-over trap → LRN-139); superpowers plugin wording (external upstream). Plan+synthesis: .claude/tasks/plans/2026-07-30-opus5-config-tuning-1238.md. Branch feature/opus5-config-tuning, unmerged (human gate).

BDR-082 — seo/geo analyzers de-prescribed for Opus 5 (C1) [accepted] (2026-08-02)

BDR-081 N5 follow-on, user-directed apparatus (plan+3-lens challenge+census+dogfood). Method: audience×mode-range invariant — dedup ONLY verbatim same-audience (spec rule / bundle-item payload / phase-local caveat) same-mode-range repeats; cross-mode + agent↔dispatcher twins stay (standalone paths need them). Census-FIRST: lib/tests/seo-geo-contract.test.sh 71 locks (verdict grammar, sentinels, ALL STEP headers incl. interiors, item fields, score labels, envelope keys), flip-proven 7 mutations→7 FAILs, committed BEFORE reword. Shipped: self-output verification removed (":970 run twice"→conditional integrity guard; ":1217"→single-shot-scoped), 2 pre-BDR-061 vestigials fixed, caps softened (P0-rule/MANDATORY/ALWAYS→plain content rules), 2 essays compressed, checklist :1309→routing map rows verbatim (challenger caught it = routing table, NOT self-check), true same-range dups only (seo Handoff+landing-page blocks; geo ZERO — all claimed pairs distinct on inspection). FROZEN: guard-first url-guard orderings, :550 denominator-before-sampling (ordering IS the honesty mechanism), R2/NAP/COVERAGE/citation invariants, external-freshness checks (world drift ≠ self-verification). Deltas: seo 1528→1503 l ("P0 rule" 2→0, ALWAYS 1→0, MUST 5→4, NEVER 9→9 = class-B bans kept); geo 1106→1107 (MANDATORY 1→0, MUST 4→3). Plan challenged correctness FATAL / robustness FATAL(3 BLOCKER) / simplicity CONCERNS + confirmation FATAL(9) — every BLOCKER closed by named change (§5bis record). Dogfood before/after on frozen zenquality copy: judge-replay on frozen signals (zero collect variance) + templates + fresh collects + e2e judge + 42/42 assert battery BOTH sets + blind reader "interchangeable; all deltas = presentation variance both directions OR after MORE spec-conformant". Alternatives rejected: keyword dedup (challengers proved audience/range-blind — most annex "twins" were distinct obligations), FULL/aggressive dogfood (billing gate killed nested CLI; left as user option), banner/shape locks (LLM-convention layers wobble — lock strings only). Evidence: .audit/dogfood-baseline/ (18 artifacts + DOGFOOD-VERDICT.md), plan .claude/tasks/plans/2026-07-30-seo-geo-deprescription-1402.md. Branch feature/seo-geo-deprescription, UNMERGED (human gate).

BDR-083 — contract gates: deterministic floor (GATE 0) under the verifier [accepted] (2026-08-24)

User asked what to take from unlazy skill (Leonxlnx/unlazy 2.1.0, MIT). Verdict on its verification ARCHITECTURE: teaches nothing we lack — contract + fresh blind verifier + bounded loops + order invariant already shipped (BDR-049/050/066, LRN-083). Real gap found elsewhere: between executor and GATE 1, NO deterministic floor. GATE 1 = LLM dispatch; verifier's mandatory PROOF: line = a line the verifier WRITES — nothing structurally stops it being produced without executing anything (LRN-048 demands a pass prove it looked; the proof is self-reported prose). Decision: import unlazy's gate ledger INTO the existing contract, never alongside it. Palier 2, user-chosen over doctrine-only / defer. TAKEN: criterion carries an oracle (indented CHECK: cmd + EXPECT: success-only marker + EVIDENCE: slot); fail-closed = exit 0 AND marker (a nonzero process never passes because its error text carries the token); evidence persisted INTO the contract → the fresh verifier reads fact, not the executor's report; ABANDON: <id> <non-blank reason> = impossible criterion never deleted, blocks CONFORME, routes to human gate (new verdict token ABANDONED(n) — distinct routing from ECARTS ⇒ distinct token, not a sub-line to re-derive); 4 gate-authoring rules (observe the named artifact / success-only marker / positive control before any absence check / recompute supplied numbers, never copy one into EXPECT); 4-pass executor discipline (feater full; bugfixer narrowed to fix+test under "keep the fix minimal", pass 3 = negative control proving the regression test fails without the fix). REFUSED + why: Stop hook decision:"block" — contradicts "STOP + human escalation", "gone WRONG → STOP re-plan", "merge only on explicit human signal"; a hook FORCING continuation is the inverse of our gates; its 6-block release either traps the session or gives up; each block = an agent continuation = real tokens. Approval store ~/.unlazy/approved (binds ledger+cmd+CWD+shell+timeout+platform+full PATH) — exists to execute ledgers INHERITED from untrusted repos; our contracts are authored by our own orchestrator in our own repo ⇒ biggest chunk of their 28k checker closes zero threat here. .unlazy/<scope>/ tree (PLAN+GATES+gates/+status.log+session+hook-state+locks/) — a 4th bookkeeping tree beside .claude/tasks/{contracts,plans} + memory/ + audits/. tree N Depth-Tree effort arithmetic — disowned by unlazy's OWN research/validation-protocol.md (v1 six-run figures unreproducible), while the repo DESCRIPTION still advertises the retracted claim. Node checker (28k .mjs + 54k .mjs tests) — lib stack is 100% bash, Health Stack = shellcheck *.sh hooks/*.sh lib/*.sh would cover none of it. OWNS: ownership leases — deferred (Palier 3): our parallel dispatches (seo/geo, 3 plan-challengers) are read-only, the write-collision problem does not exist yet. Shipped: lib/gates.sh (~250 l bash; status never executes and never writes · run ALWAYS re-executes every runnable criterion — trusting written evidence is the failure being closed, so there is no incremental mode to get wrong; rc 0 MET / 2 UNMET|malformed / 3 ABANDONED; parse fails closed on partial oracle, duplicate id, unindented attribute, runnable-without-EVIDENCE, and executes nothing when the ledger is malformed). GATE 0 in lib/verify-secure-loop.md (red floor → executor re-dispatch with the NOT-MET rows, NO verifier spawned; own 3-iteration budget, separate from conformity; malformed ledger fixed in the main loop, never dispatched to a dev). Order invariant now GATE 0→1→2 on every re-loop. lib/contract-interview.md: ORACLES section + template + ABANDONMENT lifecycle + per-flow oracle weight. agents/verifier.md: oracle-consumption rules — a red or unrun oracle is NEVER overridden by reading code; a MET oracle proves the ORACLE, not the English sentence ⇒ vacuous oracle = NOT-MET, the one judgement no command can make; verifier may re-run a CHECK but never edits the contract. lib/tests/gates.test.sh 64 assertions (sentinel-proved non-execution, with its own positive control asserted first). Alternatives rejected: Palier 1 doctrine-only (CHECK:/EXPECT: become decorative without an executant); port the Node checker (stack break, shellcheck-blind); fold ABANDONED into ECARTS (would send a dev to fix the impossible and eat the 3-iteration budget); status revalidating old evidence (that trust is the failure being closed). Branch feature/contract-gates, UNMERGED (human gate). make test rc 0, shellcheck clean, e2e verified on a real contract in the documented template.

BDR-084 — /tour multi-project: parallel runners, bounded LRN-083 derogation [accepted] (2026-08-24)

User asked whether agent parallelism on independent tasks is ACTIVE. Measured first (LRN-080): (a) mechanics — nested probe, 1 dispatched orchestrator fanned 3 sub-agents, execution windows all overlap, 9.1s vs ~18s sequential ⇒ nested parallel dispatch WORKS; (b) doctrine — already prescribed at 3 layers (harness "single message" injection; /seo, challenge-plan, /cso, graphify explicit same-message mandates; graphify even anti-sequential wording); remaining serializations all MOTIVATED (audit-delta crash-resilience documented, verify-secure-loop order invariant); (c) behavior — probe orchestrator batched spontaneously without being told "parallel" (N=1), this session fanned 8+7 agents/message during the RED. Conclusion: nothing to add globally — a CLAUDE.md "parallelize" line would duplicate-stack the harness injection (BDR-081 anti-pattern). ONE real sequential-but-independent candidate: /tour multi-project (independent repos, one by one, no documented reason). User gate: option "tout paralléliser" chosen over report-only-only and no-change, WITH the model invariant "orchestrateur garde le modèle orchestrateur; skills/agents suivent leurs orchestrateurs définis". Decision: STEP 0 routes (1 project = inline unchanged; ≥2 = STEP 0b fan-out). One general-purpose runner per project, ALL in ONE message, dispatched with NO model override — inherits the session model (model-gate already validated big; a runner carries tour's reflection: fix decisions, convergence). Inside a runner every agent keeps its defined tier (security-auditor sonnet, Phase B opus, doc-syncer sonnet two-mode). Dead/mute runner ⇒ explicit RUNNER FAILED summary row (mute is never a pass). Capitalize offer stays MAIN LOOP ONLY (registries = shared state). LRN-083 derogation, bounded: per-project fix loop + convergence now run INSIDE the dispatched runner. Bounded because nothing a runner decides touches shared state — independent repos, per-repo chore branches, branches stay UNMERGED for human review exactly as inline (report-as-approval-gate design unchanged). Precedent: client-handover-writer already a dispatched orchestrator running parallel audit loops (BDR-077). Alternatives rejected: report-only-only parallel (my recommendation — user overrode: full parallel wanted); one sub-orchestrator agent .md file (drift risk vs SKILL.md, the runner reads the skill from disk instead — client-handover→/seo precedent); pinning the runner (would put tour reflection on an executor tier — inverts BDR-076); global CLAUDE.md parallelism line (duplicate of harness injection). Census §12: 6 locks (fan-out present, no-pin, single-message, capitalize main-loop, RUNNER FAILED, no pinned runner), flip-tested. Branch feature/tour-parallel, UNMERGED (human gate).

BDR-085 — user permanent rules: writing-style always-on in rules/, web rules path-scoped [accepted] (2026-08-25)

User supplied 4-block permanent rule text (writing / website / code security / self-check), asked: coverage check, conflict check, integrate. Coverage verdict: security CORE (parameterized queries, input validation, env-var secrets, AuthN/AuthZ split + default deny, no stack traces, fail closed, least privilege) ALREADY in CLAUDE.global.md §Security — NOT duplicated. NEW: entire writing-style block, design anti-default list, public-site done-checklist, web-app specifics (browser-exposed keys, service-key/client split, RLS, server-side auth, IDOR, hashed passwords + cookie flags, field minimization, rate limiting, upload restrictions). Placement: CLAUDE.global.md at 308/320 (session-start density guard) → no room for ~30 always-on lines. Decision: rules/writing-style.md WITHOUT paths: (always-on load, same session cost, outside the 320 budget) + rules/web-building.md + rules/web-security.md WITH paths: (lazy-load = token win, fire only on web/code files). Project CLAUDE.md doctrine line amended with the budget exception. Feeds C2 self-contradiction audit. Conflict carve-outs, stated INSIDE the rules: registries keep caveman format (fragments, em-dashes, bullets); code comments keep code style; structured skill/report templates keep their formats; robuste/transformer banned in buzzword sense only (robustness lens, math transform allowed); no-Inter default rule carries "existing brand identities keep their fonts" (ZenQuality deliverables use Inter+Playfair by brand decision — client-handover BDR). Self-check rule scoped to DELIVERABLES (text, site, feature), not every conversational reply — literal "avant de me rendre quoi que ce soit" would append a compliance note to every chat answer, pure noise. User can re-widen. Alternatives rejected: compress into CLAUDE.global.md (~11 lines to fit → loses the carve-outs, zero headroom left); path-scope writing-style (applies to conversation, not file reads → would never fire in chat-only sessions); one merged web file (two concerns, one-rule-one-file). Branch feature/user-writing-web-rules, UNMERGED (human gate).

BDR-086 — darwin bug-pass scope: verified defects fixed above threshold

  • Date: 2026-08-26
  • Decision: units < threshold get full weighted-gap optimization loops (per-unit checkpoint). Judge-VERIFIED defects (file:line, confirmed) in above-threshold units get targeted fixes in a grouped pass — same paired 3-judge validation, one batched checkpoint. User-gated at the scorecard.
  • Why: leaving a verified destructive path (hotfix git restore . wiping tolerated user edits, file scored 85) unfixed = score-worship; rubric serves quality, not the inverse.
  • Alternatives rejected: strict threshold (ships known bugs); optimize-everything (cost, HL-4 diminishing returns).
  • Reference: run 2026-08-26, commits 6eceedb..6eac7fb, .claude/audits/DARWIN-2026-08-26.md.

BDR-087 — Stop hook = attention signal only, never control flow

  • Date: 2026-09-03
  • Decision: hooks/notify-attention.sh wired on BOTH Notification (matcher = input-needed set) AND Stop (no matcher). One script, branches on .hook_event_name when .message/.notification_type absent → Stop yields "Claude has finished responding". Bell + toast now fire every turn end.
  • Why: Notification types cover input-needed ONLY. Turn-end had no event; nearest was idle_prompt, ~60s late — useless for Remote-SSH user away from screen. User enumerated turn-end as required case.
  • Alternatives rejected: second dedicated script (duplicates terminalSequence + jq logic, two files to keep in sync); idle_prompt alone (60s lag); SubagentStop too (noise, subagent completion not user-visible moment).
  • Guard vs prior refusal: BDR-083 (unlazy review, GATE 0) REFUSED a Stop hook using decision:"block" (forces continuation, inverts human gates). THIS Stop hook returns terminalSequence + suppressOutput only, exit 0, zero control-flow effect. Signal ≠ control. Do not read the refusal as banning Stop outright.
  • Status: accepted.
  • Reference: LRN-146 event-coverage gap, BLK-020 client-side faults, LRN-145 terminalSequence pattern. Verified live: turn-end + AskUserQuestion both ring; permission_prompt unexercisable under defaultMode: auto.

BDR-088 — gstack Playwright bump shared via lib; update helper never touches submodule tree

  • Date: 2026-09-15
  • Decision: gstack_bump_playwright_if_unsupported moved out of install-plugins.sh into lib/gstack-playwright.sh, sourced by install-plugins + update-all + doctor. update-all's submodule block now calls gstack_submodule_update_with_bump: re-applies bump after successful submodule update --remote; on failure prints git's own message, hints make plugin, returns 1. Never touches submodule worktree.
  • Why: BDR-029 caveat open — bump survived only till next make plugin, update path never re-checked OS support. Real gap, user-reported.
  • Alternatives rejected: conflict-RECOVERY branch (discard package.json+bun.lock → retry → backup/restore). Withdrawn at human gate after 4-agent challenge: concentrated 3 BLOCKER + 4 MAJOR. Worst case = bump discarded, re-apply silently no-ops (bun absent / registry down — bump returns 0 on every path), ./setup rebuilds browse against unsupported Playwright → BLK-008 returns. Pre-existing behavior just failed the update and kept bump intact, so the "improvement" could regress a working install.
  • Deviations carried from "code MOVED not changed": || true on ostag capture (line exited 1 on every non-Ubuntu host → aborted caller under inherited errexit, reproduced); timeout on all 3 bun calls, exit 124 → warn + no bump (TERM'd install leaves node_modules half-written, poisons the support grep).
  • Status: accepted.
  • Reference: commit 2cebecb, lib/gstack-playwright.sh. Links BDR-029, LRN-070, LRN-071, LRN-150, BLK-008.

BDR-089 — No Playwright browser-cache pruner; read-only doctor report instead

  • Date: 2026-09-15
  • Decision: doctor.sh gains own ── Playwright browsers ── section — cache size, per-revision the installs requiring it, counts of unreferenced dirs + broken links. Zero deletion anywhere in the lib.
  • Why: measured, not assumed. ~/.cache/ms-playwright/.links/ registers 3 installs — gstack 1.61.1 → rev 1228, gsd-pi nvm 1.61.0 → 1228, gsd-pi ~/.local 1.63.0 → 1243. Every dir on disk referenced → 0 bytes reclaimable. Playwright's own _deleteStaleBrowsers already unions across all registered installs on every install.
  • Alternatives rejected: hand-rolled pruner guarded on "revision resolved by gstack's local playwright" (the originally requested shape) — that guard keeps 1228 and DELETES 1243, breaking gsd-pi. The guard was wrong, not just its implementation.
  • Status: accepted.
  • Reference: commit 2cebecb. Links LRN-151, BDR-088.

BDR-090 — Destructive shell work → autoMode soft_deny/hard_deny; ask tier abandoned

  • Date: 2026-09-15
  • Decision: 10 rules leave the static tiers (user's own edit): rsync kill -9 killall pkill out of deny; python3 -c python -c xargs sed cp mv out of ask. Cover rebuilt in autoMode — 7 soft_deny (write outside cwd, rsync --delete, SIGKILL/kill-by-name, in-place edit spanning >1 file, directory move, inline interpreter or xargs that deletes or writes outside cwd) + 3 hard_deny (secret exfiltration, prod deploy, disarming guardrails). Intent clears a soft block for the CURRENT TURN only — encoded as a rule line, no setting exists for it. classifyAllShell stays false. permissions.deny +10 .env reader rules (sed awk cut tr sort uniq diff od xxd strings), 6 of which sat in allow.
  • Why: ask raises no prompt under defaultMode: auto (LRN-146, verified live). It gated nothing, so a destructive rule moved deny→ask was a silent loosening dressed as a confirmation. soft_deny = the tier the classifier enforces and user intent clears. hard_deny = the 3 classes no command pattern can express — read-then-send spans turns, a prod target is a name not a verb, widening a deny list is self-disarming.
  • Alternatives rejected: keep them in ask — inert, false sense of a gate. Back to deny — blocks legit process cleanup and inter-project copy, and the user works Bash-first under auto mode. classifyAllShell: true — closes the allow-tier blind spot but bills a classifier call on every git status. Published-history rewrite as hard_deny — user declined; rebase then an ordinary push stays uncovered, known gap.
  • Scope fix (same commit): autoMode.environment named /home/bchanot/Documents/atlast, its FTP deploy target and its customer data, inside the file link.sh:21 symlinks to ~/.claude/settings.json. Every project received atlast's facts, and this repo's own Gitea remote contradicted the block's "no remote configured". Global block now machine-generic; atlast facts moved to atlast's gitignored .claude/settings.local.json.
  • Caveat: the guardrail hard_deny bars REMOVING a deny/soft_deny/hard_deny entry, not adding one. Future loosening goes through /permissions or the user's own edit — deliberate, confirmed with the user.
  • Status: accepted.
  • Reference: settings.json, doctor.sh check_automode, templates/settings/SETTINGS.md. Links LRN-153, LRN-146, BDR-004.

BDR-091 — Ask, don't guess: open-choice sweep + mid-run channel supersede "one question upfront"

  • Date: 2026-09-16
  • Decision: CLAUDE.global.md rule → ask on a VISIBLE (placement, wording, order, behavior), PUBLIC NAME (command, flag, endpoint, file) or SCOPE ("X too?") choice the request leaves open, even mid-task; class 4 (internal technical, no observable effect) never. lib/contract-interview.md STEP 2 = CLARIFY: pass A (gaps: outcome / scope / constraints) at contract time; pass B (open-choice sweep, 3 classes) ONCE at each flow's PLAN step, no question cap, >5 open → under-specified, list + stop; "you decide" recorded A: delegated — <default>, never re-asked. New MID-RUN CLARIFICATION: executor halts NEED-DECISION + CLASS: tag; visible / public-name / scope → human verbatim; internal → loop decides, max 2 round-trips. New HOW TO ASK (LRN-102: ≤4 → one AskUserQuestion, context in option descriptions; else plain text ending the turn). Wiring: feat STEP 1, bugfix STEP 3, hotfix LOCATE (pass A stays silent autofill; one re-dispatch on class-tagged BLOCKED = the sole hotfix re-dispatch), ship-feature STEP 2, init-project STEP 3; interviewer: class 1-3 item never (assumed), one extra targeted question. Executors (feater, bugfixer, hotfixer) report the class. Locks: contract-verifier (9), loops-light (hotfix), gates (3).
  • Why: gap-only trigger structurally blind to taste — "add a share icon" passes outcome / scope / constraints and the icon lands wherever the executor put it; raising the 3-question cap changes nothing. feat:153 / bugfix:165 told the orchestrator "make the decision HERE", twice, before escalating = institutional guessing. Fresh re-dispatch keeps the tree, loses the executor's reasoning → a plan-time batch costs less than the same question mid-run; the mid-run channel stays for leftovers.
  • Alternatives rejected: bigger budget (quota was never the limiter); new lib/clarify.md (extra hop, STEP 2 already the mandatory passage every orchestrator runs); global rule only (skills carried explicit counter-instructions — zero questions ever, make the decision HERE, max 3 questions — the specific beats the general).
  • Risk watched: chattiness. Brakes = class 4 exclusion + over-5 guard. hotfix identity (speed, silence) = the flow to watch; if pass B fires on most hotfixes the class definitions are too wide, not the flow.
  • Status: accepted. Behavioral check OPEN: /feat "add a share icon to the header" must ask placement before dispatch; the fully specified variant must ask nothing → record in evals.md.
  • Reference: spec + plan docs/superpowers/{specs,plans}/2026-09-16-ask-dont-guess* (purged at finish, in history at 22ce57f), commits 9eb6934..22ce57f, merge 56bd035. Supersedes the CLAUDE.global.md:51 rule line. Refines BDR-049 (contract), applies LRN-102. Links LRN-157.

BDR-092 — docker + node framed by the classifier (autoMode.allow), ask rules retired

  • Date: 2026-09-16
  • Decision: Bash(docker run|exec *), Bash(docker[-| ]compose up*), Bash(node -e *) out of permissions.ask. New autoMode.allow ($defaults first): (1) local dev containers — docker exec/run/compose against a workstation container whose name lacks prod/production, running a repo SQL file or script inside, output piped; Remote Shell Writes / Production Reads / Sensitive Remote Exec scoped to sensitive-named hosts; a literal DROP/TRUNCATE/DELETE on the command line stays under Mass Delete. (2) project-local node — node <file>, npm run/pnpm/yarn scripts, npx/pnpm exec of a lockfile-declared package, effects in cwd. +2 soft_deny: docker data destruction (rm -f, volume rm/prune, system prune, compose down -v, --privileged, bind mount outside cwd); undeclared node packages (npx/dlx absent from lockfile, npm install <name>). model bump to fable 5.1 committed alongside.
  • Why: real gate = built-in Remote Shell Writes / Production Reads soft_deny catching docker exec into supabase_db_game; inside the classifier allow = exception tier (hard_deny > soft_deny > allow > explicit intent). Static Bash(node *) allow is suspended under auto (wildcarded interpreter) → prose is the only lever for a conditional node permission; awk/echo statics short-circuit, node cannot. ask inert on 2.1.273 (probe, LRN-155) — retiring it is forward-safe: if the documented prompt behavior lands, those entries would prompt for exactly what should run free.
  • Alternatives rejected: static permissions.allow for docker (short-circuits the classifier, framing impossible); keep the ask entries (inert today, wrong tomorrow); strict on every .sql (blocks the repo's verify scripts).
  • Trade-off accepted: a repo SQL file runs even when its content is opaque to the classifier — local dev DB only, resettable.
  • Guardrail: S6 (loosening = user's own edit) overridden explicitly by the user for this change; diff reviewed on the branch before merge.
  • Status: accepted. Verified: jq valid; claude auto-mode config shows the 4 entries with $defaults expanded; doctor.sh autoMode PASS; live docker exec -i supabase_db_game psql … -f - < verify/0043 … | tail → ROLLBACK, exit 0, no prompt. claude auto-mode critique printed nothing (2.1.273).
  • Reference: settings.json, templates/settings/SETTINGS.md (autoMode.allow row + interpreter note), commit 5eccc3f, merge ddadca6. Links BDR-090, LRN-153, LRN-155, LRN-156.

BDR-093 — 21st.dev: magic MCP retired for the 21st CLI + skill pack

  • Date: 2026-09-22
  • Decision: @21st-dev/magic MCP out, @21st-dev/cli (bin 21st) in — upstream supersedes it (README 1.17.1: "one unified CLI", old magic config now a thin proxy to the same endpoint). Auth = 21st login, browser token in ~/.config/21st; no API key, no MCP process. install-plugins.sh Step 8.7: npm i -g (pin 21st in plugins.lock.json) + staged 21st skills install --global --agent claude + TTY-only login offer + pack disabled by default. toggle-external.sh manages 21st as a pack (names globbed from skills-external/21st-*, parked under plain names = interoperable with profile.sh's external path). 5 design skills (21st-ui-build, -ui-explore, -ui-review, -cli-use, -ai) in design/web/web-full/full + MANAGED_EXTERNALS; -registry/-design-sync installed, parked. MANAGED_MCPS now empty (mcp type kept, advisory). Gate: GATE-BLOCK: 21st 21st-ui-build — CLI = required-manual class, magic's old slot. Outward-facing verbs (publish*, submit, edit, delete, remove-from-catalog, profile set|upload) → one autoMode.soft_deny entry, NOT ask (LRN-153).
  • Why: user ask ("plus besoin de mcp / api, juste en cli"), confirmed at the source, not the marketing page — the 21st.dev web docs still show the MCP init --client flow and an API key; the npm package README is what states the supersession. Net wins: one less MCP loaded per session, no API key to protect by reference (BDR-026/BDR-057 vector gone), no unauthenticated local callback server (LRN-110 gone with the tool).
  • Blocker + shape it forced: 21st skills install --global writes <HOME>/.claude/skills/<n>/SKILL.md and calls assertNoSymlinkComponents on every path segment — ~/.claude/skills IS a symlink to repo/skills, so the documented 21st install-skill fails hard ("Refusing to access symbolic link …/.claude/skills", reproduced live). → install under mktemp -d as HOME, move each skill into skills-external/21st-* (gitignored), symlink on demand. Same impeccable/ctx7 machine-owned pattern.
  • Alternatives rejected: project-scope install (<cwd>/.claude/skills) — Claude Code would ALSO scan it as project skills in this repo = every 21st skill listed twice; add the pack to link.sh's EXTERNAL_SKILLS — that loop force-creates symlinks, resurrecting a default-disabled pack on every make link; all 7 skills in the design profiles — publishing flows cost 2 descriptions/session for a workflow the user does not run; ask entries for the publish verbs — inert under auto (LRN-153, BDR-090/BDR-092 already retired that tier).
  • Status: accepted. Verified: toggle enable/disable/restore/idempotent round-trip (7 skills), profile.sh show design, gate INCOMPLETE→names 21st with the two commands, gate PATH repair proven under env -i PATH=/usr/bin:/bin with an nvm-stub, profile-set-managed.test.sh 17/17, make test (2 pre-existing FAILs, gitleaks binary absent on this host), shellcheck clean. OPEN for the user: npm i -g @21st-dev/cli && 21st login — the deny rule Bash(npm install -g *) means the agent cannot run it.
  • Reference: install-plugins.sh Step 8.7, update-all.sh 7.4, lib/toggle-external.sh, lib/profile.sh, lib/profiles/*.profile, lib/design-tool-gate.sh, lib/design-gate.md, CLAUDE.global.md, README.md, settings.json, .gitignore, .gitleaks.toml, .env.example, link.sh. Supersedes the operative parts of BDR-059 (the 4 mcp__magic__* ask entries) and the magic instance of BDR-026/BDR-057; BDR-025's required-manual class stands, its magic example does not. Links LRN-158, LRN-110.
  • Date: 2026-09-22
  • Decision: install-plugins.sh Step 8d + update-all.sh run npx -y impeccable@<pin> skills install -y --providers=claude --scope=global --no-hooks straight through the ~/.claude/{skills,agents} symlinks → lands in skills/impeccable + agents/impeccable-*.md (both gitignored, machine-owned). No staging, no mv. Precondition guard: both symlinks must already point into the repo, else "run make link first". Pin failure → @latest + loud "bump plugins.lock.json" warn. Profile-parked copy stays parked (install to live slot, mv back to skills-disabled/). Success = rc 0 AND installer output free of Download failed|Could not check for skill updates (imp_install, mirrored in both scripts, sets IMP_FAIL). Pin 3.2.0 → 4.1.0 (CLI only; skill dist 4.3.1 + engine 0.1.5 own tracks). link.sh EXTERNAL_SKILLS drops impeccable; skills-external/impeccable/ gone. lib/design-gate.md §5: suggest /impeccable init once when frontend project lacks PRODUCT.md.
  • Why: (1) 3.2.0 skill dist gone upstream → rc 1 → make plugin printed "run manually" forever. (2) --scope=project + staged mv moved skill dir only, dropped the 4 subagents the same run wrote. (3) Global scope IS the repo install under the symlink model; staging bought nothing. (4) rc lies once a copy exists. Probe 2026-09-22, sandbox HOME, real installer: 4.1.0 then 3.2.0 → rc 0, "Could not check for skill updates: invalid zip data … Existing skills were left unchanged"; same-pin rerun → rc 0, "Skills are up to date (v4.3.1)"; both leave SKILL.md byte-identical (same mtime, same sha).
  • Alternatives rejected: before/after skill-version compare (first idea) → cannot separate rotted-pin no-op from up-to-date no-op, identical files + rc 0 both times → false warn on every rerun. --force → re-downloads ~15 MB engine + dist on every make plugin, and the CLI's own update check already refreshes without it. Shared lib/impeccable.sh for imp_install → deferred: two mirrored 12-line helpers vs new lib + test; revisit at a third caller. Project-scope install inside this repo → Claude Code scans .claude/skills too = skill listed twice, shadows the global copy (seen live, TODO T6).
  • Status: accepted. Verified: harness on extracted Step 8d, sandbox HOME, real installer, 4/4: fresh install (skill 4.3.1, 4 agents); rotted pin over a copy → fallback fires; same pin rerun → no false warn; parked + rotted → fallback, returned to skills-disabled/. make test green minus 2 pre-existing T16a (gitleaks absent), shellcheck clean. update-all.sh block: bash -n + shellcheck only, same helper, not run end to end.
  • Reference: install-plugins.sh Step 8d, update-all.sh, plugins.lock.json, .gitignore, link.sh, lib/design-gate.md §5. Links LRN-159, LRN-158 (21st: opposite case, installer refuses symlinks → stage), LRN-077 (pin doctrine), BLK-021.

BDR-095 — Data-loss guardrails: static deny for transfer/destructive tools, push every commit, brief ≠ user authority

  • Date: 2026-09-22
  • Decision: layer C of the post-incident plan (layers A OS/backup and B sandbox/managed-settings = user's side). (1) permissions.deny static: lftp/sftp/ftp/curl -T, rsync --delete, xargs rm, pipe-to-shell, chmod/chown -R, sudo/doas/pkexec, dd/mkfs/shred/wipefs/fdisk/parted, chattr, docker volume drops/system prune/compose down -v/--privileged/docker.sock/-v /:, git push --delete/--mirror/:ref/--force-with-lease, branch -D, filter-branch, reflog expire, stash clear/drop, clean -f, --no-verify, core.hooksPath. ask entries for pipe-to-shell + stash drop/clear retired into deny. (2) hard_deny: destructive tool against a local path from a variable/~/../wildcard/outside cwd+tmp, trace or rehearsal included, brief carries no user authority. (3) soft_deny: docker entry reworded (promoted items out), + discarding uncommitted work. (4) environment: incident, push discipline, Claude never deploys. (5) lib/gitflow.sh: start pushes -u origin, _gitflow_merge_into pushes target, install-hook writes post-commit + post-merge push hooks (--follow-tags, timeout 30, GITFLOW_NO_PUSH=1, warn-never-block). (6) hooks/unpushed-guard.sh SessionStart+Stop systemMessage. (7) doctrine section "Destructive tools & data loss" + 4 report-only agents clause. (8) lib/tests/guard-bash.test.sh = spec of the PreToolUse guard, hook not shipped (BLK-022).
  • Why: 21/09 wipe (LRN-160): prose tiers named neither lftp nor a local trace, the orchestrator's brief authorized it, auto mode inherited by the sub-agent, nothing pushed for 4 days. User: Claude never deploys, only explains; test = dev server on this machine → lftp has zero legitimate use. Static deny resolves before the classifier and inside sub-agents (doc verified 2026-09-22); prose is judgment, static is a rule.
  • Alternatives rejected: ask tier → doc says it prompts under auto, LRN-155 probe says inert, unresolved → nothing entrusted to ask. Keep docker volume drops in soft_deny (BDR-092) → "à tout prix" beats in-turn convenience; user runs them by hand. Post-commit hook alone for push → git merge fires post-merge, not post-commit (T18f caught it) → lib pushes the target explicitly AND post-merge hook emitted. Force-push allowance after amend → static deny stays; a rejected push warns and the user decides. Stop-hook decision: block on unpushed work → BDR-083 refused control-flow hooks; systemMessage only.
  • Status: accepted, on feature/destructive-guardrails. gitflow-test.sh T18 7/7 + T19 3/3, unpushed-guard 9/9, make test green minus 2 pre-existing T16a, shellcheck clean, doctor 0 errors. NOT DONE: hooks/guard-bash.sh (BLK-022). Existing projects need gitflow install-hook re-run for the push hooks.
  • Reference: settings.json, lib/gitflow.sh, .githooks/{post-commit,post-merge}, hooks/unpushed-guard.sh, lib/tests/{guard-bash,unpushed-guard}.test.sh, CLAUDE.global.md, templates/settings/SETTINGS.md, agents/{verifier,plan-challenger,analyzer,security-auditor}.md. Extends BDR-090 BDR-092 (ask inert, soft_deny doctrine); links LRN-114 (T19 drift gate), LRN-155, BDR-083.
  • Amendment 2026-09-22 (user go: "je valide les deux"): no per-project install-hook step. (a) GLOBAL: make link runs gitflow global-hooks → generates githooks/ from the emitters + git config --global core.hooksPath ~/.claude/githooks (symlinked into the repo) → every repo on the machine is protected + auto-pushed, gitflow-initialized or not (faunosteo class). Git precedence: a repo's local core.hooksPath wins. (b) RECONCILE: hooks/session-start.sh calls gitflow reconcile-hooks once per session → rewrites a lagging .githooks/ (LRN-114 automated), banner line + commit reminder; pre-commit whitelist extended to .githooks/** so that refresh commits on develop. (c) Opt-outs per repo (foreign clone): git config gitflow.protect false, gitflow.autopush false — human-only, static deny on git config gitflow.* and on the GIT_CONFIG_GLOBAL=/GIT_CONFIG= env bypass. (d) Hermetic tests: make test + the two suites committing on main export GIT_CONFIG_GLOBAL=/dev/null, else the machine's global hooks fire in throwaway repos. (e) doctor: global setting + githooks/ == emitted. Tests T18h, T19d, T20, T21. Rejected: init.templateDir (new repos only, ignored once hooksPath is set); dropping the per-repo .githooks/ (portability to a machine without claude-config). Status: verified once /tmp was freed — gitflow 127/129 (2 pre-existing T16a), review-guards G5 flagged this repo's own stale .githooks/ (the LRN-114 gate doing its job; refreshed via install-hook), shellcheck clean. make link (global core.hooksPath) refused to the agent by the classifier twice → user runs it. Doctor gained a "Scratchpad" check (BLK-021 mechanism).

BDR-096 — Branch deletion guard: lib-only delete after verified merge, main/develop undeletable at the ref layer

  • Date: 2026-09-24
  • Decision: user rule "auto-delete OK only once merged into develop or main; main/develop never". (1) gitflow_delete = single delete path (finish + CLI delete <br>): rc 2 unknown, rc 6 protected base, rc 5 not ancestor of develop or main (gitflow_merged_into_base, fail closed when neither base exists), then git branch -d kept as 2nd layer. (2) 4th generated hook reference-transaction: prepared call, refs/heads/main|develop with all-zero new value → exit 1, whatever issued it (branch -d/-D, update-ref -d, rename, script, sub-agent); gitflow.protect false opt-out checked only on a hit. GITFLOW_HOOKS array = single hook list (write/emit/reconcile, T19d, doctor via gitflow.sh hooks). (3) static deny git branch -d|--delete|-dr|-rd *, -m|-M main|develop*; hard_deny "Branch deletion by hand"; Disarming entry now covers all 4 hooks + gitflow.* config; environment protected-branches line. (4) doctrine CLAUDE.global.md gitflow §, SKILL.md delete op + rc 5/6 rows, guard-bash spec T8w flips to deny, SETTINGS.md/README/CHANGELOG.
  • Why: since BDR-095 start pushes -u origin → git branch -d checks merge into the UPSTREAM (origin/
    , kept in sync by post-commit), not HEAD → its valve is dead; T22a proves it. _gitflow_delete survived only because finish chained it after a successful merge. Same doctrine as 21/09 (LRN-160): mechanical + static before prose; ref-layer hook holds for nested commands and sub-agents where the Bash deny cannot see.
  • Alternatives rejected: hook also refusing UNMERGED deletion → files backend rename = delete + create in separate transactions, branch -d passes zeros as old oid → merged check impossible/false positives on branch -m, user-shell friction; lib + deny carry that rule. Remote cleanup after finish (push --delete origin/<br>) → in static deny since BDR-095, not requested; origin/
    accumulates, flagged to user. -D in the lib → no, -d stays as defense in depth. Interactive brainstorm → user absent (autonomous run), request unambiguous; trade-off (hook blast radius) stated in the report instead.
  • Status: accepted, on feature/branch-delete-guard, UNMERGED (human gate). gitflow-test 152/154 (2 pre-existing T16a, gitleaks absent), T22 12/12 + T23 11/11, shellcheck clean incl. emitted hook, doctor 4/4 hooks match. Hook LIVE machine-wide via global githooks/ while the branch is checked out (symlink follows the checkout).
  • Reference: lib/gitflow.sh, lib/gitflow-test.sh T22/T23, githooks/reference-transaction, .githooks/reference-transaction, settings.json, doctor.sh, skills/gitflow/SKILL.md, CLAUDE.global.md, templates/settings/SETTINGS.md. Extends BDR-095; links LRN-161, LRN-114.
  • Amendment 2026-09-24 (user go: "nettoie aussi les branches distantes une fois mergées"): _gitflow_delete_remote runs after the local delete — ls-remote --exit-code reads the remote tip, gitflow_merged_into_base <tip> re-checks it (unknown or unmerged sha → remote copy KEPT, loud), then push origin --delete. Best effort like the pushes: no origin / GITFLOW_NO_PUSH=1 / gitflow.autopush false → skip; unreachable or refused → "NOT removed" + the hand command, rc 0. Explicit protected-base guard inside the helper too. Static deny on hand git push --delete UNCHANGED: it matches the Bash tool's command string, the lib's sub-process is the sanctioned path (prose says so). Rejected: making a failed remote delete fail finish (merge done, local gone → nothing to roll back; loud is enough); deleting without re-checking the remote tip (a push from another clone would be lost). T24 9/9, suite 161/163 (2 pre-existing T16a). Live: origin/feature/branch-delete-guard + origin/feature/destructive-guardrails removed by gitflow.sh delete (both tips verified merged), bases untouched. On feature/remote-branch-cleanup, UNMERGED (human gate).

BDR-097 — graphify from 200 tracked code files: the banner informs, the user decides

  • Date: 2026-09-24
  • Decision: deterministic threshold, not AI judgment. lib/graphify-gate.sh: git ls-files code extensions (graphify's AST set), vendored trees (vendor|node_modules|third_party|dist|build) excluded, ≥ 200 AND no graphify-out/graph.json → one banner-sized line graphify? N code files ≥ 200, no graph + → /graphify (AST, seconds) — you decide in session-start. Nothing built, installed or updated by the hook. Doctrine: CLAUDE.global.md graphify § carries the threshold + "never graphify claude install without a go"; plugin-advisor thresholds stop pre-enabling graphify at scaffold time. GRAPHIFY_MIN_CODE_FILES overrides (tests).
  • Why: user question "when is graphify worth it, can the AI suggest it, even set it up". Measured first (LRN-162): value = localisation (who calls what), not editing (the file is read anyway); code-only build is free (AST), docs cost session tokens; a query costs 2-3k tokens ≈ two file reads. Below ~200 files grep beats the graph. User's own words: "tu informes, je décide" — an AI-estimated trigger is judgment (irreproducible, invisible when silent), a count is a rule. Fits graphify-out/ being a per-project write the user owns.
  • Alternatives rejected: AI "estimates the project needs graphify" → not reproducible. Auto-build at threshold → writes ~8 MB into the project, user's decision. Interconnection metric (import graph density) → needs the graph itself to compute; file count is the honest proxy. Post-commit graphify update in the gitflow hooks → deferred to a pilot (user picked the inform-only compromise); note update refuses a smaller graph without --force, and graphify hook install is inert under the global core.hooksPath. graphify claude install → rejected again (PreToolUse nudges on every Read/Glob = the context tax, BDR-028).
  • Status: accepted, on feature/graphify-threshold-banner, UNMERGED (human gate). Test 11/11, shellcheck clean; live: this repo silent (74 files), robin_petier fires (214).
  • Reference: lib/graphify-gate.sh, lib/tests/graphify-gate.test.sh, hooks/session-start.sh, CLAUDE.global.md § graphify, agents/plugin-advisor.md, CHANGELOG. Links LRN-162, BDR-028, BDR-021 (conditional graphify rules).

BDR-098 — CLAUDE.global.md density pass 352 → 270: compression only, three name-obvious routing lines dropped

  • Date: 2026-09-24
  • Decision: user go "fais la passe de densité". BDR-031 principle kept (compression, no path-scoping, no externalization, no caveman); BDR-062's 320 guard kept. Method: prose tightened section by section, blank lines after headings removed, the 6 classic Security subsections folded into one bold-labelled bullet list (### Destructive tools & data loss kept as a heading, referenced from Workflow), Session-start / Planning / After-code numbered lists collapsed, Memory-registries prose rewritten (routing list → one sentence, language + format paragraphs merged, close ritual → one sentence), radical-honesty tenets paired two per bullet, gitflow paragraphs re-flowed. Deliberately dropped: routing lines release-candidate, audit-delta, init-project/onboard (name-obvious, the skill descriptions carry them — BDR-031's own criterion), rationale clauses (why English, why caveman), ~/.claude/githooks literal, T22a cite, pa11y/HTML-CSS detail in the web-validate line. Every ## heading verbatim (Design work — full toolchain (tiered by scope) is matched by the design-toolchain hook). graphify section left byte-identical: feature/graphify-threshold-banner (unmerged) edits it, a clean merge matters more than 2 lines.
  • Why: 352 lines after BDR-085/091/095/096/097 growth, banner red since 2026-09-22. Words 2694 → 2302 (−15%), doctor passive estimate ~3.9k tokens; loaded every session in every repo. Token-diff audit of the old vocabulary: 174 tokens absent, all rephrasings or the listed drops, no constraint lost.
  • Alternatives rejected: path-scope Design work / Web sections into rules/ (BDR-031 principle; the design hook needs the section in context regardless of file type); caveman doctrine (BDR-031: instructions-to-follow must stay prose); raise the 320 guard again (BDR-062 already moved it once; a guard that follows the file is not a guard).
  • Status: accepted, on chore/claude-global-density, UNMERGED (human gate). make test unchanged (2 pre-existing T16a), banner density warning gone, doctor 0 errors.
  • Reference: CLAUDE.global.md, hooks/session-start.sh (320 guard), hooks/design-toolchain-reminder.sh (heading match). Links BDR-031, BDR-062, BDR-085.

BDR-099 — C2 coherence: 30 doctrine/skill tensions resolved, doctrine wins, BDR-068 kept as the written exception

  • Date: 2026-09-24
  • Decision: user go on all four groups. G3 judgment calls: (12) BDR-068 auto-finish of the memory-only chore/* by /capitalize+/close KEPT, written into CLAUDE.global.md + gitflow SKILL as the one finish without a live signal; (13) memory commit on develop: hook exemption for a commit that follows merged work, aiguillage chore/* for a standalone memory task — both written; (14) chore/* widened to "maintenance without new behaviour" (memory, docs, cleanup, /refactor, /tour fixes), /commit-change asks the type (public name); (15) /tour never applies a contract-breaking fix → open — needs decision (BREAKING); (16) client-handover: children audit and return FIX BUNDLEs, the main loop applies AUTO items and gates the rest at ONE gate (harden whole bundle, seo D/E, geo G5, CSO dependency bumps); (17) /hotfix on develop → type bugfix, hotfix/* = prod incidents only; (18) /seo aggressive + /web-validate --fix → feature branch, /refactor → chore, via the aiguillage; (19) /doc → chore row + STEP 0. G1/G2/G4 = doctrine wins mechanically (see CHANGELOG). Lib: gitflow init socle via chore/gitflow-adopt merge (T2c).
  • Why: TODO C2 (2026-08-25). Three read-only Plan-agent audits (doctrine+rules / skills A-H / skills I-W), 39 raw → 30 unique pairs, heaviest spot-checked by grep. Two classes dominated: today's own partial fixes (graphify 200 rule missing in init-project/onboard; density pass removed a heading 5 skills cited; deploy routing line) and BDR-095 staleness (push everywhere made deploy/release/tour/capitalize push wording false; global hooks broke init on existing repos). LRN-164 applied to myself: a rule change must grep every citer.
  • Alternatives rejected: revoke BDR-068 for a strict human gate → 2 months of clean memory auto-persists, memory-only scope, --no-push opt-out exists; a new branch type for tour/commit-change code → widen chore instead, fewer types; child-held gates in handover → a dispatched child cannot hold a gate (LRN-165 class); keep push_deploy_tags/release push gate as no-ops → false statements in doctrine-bearing skills are worse than absence.
  • Status: accepted, feature/c2-coherence, UNMERGED (human gate). 33 files, make test 168/170 (2 pre-existing T16a), shellcheck clean, doctor 0 errors, CLAUDE.global.md 282 lines.
  • Reference: CLAUDE.global.md, lib/gitflow.sh _gitflow_adopt_socle, lib/gitflow-test.sh T2c, lib/gitflow-aiguillage.md, lib/verify-secure-loop.md, lib/design-gate.md, 16 skills, 4 agents, CHANGELOG. Links BDR-068, BDR-095, BDR-097, BDR-098, LRN-164, LRN-165, LRN-169.

BDR-100 — Guardrail evasion and partial rule changes get mechanisms, not lessons: refusal ends the attempt, citers census in make test

  • Date: 2026-09-24
  • Decision: user asked "why did you make these errors, fix the causes". (1) Evasion: permissions.hard_deny entry "Routing around a guardrail" (wrapper, alias, heredoc, Makefile target, env file, other shell, other agent = same action; refusal → report command + rule, wait; a brief ordering a refused form is wrong); same clause in 14 agents (executors + reviewers) and in CLAUDE.global.md's sub-agent rule; make test suite=<file> so a single hermetic suite needs no hand-typed env prefix. (2) Partial rule changes: lib/tests/doctrine-citers.test.sh in make test — every CLAUDE.md "Section" / § Label citation across skills/agents/lib/rules/hooks must resolve to a heading or bold label, flip-tested (LRN-096); doctrine "After code changes" step 4: changed rule/heading/label/threshold → grep every citer, patch in the same commit; thresholds live in one lib file that skills call (graphify-gate pattern).
  • Why: root causes established on evidence, not guessed. Evasion: MY brief to E2 said "run the suite with GIT_CONFIG_GLOBAL=/dev/null … exported first" — I ordered the exact form the static deny lists; the executor was refused, then wrote run-rc.sh carrying the prefix one level down and ran it. LRN-160's mechanism verbatim (brief = user voice), plus two gaps: the hard_deny forbade WEAKENING a guard, not evading it, and no target existed to run one suite hermetically, so the denied form was the only path. Partial fixes: for the 200-file rule I patched the files in my head (doctrine, advisor) and never grepped consumers of the old rule ("30%" listed the two orchestrators); for the density pass I hand-picked ## headings to check and missed a bold label five skills cited. Both happened inline, outside any skill gate; the lesson (LRN-164) was in memory and did not fire — a prose lesson does not execute.
  • Alternatives rejected: static deny on bash <scratch>/*.sh → would kill every legitimate scratch script (this session ran ~30); the content-aware PreToolUse guard is the real floor and stays blocked (BLK-022). Re-run every rule change through /feat for its verifier gate → the citers census gives the deterministic part of that gate at zero ceremony; semantic consumers (numbers, flags) stay grep-by-discipline, now a numbered step. Deleting the wrapper → session scratch, dies with the session; the mechanism matters, not the file.
  • Status: accepted, feature/guardrail-evasion-citers, UNMERGED (human gate). doctrine-citers 5/5 (flip + repo, one real dangling fixed), make test 168/170 (2 pre-existing T16a), make test suite= verified, shellcheck clean, CLAUDE.global.md 287 lines.
  • Reference: Makefile, settings.json hard_deny, CLAUDE.global.md Workflow + After code changes, agents/*.md (14), lib/tests/doctrine-citers.test.sh, lib/project-archetypes/rest-api-node.md. Links LRN-160, LRN-164, LRN-169, EVAL-030, BLK-022, BDR-095, BDR-099.

BDR-101 — full = default profile: no selection ⇒ full in force, reset applies it, install applies it

  • Date: 2026-09-25
  • Status: accepted, feature/default-profile-full, UNMERGED (human gate)
  • Decision: DEFAULT_PROFILE="full" once in lib/profile.sh; active_profile() resolves cache absent / empty / legacy none → full. reset = set full (exclusive: enable full's list, park non-listed gstack/managed items). current label-driven: names active_profile(), scores THAT profile only, default — not applied yet until set/apply/reset wrote cache; cross-profile best-guess scan + none/custom sentinels gone. Statusline reads constant (sed, literal fallback), shows full not ?. make plugin Step 11: no selection → reset, selection → set <sel> (Steps 2/10 rewrite skill state every run); Step 8.7 no longer parks 21st pack (profile governs: full links 5 design skills, 2 publishing stay on demand). User-gated: reset semantics, install applies default, README one history line, .env line deleted.
  • Why: user ask "profil par défaut = full". LRN-020 kept honest: full made the REAL default (state = label), not a relabelled sentinel. Parked-gstack count was false signal: gstack OFF on real tree (BDR-030), 0 parked ≠ all enabled.
  • Alternatives rejected: label-only reset (lies about plugins/externals); additive reset (gstack on + apply full, state ⊇ full, label ambiguous); keep none sentinel + statusline ? (request unmet); install display-only (fresh machine ≠ full, 21st pack parked); public profile.sh default verb for installer (reset already IS "go to default"); one shared cache parser (statusline must not spawn profile.sh → 3 copies kept, each commented).
  • Caveats: make plugin re-run re-applies selected profile → manual layering (gstack on over dev) trimmed back. Plugin legs of Step 11 install-immutable (BDR-028 EXIT guard; committed enabledPlugins already match full). LOW security note: cache content not charset-checked before path use (pre-existing in read_profile) → follow-up.
  • Reference: commits e196328 (residue scrub), 0d035fc (profile), 1bbdad0 (install); contract/plan 2026-09-25-default-profile-full-1254; lib/tests/profile-default.test.sh 29 checks. Links BDR-017 BDR-018 BDR-079 BDR-093 LRN-170 EVAL-031.

BDR-102 — agent-skills: no plugin, vendor 3 skills + build floor-guard + routing census + rest-api rule

  • Date: 2026-09-27
  • Status: accepted, feature/agent-skills-borrow, UNMERGED (human gate)
  • Decision: addyosmani/agent-skills (99.4k stars, 25 skills) NOT installed as plugin. Borrowed 4 things, user go: (1) observability-and-instrumentation, deprecation-and-migration, ci-cd-and-automation vendored emil-way at pinned commit 2686b620 (lock agent-skills, install Step 8e tmp+mv, update-all 7.3, link.sh, toggle-external per-name, profiles full/backend/dev); (2) lib/floor-guard.sh diff-scoped bar-weakening detector, verifier STEP 3 mandatory, waiver floor-guard: allow <reason>; (3) lib/tests/skill-routing-census.test.sh TF-IDF description-collision census, WARN 0.50 / FAIL 0.75; (4) rules/rest-api.md path-scoped, distilled from api-and-interface-design minus the one-version rule.
  • Why: 20/25 skills already covered (superpowers, personal skills, gstack, built-ins). Real gaps grep-verified: observability (archetype question only), migration (one strangler line), CI build (ship/cso only detect), bar-weakening guard (security-auditor flags nosemgrep only), collision census (routing section exists because of collisions; darwin scores quality not collisions). Upstream comparison doc: never stack two skill routers.
  • Alternatives rejected: plugin install (1.8k tok/session for 20 % novelty, /spec /review /ship collide with gstack, /code-simplify vs /simplify, trunk-based git + one-version API contradict doctrine, second router); vendor api-and-interface-design whole (one-version rule vs § Web APIs → distilled); grouped agent-skills toggle pack (no shared installer, per-name like emil); web-full profile for the trio (design-class, allowlist wins over "lists bugfix"); Tier 2 prompt ranking (follow-up).
  • Caveats: vendored prompts = third-party content loaded into sessions, the pin is the review point (security-auditor scanned: benign, no hidden Unicode); floor-guard waiver is self-service, WAIVED informational → security MEDIUM, design decision pending with user (require CLARIFICATIONS ack outside test fixtures?); floor-guard prints raw diff snippets a verifier reads (LOW, framing follow-up); update-all 7.3 failure branch leaves .tmp like emil (parity, not fixed); make link after merge to symlink the trio (user).
  • Reference: commits d28c45e (trio), 2b25cb4 (floor-guard), 409db51 (census), 1a8e6de (rest-api); contracts .claude/tasks/contracts/2026-09-27-{agent-skills-vendor,floor-guard,skill-routing-census,rest-api-rule}-1525.md; gates MET ×4, verifiers CONFORME ×4 (2 re-dispatches), security PASS ×2. Links BDR-100 LRN-172 LRN-173 EVAL-032. Case 1 of the same review: ladder in doctrine, feature/yagni-ladder 9315c6c.
  • Amendment 2026-09-27: waiver policy strict, user go: WAIVED outside a test file = gap unless the contract's CLARIFICATIONS names file + reason (verifier STEP 3, loop doc). Commit 6617889. Closes the security MEDIUM caveat above.

BDR-103 — 6-repo review: 5 verdicts, 3 criteria; stars decided nothing

  • Date: 2026-09-27
  • Status: accepted, chore/six-repo-review-notes, merge on user go 2026-09-27
  • Decision (user-approved case by case, order = layer touched per turn → orthogonal):
    repo stars verdict one-line why
    ponytail + chisle 146.7k / 566 no plugin; ordered YAGNI ladder + shortcut: marker into § Code style (feature/yagni-ladder) per-turn + per-subagent injection, prose rules vs writing-style.md, caveman purge precedent, rtk covers the input axis
    addyosmani/agent-skills 99.4k no plugin; vendor 3 skills, build floor-guard + routing census, distil rest-api rule (BDR-102) 20/25 covered, 1.8k tok/session, /spec /review /ship collide, trunk-based + one-version vs doctrine, second router
    ibelick/ui-skills 9.2k nothing installed; 14 micro-rule lines in rules/web-building.md CLI/MCP = curl of raw SKILL.md, third router, baseline-ui stack mandates vs Astro-first; registry of 36 third-party skills (mengto motion pack) NOT evaluated
    reticlehq/reticle 898 parked, 4-step pilot recipe in TODO only real capability gap (store state, verdict with file:line, replayable flows, CI gate); FSL server, PostHog telemetry, per-project build instrumentation, skill auto-runs init
    OmniRoute 70.6k rejected, no recipe subscription cannot pass a keyed gateway; fail-open guardrails, default JWT secret, Socket.dev block on 3.8.5, JA3/JA4 spoofing + free-tier pools
  • Criteria that decided every case: (1) coverage verified by grep on local assets (skills, agents, rules, archetypes), never from the README; (2) permanent per-session cost (descriptions, hooks, MCP schemas) against the share of novelty; (3) conflict with doctrine (gitflow, versioned APIs, Astro-first, ask-don't-guess, fail-closed). Stars decided nothing: 566-star chisle beat 146.7k-star ponytail on method; 898-star reticle is the only real capability.
  • Alternatives rejected: install-then-prune (sunk cost, BDR-047 ECC lesson); one bulk verdict (user wanted one case per turn, each with a build-vs-install call); building reticle's engine (1 286 server files).
  • Caveats: borrowed prompts change upstream with no diff, the pin is the review point; do not re-audit these six expecting more; mengto motion pack from the ui-skills registry is the open follow-up if site-level choreography (GSAP/ScrollTrigger, WebGL hero, masked reveals) proves thin locally.
  • Reference: branches feature/yagni-ladder (9315c6c, de7371d), feature/agent-skills-borrow (d28c45e 2b25cb4 409db51 1a8e6de 7401383 6617889 d71f3a7), feature/web-building-microrules (a2e654d 5a27372), chore/six-repo-review-notes (da35cde 197225a). Links BDR-102 LRN-172 LRN-173 EVAL-032 BDR-047 BDR-006.

BDR-104 — MengTo motion pack: vendor 5 scroll skills via a shared helper + build site-motion; 17 skipped

  • Date: 2026-09-28
  • Status: accepted, feature/mengto-site-motion, UNMERGED (human gate)
  • Decision: (1) lib/vendor-skills.sh = one vendor_pinned_skills <lock-key> [refresh] for every curl-vendored upstream (agent-skills moved onto it; lock skills as list or dict of file lists; python argv lock read; ../charset guard; VENDOR_BASE_URL only as file://; tmp+mv; refresh skips never-installed skills, update-all convention). (2) Vendored at a965851: scroll-world-storytelling, build-threejs-scroll-worlds (+5 refs incl. scroll-conductor.js), scroll-scrubbed-visual-sequence, scroll-scrubbed-word-reveal, scroll-progress-timeline; text only, never demo/agents/binaries; design/web/web-full/full profiles. (3) skills/site-motion personal: invariants of the other 17 (gates, engine choice, Lenis sync, Astro ClientRouter lifecycle, numbered recipes, pitfalls); routed in CLAUDE.global.md Build UI chain + design-gate (not GATE-BLOCK).
  • Why: user asked whether the UI profiles carry motion knowledge for lively sites. Census: component polish deep (emil 27 KB, motion cookbook, impeccable animate), site choreography thin as workflows; ui-ux-pro-max CSVs cover terms but as search rows (LRN-174). Two analyzers read 22 skills: 5 clean workflows/recipes absent locally; 17 covered, generic, buggy (reduced-motion clearProps, gate before registerPlugin, no-JS opacity 0, refresh-rate phi) or Codex/Xcode machinery → distil per LRN-141. Registry sample (11) ≠ catalog (88): always list the tree.
  • Alternatives rejected: vendor all 22 (bugs + duplicates + 17×~100 tok descriptions); distil only (loses the two deep workflows whose value is their full text); second inline curl loop (duplication; helper instead); VENDOR_BASE_URL gated by a companion var (file:// prefix check suffices); grouped toggle pack (per-name like emil).
  • Caveats: vendored prompts change upstream with no diff, pin = review point; GSAP-first content vs BDR-005 motion default, site-motion states the allowance; LOW hardening open: re.match $ accepts a trailing newline, source/path/sha lock fields not charset-checked; make link + bash lib/profile.sh apply full after merge (user); sub-agent leftovers /tmp/mengto-verify (user removes).
  • Reference: commits 2a1ad17 (helper + vendoring), ba14b5e (site-motion); contracts .claude/tasks/contracts/2026-09-27-{mengto-vendor,site-motion-skill}-0002.md; gates MET, verifiers CONFORME after 3 re-dispatches (frontmatter shape, refresh convention, security env override + traversal), security PASS ×2, make test 36 suites green minus 2 pre-existing T16a. Links BDR-103 BDR-102 LRN-141 LRN-174 EVAL-033.
  • Amendment 2026-09-28: the two LOW caveats closed on user ask (415b44e): re.fullmatch guard, commit/source/path validated before URL construction, 4 more hermetic cases (12). Security PASS, verifier CONFORME 9/9.
  • Amendment 2026-09-28 (doctor): make doctor now checks every vendored external (6394fa7, feature/doctor-vendored-skills): lock files present, symlink per active profile, parked ≠ failed, hints. Lib sourceable for the hermetic suite (11 cases); doctor mirrors active_profile() instead of sourcing profile.sh (its main runs on source). Security: lock shape-validated, allowlists on profile/item names. Closes the gap that emil/frontend-design/motion had since their install.

BDR-105 — skill-catalog prune: 9 gstack removed, full ⊇ every profile, max = everything, two plugins off, Stop review off, 21st sign-in gate

  • Date: 2026-09-28
  • Status: accepted, feature/skill-catalog-prune, merged to develop 2026-09-28 (user go "merge le tout")
  • Decision: (1) lib/gstack-removed.sh = single denylist (ship, land-and-deploy, setup-deploy, autoplan, context-save, learn, careful, guard, design-shotgun): in no profile, max included; profile.sh gstack on and toggle-external enable gstack skip them. (2) User rule: full (default) carries everything every other profile carries, one allowlisted exception pr-review-toolkit; max (# SUPERSET-OF: full) = full + parked (make-pdf, diagram, 21st-ai/ui-explore/ui-review) + pr-review-toolkit. 21st trio out of full/web/web-full/design. lib/tests/profile-census.test.sh locks the three invariants live + baseline/mutant fixtures. (3) Live plugin state: brightdata-plugin@synced false, frontend-design@claude-plugins-official uninstalled (byte-dup of the managed skills-external copy). (4) settings.json env.ENABLE_STOP_REVIEW=0: security-guidance keeps regex + commit/push agentic review, loses the Opus call per code-changing turn. (5) lib/gstack-links.sh builds the whole helper tree under ~/.claude/skills/gstack/ (83 hardcoded paths), one lib for link.sh, install-plugins.sh, update-all.sh; lib/doctor-skills.sh counts through symlinks with the census parser. (6) Routing: Ship/PR → ship-feature, never gstack ship. (7) Design gate exit 12 SIGN-IN REQUIRED: installed-but-signed-out 21st → ask ! 21st login, end turn, re-run; explicit "proceed without 21st" = only skip; unknown whoami → 11 with diagnostic.
  • Why: 5-analyzer audit over 150 skills / 53.5k chars of descriptions; harness shows ~19k, least-invoked lose theirs → 78 name-only this session (LRN-175). ship base = origin/HEAD = main on Gitea; land-and-deploy gh pr merge --squash + deploy; autoplan/make-pdf/diagram/careful/guard/freeze hardcode paths only bin + browse/dist linked (LRN-177); context-save without restore; brightdata keyless + bright-data-mcp orders WebFetch replaced; security-guidance 0 findings / 6 days, 1 FP (EVAL-024); doctor undercount ×6. 21st CLI Not logged in → user: ask and wait, not skip.
  • Alternatives rejected: rm symlinks by hand (set/reset re-materialize, gstack on restores everything → denylist instead); per-skill toggles inside ui-ux-pro-max (all-or-nothing, unverified); drop superpowers in the same run (7 skills wired in ship-feature/init-project → tier 2, own branch); keep the 21st trio in design profiles (redundant with impeccable + ui-ux-pro-max, CLI signed out); raise SLASH_COMMAND_TOOL_CHAR_BUDGET (costs context, the opposite goal); keep the official frontend-design plugin and drop the copy (copy is profile-managed and gate-checked); shared 21st auth helper across 3 scripts (breaks 4 fixture suites, changes installer semantics — LRN-178); in-session export TWENTYFIRST_TOKEN remedy (env does not persist across tool calls).
  • Caveats: kept gstack skills still route to removed names in their upstream prose (Skill call fails, doctrine applies); helper tree links every top-level submodule entry (no SKILL.md exposed, asserted); security-guidance commit review quota unmeasured; doctor constants rebased on 2026-09-28 measures; apply is additive → other machines run set full, not apply.
  • Reference: f83f8f7 02b62f7 4c86d6d 729d715 (prune), bd3e525 132bcdf (21st gate); contracts 2026-09-28-skill-catalog-prune-0554 (18 criteria, oracles in .oracles/) and 2026-09-28-21st-signin-gate-1215 (7); plans r4 / r3 after 3 challengers + 1 confirmation each; GATE 0 MET, verifiers CONFORME (iter 2 / iter 1), security PASS ×2; 42 suites green minus 2 pre-existing T16a. Links BDR-030 BDR-101 BDR-093 BDR-095 BDR-080 BDR-025 BDR-070 LRN-175 LRN-176 LRN-177 LRN-178 BLK-023 EVAL-034.

BDR-106 — superpowers: 7 skills vendored at v6.4.1, plugin dropped

  • Date: 2026-09-28
  • Status: accepted, feature/superpowers-vendored, merged to develop 2026-09-28 (user go "merge le tier 2")
  • Decision: (1) plugins.lock.json superpowers entry (obra/superpowers @ 5bf4e78 = tag v6.4.1, path skills, per-skill file lists, always_on: true), fetched byte-for-byte by lib/vendor-skills.sh: brainstorming, writing-plans, subagent-driven-development, test-driven-development, requesting-code-review, using-git-worktrees, writing-skills; STEP 8e vendors, update-all refreshes at the pin, link.sh links, .gitignore ignores. (2) Plugin + marketplace uninstalled (one shot by hand after the fetch proved byte-identical), settings.json keys removed by hand, PROTECTED_PLUGINS = security-guidance only; detect_superpowers = [ -f ~/.claude/skills/brainstorming/SKILL.md ], no plugin fallback; doctor/session-start no longer charge the injection. (3) doctor-vendored always_on class: third lock column, _dv_check_link 5th param — always-on externals are link-checked, never "parked". (4) Citers call the bare names; CLAUDE.global.md maps the four non-vendored skills the vendored text still references (executing-plans → SDD, finishing-a-development-branch → gitflow finish, systematic-debugging → bugfix, verification-before-completion → verifier gates). Vendored text never edited (BDR-104 rule).
  • Why: 7 skills wired (ship-feature, init-project, writing-skills TDD), 8 duplicate personal flows; SessionStart injection 3.6 KB per start/clear/compact + a competing router ("1 % → MUST invoke", BDR-080 conflict); 15 descriptions → 7. Tier 2 of BDR-105.
  • Alternatives rejected: shared auth/detect helpers sourced at top level (break the fixture cp suites, LRN-178); installer-side uninstall (plugin gone before the fetch on a network failure; precedent = comment only, one-shot by hand); detect with plugin-cache fallback (the marketplace dir matches *superpowers* → "vendored" on a plugin-only machine, fail-open); rewriting vendored text to fix cross-refs; vendoring all 15; map text spelling the colon form or wrapping identifiers (criteria 3/7 grep line by line — both caught by challengers).
  • Caveats: upstream cross-refs to the plugin prefix and the 8 dropped skills remain in the vendored text (a call on a dropped name fails, doctrine map applies); no upstream auto-update (bump the pin deliberately); the harness hot-loaded the 7 bare names in the running session after link.sh, the plugin names leave at restart; superpowers-marketplace cache dir may linger empty; other machines: make plugin (vendors) + make link, then uninstall the cached plugin by hand (CHANGELOG).
  • Reference: 18f8c89 (wiring), ddea411 (citers/docs/settings); contract 2026-09-28-superpowers-vendored-1357 (12 criteria, oracles in .oracles/), plan r3 after 3 challengers (simplicity CONCERNS(2), robustness CONCERNS(3), correctness FATAL(5)) + confirmation CONCERNS(1); executors 2/2 DONE first pass; GATE 0 MET, verifier CONFORME 12/12, security PASS; catalog 82 skills, plugin passive cost 670 t (ui-ux-pro-max only). Links BDR-105 BDR-102 BDR-104 BDR-065 LRN-178 EVAL-034.
  • Amendment 2026-09-28 (merge): gitflow finish → 65665a5, no conflict, pushed, local + origin copies removed; the 7 vendored skills stay linked after the merge. Whole prune (tiers 1 + 2) on develop.

BDR-107 — Effort tiering: session high, agent pins, skill entry levels, paired phase shifts, max at escalation [accepted] (2026-09-28)

  • Decision: settings effortLevel high (was xhigh). effort: pin on 20 repo-authored agents by role: low appliers (hotfixer, release-executor, plugin-probe, validator-analyzer), medium executors (feater, bugfixer, code-cleaner, onboarder, scaffolder), high judgment (refactorer, analyzer, commit-changer, doc-syncer, handover-doc-writer), xhigh challengers + gates (plan-challenger, plugin-advisor, verifier, security-auditor, seo-analyzer, geo-analyzer); none on interviewer/client-handover-writer (inline-load), status-reporter (haiku), impeccable-* (vendored). effort: on 28 tracked user-invoked skills = run entry level (low bookkeeping, medium gitflow/prune-memory, high feat/hotfix/bugfix/refactor/audits-with-fix, xhigh orchestrators) + xhigh on vendored brainstorming/writing-plans (skills-external/, re-applied by install-plugins STEP 8e). Five shifter skills effort-{low,medium,high,xhigh,max} loaded by orchestrators per lib/effort-shift.md: medium at dispatch span, own level before challenge synthesis, low at bookkeeping tail, max at verify-secure caps (GATE 0/1/2) + ship-feature 4b; re-assert after nested skill / prose gate. STOP texts name $CLAUDE_EFFORT, suggest /effort-max. statusline shows $CLAUDE_EFFORT; banner warns on CLAUDE_CODE_EFFORT_LEVEL. Census lib/tests/effort-routing.test.sh. Audit script lib/effort-audit.py.
  • Why: session-wide xhigh burned thinking on bookkeeping; EVAL-035: 97 % of thinking in the main loop, sonnet subagents ~26 tok/request → main-loop levers (entry level, shifts) carry the savings; pins = explicitness + future models. A/B /reconcile high→low: requests 18→15, output −27 %, thinking −28 %, time −19 % (EVAL-036).
  • Harness facts (2.1.283): skill effort: applies on user slash invocation and on interactive Skill-tool load; the Skill-tool load applies ONLY when paired with another tool call in the same message (lone call = no-op); re-load re-applies (text deduped); not applied in -p/SDK; prompt cache kept across a shift; CLAUDE_CODE_EFFORT_LEVEL beats every frontmatter; one effort per agent file, no call-site override; unpinned agents inherit the level in force at dispatch.
  • Alternatives rejected: executor pins only (they barely think); escalation-diagnoser agent fable+max (no context, one more agent; main-loop max keeps the failure context); reflection in fable skill-runner children with session medium (loses interactivity); settings.json rewrite mid-run (LRN-098 class); maxEffortLevel caps (hide a mis-pin the census should fail); pins on machine-generated skills (find-docs: ctx7 regenerates, gitignored) or gstack skills (spec, skillify).
  • Caveats: shifts inert headless; a prose gate ending the turn resets to session level (re-assert wired in bugfix and ship-feature 4b); mode-based agents pin their judgment mode; a shift paired with a built-in judgment dispatch would downgrade it (pair with Read/Bash instead); lib/gitflow-test.sh T16a red on this machine = gitleaks not installed, unrelated.
  • Refs: spec docs/superpowers/specs/2026-09-28-effort-tiering-design.md, plan docs/superpowers/plans/2026-09-28-effort-tiering.md, LRN-179, EVAL-035, EVAL-036, BDR-077.
  • Correction (2026-09-28): EVAL-035 counted one record per content block (~2.8× on request counts); deduped figures in EVAL-037: main-loop thinking 99.9% of total thinking (was 96.6%), thinking 5.6% of weighted cost (was 8.4%), sonnet think/request 26→0.2 tok. Conclusions hold, sharper: main loop still carries almost all thinking, executors stay cheap.

BDR-108 — Effort round: every skill carries a level next to its model pin; model pins stay tier aliases [accepted] (2026-09-29)

  • Decision: 3 repo skills pinned (skills-perso low, pdf-translate medium, site-motion high). 25 vendored externals (superpowers 7, agent-skills 3, design stack 9, 21st pack 6) get level from lib/effort-pins.txt, applied by lib/effort-pins.sh after LAST vendoring step of install-plugins.sh (21st pack, STEP 8.7) AND update-all.sh (§7.4). Design stack = ONE level, high. hotfix stays high. Model pins stay aliases (sonnet opus haiku fable). Doctrine: design stack loads paired with first Read; census order-locked by line number; lib/effort-audit.py prints thinking coverage.
  • Why: user rungs (low fix-a-line · medium day-to-day · high refactor/resisting bug · xhigh architecture/audit · max stuck). Latest version of each tier = cheapest or same price (Sonnet 5.5 = Sonnet 5, Opus 5.5 < Opus 5, Haiku 4.5 alone, Fable 5.1 = Fable 5) → no version arbitration, only tier × effort. Stacked skills: last loaded wins → two levels in a stack = effort depends on load order. Aliases track generation free (transcripts: sonnet → sonnet-5 then sonnet-5-5).
  • Alternatives rejected: full model IDs in frontmatter (maintenance, Agent-tool call site enum cannot pin a version, older gen never cheaper); hotfix → medium (no A/B on a reflection skill yet, EVAL-036); design stack medium; untrack design-motion-principles (only tracked external, gated in contract instead); pins on gstack / impeccable / graphify / find-docs / darwin (machine-owned, BDR-107).
  • Gates: GATE 0 MET; verifier ECARTS(3): resync re-apply sat BEFORE the 21st refresh (real, fixed by fresh executor), tracked file out of scope (gated), shellcheck directive unauthorized (clarified) → CONFORME 7/7; security PASS + 4 LOW hardened (criteria 8-9); make test 44 suites rc 0.
  • Refs: contract .claude/tasks/contracts/2026-09-29-effort-round-1315.md, BDR-107, BDR-077, LRN-181, LRN-182, BLK-024, EVAL-038.

BDR-109 — Higgsfield pack: npm CLI + cloned skills, OFF by default, two toggles, allowlist [accepted] (2026-09-30)

  • Decision: @higgsfield/cli (latest) installed by install-plugins.sh Step 8.6. 8 upstream skills git-cloned (higgsfield-ai/skills, tracks main, no pin) by lib/higgsfield-skills.sh into gitignored skills-external/higgsfield-*; refreshed by update-all.sh 7.3b (npm only when npm ls -g owns the CLI). Two toggle-external.sh tools, both off, in no profile, not in MANAGED_EXTERNALS, not in link.sh: higgsfield = allowlist HIGGSFIELD_MEDIA_SKILLS (7 names), higgsfield-websites = 1 skill, landing-page aid inside Design work, never higgsfield website create|deploy|publish. CLAUDE.global.md Skill routing (6 lines, 312/320): explicit ask → enable toggle → Read skill; higgsfield generate cost before paid run. CLI presence = higgsfield_cli_ok probe, never command -v. doctor: pass/info only. settings.json deny += npm i -g, npm install --global, npm i --global.
  • Why: media generation occasional + metered → parked pack costs 0 (8 descriptions ≈ 1.7k tok when linked). websites skill triggers on "landing page", collides with Design work stack, Astro rule, no-deploy doctrine → own toggle, named ask only. Upstream unpinned → allowlist = default deny on added/renamed skills.
  • Alternatives rejected: npx skills add (relinks all 8 into skills/ on every refresh, breaks off-by-default); creative profile (profile apply overwrites the active label, next make plugin runs exclusive set, parks the design stack); +creative profile modifier (parser + statusline + census for a label); commit pin (user: track main like 21st; security gate reports 2 MEDIUM, accepted); glob "every higgsfield-* except websites" (renamed upstream skill linked with no review).
  • Gates: plan challenge 3 lenses + 1 confirmation, 0 BLOCKER, 7 MAJOR closed by named changes; SDD 7 tasks (sonnet), 1 fix round; GATE 0 MET; verifier ECARTS(6) (2 shellcheck suppressions of mine + 4 plan copies) → CONFORME 14/14, again CONFORME after the fix wave; security PASS ×2; final review (opus): 1 Important (drift never reported once enabled) + 5 minors fixed in one wave, 3 deferred with rulings; make test 45 suites rc 0.
  • Refs: contract .claude/tasks/contracts/2026-09-30-higgsfield-pack-1412.md, commits 3dad33e..5350221 (feature/higgsfield-pack), BDR-093, BDR-079, BDR-108, LRN-183, LRN-184, LRN-185, LRN-186, LRN-187, LRN-188, BLK-025, EVAL-039.