Development record
abcd Loads Its Own Rules On Demand
Press Release
abcd ships a modular rules loader so the plugin's discipline never bloats your context. Instead of force-loading a monolithic CLAUDE.md every session, abcd ships a default rule set bundled with the plugin and a per-repo
.abcd/rules.jsonoverride. AUserPromptSubmithook scans eachUserPromptSubmit-event payload, matches recall keywords against domains (COMMITTING, DOCUMENTATION, ROADMAP, ISSUES, INTENTS, LIFEBOAT), and injects only the rules relevant to what you're actually doing. Sessions about React rendering pay zero tokens for the ADR-format rules; sessions about a PR description load only the commit-and-PII rules. CLAUDE.md becomes a small marker block, not a 1000-line wall."I had a 978-line CLAUDE.md silently loading into every conversation," said Bob, staff engineer. "abcd's rules loader took it down to a 30-line marker block plus a JSON file. The full rules still apply — they just turn up when they're relevant. My token bill dropped, my keyboard inputs started landing more accurately, and I never have to maintain CLAUDE.md by hand again."
Why This Matters
abcd already commits to "single source of truth" and "no scaffolding outside the repo." But the existing methodology document (~/ABCDevelopment/.claude/CLAUDE.md) is 978 lines and is force-loaded into every Claude Code session in any abcd-managed repo. Most of those rules don't apply to most prompts — yet every prompt pays the token cost.
Prior art (CARL) demonstrates the fix: domain-keyed rules with prompt-keyword recall, loaded just-in-time. abcd takes the mechanism into its own plugin (rather than depending on CARL the runtime) and ships abcd-shaped defaults out of the box. See research/related-work.md for the full comparison.
This is also the keystone for retiring ~/ABCDevelopment/.claude/ entirely. Without modular loading there's no way to relocate the methodology rules without losing them; with modular loading, the rules ship with the plugin, override per repo, and the dev-root tree can be archived.
What's In Scope
hooks/prompt_router_hook.py—UserPromptSubmithook. Reads merged rules (plugin defaults +<repo>/.abcd/rules.json), keyword-matches thepromptagainst each domain'srecalllist, injects matching rules as a system message. Signature-based dedup; force full re-inject every N turns (N defaults to 5, configurable).- Binary-bundled defaults embedded in the Go binary (
internal/core/...). Domains: COMMITTING, DOCUMENTATION, ROADMAP, ISSUES, INTENTS, LIFEBOAT, PII. Each carriesrecallkeywords +rulesarray. Content harvested in a one-time manual session from the legacy~/ABCDevelopment/.claude/CLAUDE.md. <repo>/.abcd/rules.jsonschema (JSON Schema embedded in the Go binary,internal/core/...). Per-repo file. Can extend defaults, override individual rules, disable a domain entirely, or add custom domains./abcd:ahoywrites a minimal skeleton.- CLAUDE.md marker block owned by
/abcd:ahoy. ~30 lines. Identifies that abcd is active, points at.abcd/rules.jsonand the plugin defaults, lists the active domains, gives the developer the explicit-activation syntax for star-commands. - Star-command bypass —
*<DOMAIN> …(e.g.*ROADMAP draft a milestone) explicitly activates a domain regardless of keyword match. - Dedup + refresh discipline — same rule signature isn't re-injected within the same session; full refresh every N turns to recover from compaction.
- Per-domain
statefield (active/dormant) — lets users toggle a domain off without deleting the rules. abcd rules [domain]CLI subcommand — bareabcd rulesprints the full active rule set;abcd rules <domain>scopes to one domain. Diagnostic / explainability. Aligns with the bare-command-as-render discipline (see02-constraints/04-naming.md); noshowsub-verb (collapses to bare-with-positional-argument).
What's Out of Scope
- Global
~/.abcd/rules.json— explicitly rejected. Keeping a personal cross-repo rules file recreates exactly the "scaffolding accumulates outside the repo" failure mode this intent exists to fix. Per-repo only. - MCP server for runtime rule editing — CARL ships one (
carl_v2_add_rule,toggle_domain, etc.). abcd ships JSON-only; the agent editsrules.jsonvia the standard file-edit tools. MCP integration is a candidate if friction is real. - Star-command bibliography — only domain activation; no arbitrary
*<command>macros (CARL's broader capability). - Cross-
promptrule chaining — keyword recall is independent perprompt. No "session sticky" mode. - Migration tooling — the harvest from legacy
~/ABCDevelopment/.claude/CLAUDE.mdto plugin defaults is a one-time manual session, not a/abcd:cullcommand. See note in the brief.
Acceptance Criteria
BDD format, per
itd-1-acceptance-gates. These gates are checked byintent-fidelity-reviewerwhen this intent moves toshipped/.
- Given an abcd-installed repo with
.abcd/rules.jsondeclaring the COMMITTING domain active, when the developer sends apromptcontaining "commit" or "PR" or "git add", then the COMMITTING rules appear as injected system context within that turn. - Given the same repo, when the developer sends a
promptunrelated to any active domain'srecallkeywords, then zero abcd rules are injected and the token overhead is < 200 tokens (header only). - Given the same
promptrepeated three times in a session, when dedup is enabled, then the rules inject onprompt1 and skip on prompts 2-3 (until forced refresh onprompt5). - Given a developer runs
*ROADMAP draft a milestonewith no roadmap-related keywords, then the ROADMAP domain's rules inject regardless of keyword match. - Given a fresh
git cloneof an abcd repo with only the plugin installed, when Claude Code starts a new session, then CLAUDE.md is < 50 lines and contains the abcd marker block. - Given the legacy
~/ABCDevelopment/.claude/CLAUDE.md(978 lines), when harvested intodefaults.json, then every methodology rule with broad applicability is preserved (manual review, recorded inresearch/legacy-harvest.md).
Open Questions
- What's the right N for forced refresh? CARL uses 5; abcd's typical session might be longer (lifeboat sessions especially).
- Should the hook respect a
.abcdignorefor prompts that shouldn't trigger any injection (e.g. paste-of-foreign-text)? - How does this interact with Claude Code Skills? Skills are procedural-workflow shaped; rules are declarative. Boundary needs documenting in the marker block.
- Does the hook need to coexist gracefully with CARL if a developer has both installed? (Probably yes — separate hook scripts, separate JSON files, no conflict expected, but worth verifying in the corpus tests.)
Audit Notes
Manual fidelity audit — the
intent-fidelity-revieweragent is not yet built, so this audit was performed by hand. Judge: Claude Opus 4.8; date 2026-07-11; reviewed against the merged loader (the rules-loader design + implementation). Per-criterion verdicts per the itd-1 discipline, then a three-bucket prose audit.spec_id: spc-1is reserved for itd-3; the future native spec store adopts it rather than re-minting.
Fidelity review — receipt rcp-a2e0ab1dca95 (verifier abcd:intent-auditor claude-opus-5-5).
Provenance: abcd:intent-auditor@claude-opus-5-5 · rubric_hash sha256:effa65b3e9e88ff29433b443ec2be159522a8b0b71cf1434526514aa61edb13e · prompt_hash sha256:cef1b0310d2a086912a28fda819999e073e6060207bda9528324fd28b0f411a4 Input attestations: diff:f4bedb247c56ed08ed6e5712070ee827a43b682a..e5fdf97c2762f570c031c2b5afe7ad5fceb761fe: the loader branch merged as d399cb464d498ca28311205b3781e480fcc4b191 (pull request 18) plus the ship commit; the request named no range, so the auditor derived it from the history@-; repository:origin/main at 2e03c61e454bf09ecda8cee0ae2f377157b522f7, the state every path:line below is read at unless its ref names a commit@-; intent:.abcd/development/intents/shipped/itd-3-modular-rules-loader.md, the working copy carrying this receipt's OWED marker@-;
Acceptance rollup: MET 2 · MET_WITH_CONCERNS 2 · NOT_MET 2 · INCONCLUSIVE 0
Audit owed (receipt rcp-a2e0ab1dca95): ac-5 NOT_MET · ac-6 NOT_MET. Remedy: fix, then re-run the audit. Carried by iss-2610241427397332.
Per-criterion verdicts:
- ac-1 — MET: The bundled COMMITTING domain recalls commit and carries pr and git add as aliases, termHit lowercases and word-bounds each term, and the prompt-router hook test puts the COMMITTING block on the hook's stdout (the context the host adds on that turn) for a commit prompt; a repo rules.json naming COMMITTING active merges per field and keeps it active. No test pins the PR or git add triggers by name; the defaults and the matcher show them. evidence: internal/core/rules/defaults/rules.json:7 — ""recall": ["commit", "push", "branch", "merge", "rebase"]," evidence: internal/core/rules/defaults/rules.json:8 — ""aliases": ["pull request", "pr", "git add", "conventional commit"]," evidence: internal/core/rules/rules.go:751 — "func (idx promptIndex) termHit(term string) bool {" evidence: internal/surface/cli/cli_test.go:132 — "out, errlog := runHook(t, hookInputJSON(t, "s1", cwd, "commit and push"), "hook", "prompt-router")" evidence: hooks/hooks.json:3 — ""UserPromptSubmit": [" evidence: internal/core/rules/rules.go:500 — "func Merge(base, over RuleSet) RuleSet {"
- ac-2 — MET_WITH_CONCERNS: Render returns zero bytes for an empty match and the hook test asserts empty stdout for an unrelated prompt, so no rule and no header reaches the context, inside the 200-token bound. Concerns: the header-only shape the criterion names was replaced by zero bytes under the signed-off delta D3; and at HEAD the same prompt hook appends blocks that are not rules and are not gated by domain recall (the AWAY block, about 410 words, on every prompt while the away record holds, and the STATUS block when the status fetch brought news), so the hook's no-match overhead stays under 200 tokens only outside those states. evidence: internal/core/rules/rules.go:870 — "if len(domains) == 0 {" evidence: internal/surface/cli/cli_test.go:167 — "out, _ := runHook(t, hookInputJSON(t, "s4", cwd, "paint a landscape"), "hook", "prompt-router")" evidence: internal/core/rules/inject_test.go:161 — "res := Inject(rs, "paint a landscape in oils", SessionState{}, 0)" evidence: .abcd/work/decisions/dcn-2607110000000004-itd-3-rules-loader-design-signed-off.md:13 — "zero model-facing tokens on" evidence: internal/surface/cli/cli.go:1876 — "text = joinBlocks(text, away.Block)" evidence: internal/surface/cli/hook_away.go:23 — "outside the rules loader's dedup ledger, add the away block to the agent's" evidence: internal/surface/cli/cli.go:1875 — "text = joinBlocks(text, status.Text)"
- ac-3 — MET_WITH_CONCERNS: Inject skips a matched domain whose rendered-block signature is already in the session ledger, and the hook test shows the second identical prompt producing empty stdout, so prompt 1 injects and prompts 2 and 3 skip. Concern: no forced refresh lands on prompt 5 by default; under the signed-off delta D1 the primary refresh is the ledger reset on SessionStart and PreCompact and the fixed-N backstop defaults to 15, so a refresh on prompt 5 happens only where rules.force_refresh_every_n is set to 5 in .abcd/config.json. evidence: internal/core/rules/inject.go:147 — "if ledger[d.Name] == Signature(d) {" evidence: internal/surface/cli/cli_test.go:146 — "out2, _ := runHook(t, in, "hook", "prompt-router")" evidence: internal/core/rules/inject.go:55 — "const DefaultRefreshBackstop = 15" evidence: internal/core/rules/inject.go:128 — "if count%backstop == 0 {" evidence: internal/core/rules/inject.go:351 — "ForceRefreshEveryN int" evidence: .abcd/work/decisions/dcn-2607110000000004-itd-3-rules-loader-design-signed-off.md:11 — "(fixed-N demoted to a ~15–20 backstop, not primary)"
- ac-4 — MET: Match appends any domain a
*NAMEtoken names before its dormant and recall checks, and the tests show*ZONKactivating a domain whose only recall word never occurs and*ROADMAPactivating ROADMAP made dormant, with the kill switch still winning. The criterion's own example contains milestone, a ROADMAP recall word, so these two tests, not the one that echoes the example, are what isolate the star path. evidence: internal/core/rules/rules.go:634 — "if stars[name] {" evidence: internal/core/rules/rules_test.go:452 — ""ZONK": {State: StateActive, Recall: []string{"zzznomatch"}, Rules: []string{"r"}}," evidence: internal/core/rules/rules_test.go:455 — "if !has(rs.Match("*ZONK do the thing"), "ZONK") {" evidence: internal/core/rules/rules_test.go:477 — "if !has(rs.Match("*ROADMAP go"), "ROADMAP") {" evidence: internal/core/rules/defaults/rules.json:31 — ""recall": ["roadmap", "milestone", "phase"]," - ac-5 — NOT_MET: Promised: in a fresh clone of an abcd repo, a CLAUDE.md under 50 lines carrying the abcd marker block. At HEAD the canonical marker block is 121 lines on its own (124 with its fences, as AGENTS.md lines 6 to 129 show), so no CLAUDE.md that carries it can be under 50 lines; ahoy's default docs target is skip, so a default install writes the block into no conventions file; and this repository commits no CLAUDE.md at all (commit 601680366 removed the link), its 469-line AGENTS.md carrying the block. At the ship commit the block was 42 lines, but this repository's CLAUDE.md was then a link to a 79-line AGENTS.md that carried no block.
evidence: internal/core/ahoy/defaults/claude-md-marker-block.md:121 — "For internals see
.abcd/development/brief/05-internals/03-configuration.md." evidence: AGENTS.md:6 — "BEGIN ABCD" evidence: AGENTS.md:129 — "END ABCD" evidence: internal/core/ahoy/detect.go:44 — "docsTargetDefault = "skip"" evidence: internal/core/ahoy/marker.go:467 — "return markerTargets(docsTargetDefault)" evidence: commit 601680366 — "Remove the committed CLAUDE.md and GEMINI.md links" evidence: e5fdf97c2:CLAUDE.md — "AGENTS.md" - ac-6 — NOT_MET: Promised: every broadly applicable rule of the legacy methodology file preserved in the bundled defaults, by a manual review recorded in research/legacy-harvest.md. The harvest record's own Pass 1 keeps section 9 (singular vs plural naming, as a NAMING domain) and section 14 (documentation footer standards) and establishes NAMING and ARTEFACTS domains, but the bundled defaults carry no NAMING or ARTEFACTS domain, no naming rule and no footer rule, and the record holds no review of the shipped set against the legacy file (the delivered range only repointed its links); the defaults ship as defaults/rules.json rather than defaults.json, and the ship decision itself names this as the one gap.
evidence: .abcd/development/research/legacy-harvest.md:33 — "Singular vs Plural Naming | Keep |
NAMING" evidence: .abcd/development/research/legacy-harvest.md:38 — "Documentation Footer Standards | Keep |DOCUMENTATION" evidence: .abcd/development/research/legacy-harvest.md:42 — "PII (high-priority), COMMITTING, DOCUMENTATION, ARTEFACTS, ISSUES, INTENTS, NAMING, LIFEBOAT" evidence: internal/core/rules/defaults/rules.json:26 — "User-facing prose in British English; code-side identifiers, comments, strings in US English." evidence: internal/core/rules/rules.go:170 — "//go:embed defaults/rules.json" evidence: .abcd/work/decisions/dcn-2607110000000005-itd-3-shipped-manually-ahead-of-the.md:11 — "the one gap is the AC6"
Gap audit:
- honoured:
- A UserPromptSubmit hook recall-matches each prompt and injects only the matching domains' rules, in place of a force-loaded instructions file evidence: hooks/hooks.json:3 — ""UserPromptSubmit": [" evidence: internal/surface/cli/cli.go:1839 — "res := rules.Inject(rs, in.Prompt, rules.LoadState(session), rules.LoadBackstop(root))"
- The default rule set is bundled with the plugin, embedded in the abcd binary evidence: internal/core/rules/rules.go:170 — "//go:embed defaults/rules.json"
- A per-repo .abcd/rules.json overrides the defaults per field and can add custom domains, and ahoy writes a minimal skeleton evidence: internal/core/rules/rules.go:44 — "const RepoRelPath = ".abcd/rules.json"" evidence: internal/core/rules/rules_test.go:500 — ""CUSTOM": {State: StateActive, Recall: []string{"widget"}, Rules: []string{"do the thing"}}," evidence: internal/core/ahoy/apply.go:2094 — "rules := map[string]any{"schema_version": 1, "disabled": false, "domains": map[string]any{}}"
- A prompt about unrelated work, the React-rendering example, pays zero tokens for the rules evidence: internal/core/rules/rules_test.go:335 — "got := rs.Match("render a react component with a gradient background")" evidence: internal/surface/cli/cli_test.go:167 — "out, _ := runHook(t, hookInputJSON(t, "s4", cwd, "paint a landscape"), "hook", "prompt-router")"
- Signature-based dedup within a session, with the ledger cleared on SessionStart and PreCompact so a domain re-injects after compaction evidence: internal/core/rules/inject.go:147 — "if ledger[d.Name] == Signature(d) {" evidence: internal/surface/cli/cli.go:1909 — "if err := rules.ResetState(session); err != nil {" evidence: hooks/pre-compact.sh:4 — "hook prompt-router-reset"
- A
*DOMAINstar-command activates that domain regardless of keyword match, overriding dormant but never the kill switch, and activates domains only evidence: internal/core/rules/rules.go:164 — "var starCommandRe = regexp.MustCompile(" evidence: internal/core/rules/rules.go:634 — "if stars[name] {" evidence: internal/core/rules/rules_test.go:489 — "if got := rs.Match("*ROADMAP go"); len(got) != 0 {" - A per-domain state field, active or dormant, silences a domain without deleting its rules evidence: internal/core/rules/rules.go:64 — "StateDormant = "dormant""
- abcd rules renders the active set and abcd rules DOMAIN scopes to one domain, with no show sub-verb evidence: internal/surface/cli/cli.go:2573 — "Use: "rules [domain]"," evidence: internal/surface/cli/cli_test.go:85 — "out := string(runCLI(t, "rules", "committing"))"
- The domains the press release names ship as bundled defaults (COMMITTING, DOCUMENTATION, ROADMAP, ISSUES, INTENTS and LIFEBOAT), with PII evidence: internal/core/rules/defaults/rules.json:5 — ""COMMITTING": {" evidence: internal/core/rules/defaults/rules.json:86 — ""PII": {"
- diverged:
- The hook is hooks/prompt_router_hook.py: delivered as the Go subcommand abcd hook prompt-router, by a recorded decision
evidence: .abcd/work/decisions/dcn-2607110000000003-itd-3-rules-loader-hook-is-go-not-python.md:8 — "the intent's
hooks/prompt_router_hook.pyis a stale pre-Go-rebuild detail and" evidence: hooks/user-prompt-submit.sh:4 — "hook prompt-router" - A forced full re-inject every N turns with N defaulting to 5: delivered as a ledger reset on SessionStart and PreCompact plus a fixed-N backstop defaulting to 15 (signed-off delta D1) evidence: internal/core/rules/inject.go:55 — "const DefaultRefreshBackstop = 15" evidence: .abcd/work/decisions/dcn-2607110000000004-itd-3-rules-loader-design-signed-off.md:11 — "(fixed-N demoted to a ~15–20 backstop, not primary)"
- No-match overhead under 200 tokens as a header: delivered as zero bytes with an out-of-band stderr line (signed-off delta D3), while at HEAD the same hook adds the AWAY block on every prompt for as long as the away record holds evidence: internal/core/rules/rules.go:870 — "if len(domains) == 0 {" evidence: internal/surface/cli/cli.go:1852 — "abcd rules: turn %d, injected %d domain(s)" evidence: internal/surface/cli/cli.go:1876 — "text = joinBlocks(text, away.Block)"
- The rules.json schema as a JSON Schema embedded in the binary: delivered as hand-rolled Go validation with no schema document evidence: internal/core/rules/rules.go:19 — "Validation is hand-rolled Go (zero new dependencies)"
- rules.json extends the defaults and overrides individual rules: delivered as wholesale per-field replacement of a domain's recall, aliases and rules lists in the schema_version, disabled, domains shape (signed-off delta D2), so changing one rule restates the domain's whole list
evidence: internal/core/rules/rules.go:562 — "r.Rules = append([]string(nil), over.Rules...)"
evidence: .abcd/work/decisions/dcn-2607110000000004-itd-3-rules-loader-design-signed-off.md:12 — "keep the shipped
{schema_version,disabled,domains{}}shape" - Per-repo only, with a global rules.json explicitly out of scope: at HEAD a machine-wide ~/.abcd.noindex/rules.json layers between the bundled defaults and the repo file, added after this intent shipped (itd-117, spc-23) evidence: internal/core/rules/rules.go:11 — "~/.abcd.noindex/rules.json (one per machine, spc-23)" evidence: internal/core/rules/rules.go:49 — "var UserRelPath = abcdhome.Rel("rules.json")"
- CLAUDE.md becomes a small marker block of about 30 lines that ahoy writes: the canonical block was 42 lines at the ship commit and is 121 at HEAD, and a default install now writes it into no file
evidence: internal/core/ahoy/defaults/claude-md-marker-block.md:121 — "For internals see
.abcd/development/brief/05-internals/03-configuration.md." evidence: internal/core/ahoy/detect.go:44 — "docsTargetDefault = "skip"" - A PR-description session loads the commit and PII rules: PII recalls only on its own words, so a PR prompt recalls COMMITTING alone evidence: internal/core/rules/defaults/rules.json:8 — ""aliases": ["pull request", "pr", "git add", "conventional commit"]," evidence: internal/core/rules/defaults/rules.json:88 — ""recall": ["secret", "token", "credential", "pii", "redact", "hostname""
- The hook is hooks/prompt_router_hook.py: delivered as the Go subcommand abcd hook prompt-router, by a recorded decision
evidence: .abcd/work/decisions/dcn-2607110000000003-itd-3-rules-loader-hook-is-go-not-python.md:8 — "the intent's
- missing:
- Every broadly applicable rule of the legacy methodology file preserved in the bundled defaults, by a recorded completeness review (criterion 6) evidence: .abcd/development/research/legacy-harvest.md:33 — "Singular vs Plural Naming | Keep |
NAMING" evidence: .abcd/development/research/legacy-harvest.md:38 — "Documentation Footer Standards | Keep |DOCUMENTATION" evidence: .abcd/work/decisions/dcn-2607110000000005-itd-3-shipped-manually-ahead-of-the.md:11 — "the one gap is the AC6" - A fresh clone whose CLAUDE.md is under 50 lines and carries the marker block (criterion 5) evidence: AGENTS.md:6 — "BEGIN ABCD" evidence: AGENTS.md:129 — "END ABCD" evidence: commit 601680366 — "Remove the committed CLAUDE.md and GEMINI.md links"
Per-criterion verdicts
- MET. COMMITTING's
recall/aliasescover "commit", "PR", and "git add"; the prompt-router hook injects the domain on the turn. - MET (exceeded). A no-match renders zero model-facing tokens — bettering the
< 200threshold. The "header only" mechanism was replaced by out-of-band diagnostics (signed-off delta D3). - MET_WITH_CONCERNS. Dedup is MET (inject on turn 1, skip 2–3). "Forced
refresh on prompt 5" was deliberately replaced by event-driven refresh on
SessionStart(compact)plus a fixed-N backstop (default 15, configurable) — signed-off delta D1. - MET. A
*ROADMAPstar-command injects the domain regardless of keyword match (it overrides a dormant state, not the kill switch). - INCONCLUSIVE. The marker block is delivered and sized (~40 lines) and ahoy installs it, but the live "fresh clone + installed plugin + new session" behaviour cannot be observed in-repo (the hook only fires in an installed plugin).
- NOT_MET. The shipped defaults are a concise curated set (eight domains)
drawn from the working conventions plus the Pass-1 mapping in
research/legacy-harvest.md; a completeness-audited rule-by-rule port of the legacy methodology file was not performed. This is the one genuine gap.
Rollup: 3 MET · 1 MET_WITH_CONCERNS · 1 INCONCLUSIVE · 1 NOT_MET.
Three-bucket audit
- Honoured. Just-in-time recall injection replacing the monolithic
force-load (the core promise); binary-bundled defaults plus a per-repo
override; per-domain dedup; star-command activation; the kill switch and
dormant state; the
abcd rules [domain]diagnostic verb; the OPINIONS domain pointing at the canonical conventions; a fail-closed, non-blocking trust boundary; a marker block under 50 lines. - Diverged (every item a maintainer-signed delta). D3 — zero no-match tokens
plus out-of-band diagnostics instead of a
< 200-token header. D1 — event-driven refresh with a fixed-N backstop instead of forced-every-5. D2 — the shipped{schema_version, disabled, domains{}}per-field-override shape instead of the earlierextends/overridessketch. D4 — no.abcdignore. - Missing / outstanding. A completeness-audited harvest of the legacy methodology file into the defaults (criterion 6). Live installed-plugin session verification (criterion 5). Neither blocks the shipped promise; both are recorded here as follow-ups.
Amendment 2026-10-27 (product thinker)
Criteria ac-5 and ac-6 are amended to what shipped, on the product thinker's ruling of 27 October 2026. ac-5 (a conventions file under 50 lines carrying the marker block) is obsolete: the marker block alone is longer, and this repository keeps its conventions in AGENTS.md with no CLAUDE.md. ac-6 (a completeness-audited harvest of the legacy methodology file, with NAMING and ARTEFACTS domains) was dropped when the bundled defaults shipped, as the ship decision names. Neither is owed; a later intent may restore either target.
References
Frontmatter
| id | itd-3 |
|---|---|
| type | intent |
| lifecycle | shipped |
| kind | standalone |
| severity | major |
.abcd/development/intents/shipped/itd-3-modular-rules-loader.md
open on GitHub ↗ · commit history ↗
References1
- spc-1not in the treeimplements
Referenced by25
- adr-2610030814023326AGENTS.md is the one conventions file abcd writes; it never writes a host-specific copyrelated
- adr-2610091918443997What may enter abcd's memory notes without the person's yes, and how a note reaches the recordrelated
- iss-2610241427397332The after-merge fidelity audit of itd-3 judged a criterion unmet, so its check is still owed: ac-5 NOT_MET, ac-6 NOT_METrelated
- iss-2610262032050596The dependencies page says abcd needs no external tool and ships as one Go binary; itd-3 makes the prompt router a Python hook.related
- iss-2610262032060007The register gives the forced rules refresh a default of 15 prompts; itd-3 says it defaults to 5.related
- iss-2610262033133842itd-3 rejects a machine-wide rules.json outright, per-repo only; the ahoy chapter's home inventory has one as the user layer of the rulesrelated
- iss-2610262033143329itd-3 puts the loader's marker block in CLAUDE.md; embark re-injects that same itd-3 block into AGENTS.md and treats claude_md as a retired targetrelated
- iss-2610262036437864The rules-loader intent rejects a user-level rules.json and keeps rules per repository; the guard chapter counts a rules.json in abcd's home folder as part of the user layer.related
- iss-2610262038011053The rules-loader intent rejects a machine-wide rules.json; the memory chapter reads a machine memory.json beside a machine rules.json layer.related
- iss-2610262039423717The README's `abcd rules` loads a machine-wide rules.json layer, which itd-3 explicitly rejects.related
- iss-261026203943968103-configuration makes the rules refresh event-driven with a 15-turn backstop; itd-3 forces a full re-inject every N turns, N defaulting to 5.related
- iss-2610262041454448The memory chapter says the prompt router is a binary sub-command, not a script in hooks/; the shipped rules-loader intent scopes it as hooks/prompt_router_hook.py.related
- iss-2610262041455139The verification matrix says the rules-loader refresh is event-driven and nothing counts prompts; itd-3 refreshes in full every N turns.related
- iss-2610262043334834itd-2609292109214516 puts its setting in the rules configuration's layered files; itd-3 rejects any personal cross-repo rules filerelated
- iss-2610262044344649Memory notes sit beside each rules.json layer, including a machine layer, but the shipped rules loader rejected any machine-wide rules.jsonrelated
- iss-2610262044555198itd-3 explicitly rejects a personal cross-repo ~/.abcd/rules.json; itd-117 ships exactly that user-scope rules filerelated
- iss-2610262044561718itd-39 reuses the rules loader's context brackets from itd-3, but itd-3's loader defines no bracketsrelated
- iss-2610262045254107itd-3 has ahoy own a CLAUDE.md marker block; itd-2610030814013772 rules that projects carry no CLAUDE.md at allrelated
- iss-2610262045266820itd-3 rejects a user-level rules.json outright; itd-2610030720038073 records that every managed project's block names the home's top-level rules.jsonrelated
- iss-2610262045278708itd-3 ships the prompt hook as a Python file in hooks/; itd-66 says hooks/ ships shell and the payload carries no Pythonrelated
- iss-2610262045286894itd-3 names a ROADMAP rule domain and teaches 'draft a milestone'; itd-2609211913453478 retires roadmap and milestone as superseded termsrelated
- itd-83The Review Bar Fires By Itself, In Every Repo abcd Managesbuilt on by
- itd-89Capture the Session You Just Had, Because You Cannot Capture It Laterbuilt on by
- itd-2609292109214516Rule injection is a seam: the native loader stays the default, and an opt-in CARL back end can take it overbuilt on by
- itd-2610091918433290Agents keep memory notes with /abcd:memory, recalled when they matterbuilt on by
mentions50
- adr-10The phase negotiator — a Socratic agent that proposes phases and grounds every trade-off
- dcn-26071100000000032026-07-11 — itd-3 rules-loader hook is **Go**, not Python. abcd is Go-only, so
- dcn-26071100000000042026-07-11 — itd-3 rules-loader **design signed off** (plan
- dcn-26071100000000052026-07-11 — itd-3 **shipped manually** ahead of the intent-lifecycle pipeline.
- dcn-26071100000000062026-07-11 — Intent-lifecycle slice 1 (build sign-off given): the pipeline is
- dcn-26071100000000072026-07-11 — `spc-N` minting rule for slice 1: `max(N over spec-store files ∪
- dcn-26082100000000032026-08-21 — Bug-hunt round 5, branch bughunt-b/round-5. Baseline (make preflight
- dcn-26102719225024802026-10-27 — Majors ruling for the next cut, product thinker, 27 October 2026, on abcd-78's recommendations for the 17 hand-captured majors since v0.15.1. Fix before the cut: the eight recommended fixes (iss-2610251555094324, iss-2610241537197997, iss-2610241414124940, iss-2610241911052191, iss-2610241240133009, iss-2610241133284600, iss-2610241239492159, iss-2610241519088123) and five recommended for deferral that the product thinker chose to fix instead: the short install command in the docs (iss-2610240827243654), a worktree per session (iss-2610240859590333), clearing lapsed holds everywhere (iss-2610241107191205), the reflect criteria (iss-2610241431221889) and the measured home-folder receipt (iss-2610241551259104). Deferred one cycle: iss-2610241228359748, iss-2610240932215918, iss-2610241613191748. Amended and closed: iss-2610241427397332 (itd-3's obsolete criteria). The 69 consistency-pass majors are ruled on branch decisions/interview-2026-10-27.
- iss-64Make rule-injection a seam: keep the native Go loader (itd-3) as the floor, add an opt-in 'carl' backend so a user with CARL installed can route to it. Per adr-22 native-default + easy-onboard-a-better-external. Config rules.backend: native | carl.
- iss-66rules loader trust-boundary (itd-3): an untrusted per-repo .abcd/rules.json can silence default guardrail domains — Merge() ORs the global kill switch (rules.go:156) and mergeDomain lets an override set any default domain dormant, so a cloned repo suppresses all rule injection (P15); session-state lives in a predictable shared /tmp path (os.TempDir/abcd-rules-state, sha256(session).json, session defaults to constant default) letting a local co-tenant suppress injection fail-open (inject.go:99, P14); Load() Lstats rules.json then os.ReadFile re-resolves by name, a symlink-swap TOCTOU (rules.go:132, C19). Detector: override cannot disable a default guardrail/kill-switch; state under UserCacheDir+ownership check; open-once O_NOFOLLOW+fstat. Corpus: P15, P14, C19.
- iss-2608210934566220The six bundled OPINIONS rules each end with a pointer to a file under .abcd/development/principles/ that exists only in the abcd repo itself — a managed repo inherits the injected lines verbatim, so every prompt whose recall matches the domain hands its agent six dangling references (the principles corpus is not part of adoption). Either the bundled lines need self-contained phrasings with the pointer marked as abcd-repo-only, or adoption (prepare-this-repo / ahoy) should ship a distilled principles set the pointers can resolve against. Found while adding the memory-graduation rule, whose line was written self-contained for exactly this reason
- iss-2608220150157498Eight typed cross-references point at targets absent from the tree — adr-22 supersedes adr-14, adr-15 and adr-17; adr-25 supersedes adr-8; adr-27 supersedes adr-16; adr-28 supersedes adr-18; adr-35 supersedes adr-4 (all retired under retire-the-name); itd-3 names spec_id spc-1 which has no file — and nothing checks supersedes targets today. The 2026-08-21 site investigation counted six; the in-session grep found eight. The planned site build arms the detector via the .abcd/site-baseline.json ratchet, seeded with what the build finds, and the tombstones-or-stubs question (itd-136/itd-137) decides how they render
- iss-2608280824478819The canonical credit surface is incomplete: design-shaping inspirations fully credited in the committed record never propagated to ACKNOWLEDGEMENTS.md. Backfill set from the 2026-08-28 attribution review: add Inspirations entries for CARL and PAUL (both Kahler, MIT; mechanisms adopted per itd-3 and itd-1), SpecStory (adr-29 seam, specstory-import ships in CLI help), the Karpathy LLM-wiki gist (memory-substrate pattern), gitleaks and TruffleHog (integrated scanners, one self-declared adapted regex), and Dieter Rams; complete the Diataxis entry with the author and CC-BY-SA 4.0; widen the mattpocock entry (ADR three-clause test, PRD shape); add Dell'Acqua et al. 2023 to the CSL references; add source URLs to the Horn and Weng credits and the arXiv id to the Iacob citation; record the skills' authorship in the socratic/first-principles research note; fix the dead to-prd deep link in itd-27.
- iss-2608301803423101an unclosed comment or fence anywhere in an issue record blinds every grounds triage route permanently and promote orphans a draft per attempt
- iss-2609012039211929GHSA-vvqc-3mv2-5p49 (CWE-15, CWE-426): the rules-loader root resolver `rulesRoot` (internal/surface/cli/cli.go) walks every ancestor of cwd and takes the first one holding a `.abcd` directory, consulting the git toplevel only as a fallback, so a `.abcd/rules.json` or `.abcd/guard.json` planted above the working tree — a world-readable `/tmp/.abcd`, or the user-scope `~/.abcd` for any session under the home directory — governs the injected rules, the refresh backstop and the shell guard of every session in a repository that has no `.abcd` of its own; a planted `{"disabled":true}` guard.json is a successful load that disarms the guard. Reproduced at v0.7.0 (8f68ffb3): `hook prompt-router` run from a fresh inner git repository injected the ancestor plant, and `guard hook` reported the registry switched off. The fix must bound the resolution at the git working tree — toplevel first, the nearest `.abcd` inside the tree wins, a tree with none resolves to its toplevel, and a non-git cwd resolves to itself with no walk — so an ancestor plant is unreachable from any repository; `banlistRoot`, `LoadBackstop` and both guard loaders share the resolver and are fixed by the same change. Behaviour changes to state out loud: a non-git directory no longer walks to the home directory; a nested repository or submodule resolves its own toplevel. Advisory severity medium.
- iss-2609290448510918The predecessor-store qualifier sweep reaches past the six intents iss-2609261536147903 named: outside drafts/ and those six, 192 citation sites across about forty intents name a spc-N at or below spc-70 that is not the citing intent's own spec, with no '(predecessor store)' on the line. Some are live cross-references (the cold-reading family's intents cite each other's live specs), and some are the predecessor store's (itd-4, itd-6, itd-29, itd-47 and itd-49 describe pre-rebuild work in its terms), so each site needs the same reading against the live spec it collides with; the specs charter's Two spc-N Namespaces section is the rule. Found while sweeping the pattern of iss-2609261536147903 in lane drainDrift3; a census script over intents/{planned,shipped,disciplines} reproduces the count.
- iss-2610020700529281The managed block ahoy install plants into an adopted repository's conventions file when a docs target is chosen (internal/core/ahoy/defaults/claude-md-marker-block.md) is abcd's own rule-loader section copied verbatim, and it breaks the repositories that receive it. (a) It ends 'For internals see .abcd/development/brief/05-internals/03-configuration.md' and says OPINIONS points at .abcd/development/principles/; both exist only in abcd's own repository, so they dangle in every adopted one. (b) At tip it is 98 lines with em dashes on 10 of them, so a repository whose own AGENTS.md rules bound file length, ban em dashes or require every link to resolve fails its own gate on the block. (c) Its header says /abcd:ahoy 'silently overwrites this block on drift (per itd-3)', citing an abcd-internal record id, so the adopter cannot trim it to fit. (d) It is inserted directly under the H1, ahead of the repository's own opening text. This record is intent-shaped: the ask is a capability, a short pointer variant of the block for adopted repositories (a few ASCII lines, no repository-internal links or record ids) or keeping the long text where consumer repositories never commit it; routing it to an intent is the product thinker's call. Related: iss-2609110944498549 (resolved, the default docs target is skip) and iss-2608210934566220 (the injected OPINIONS pointers dangle in managed repositories).
- iss-2610241430042742abcd reserve refuses every shipped intent as finished on origin's default branch (exit 3, nothing pushed), yet an owed fidelity review writes into the shipped intent's file (its Audit Notes block) and can capture an issue, so the work an intent audit drain does can never be reserved, despite the AGENTS.md rule to reserve an intent before working on it. Two sessions paying owed reviews, or running intent condition, can pick the same shipped intent and each spend a whole review on it, with only the ingest's replace-or-noop keeping the record whole. Seen on 2026-10-24: reserve itd-3 before its owed audit exited 3 with 'itd-3 is finished on origin's default branch'.
- iss-2610252224345288itd-3's criterion 6 asks for every broadly applicable rule of the legacy methodology file to be preserved in the bundled defaults, by a recorded completeness review. No such review is recorded: legacy-harvest.md marks rows Keep (Singular vs Plural Naming, Documentation Footer Standards and others) without saying which bundled rule carries each, and dcn-2607110000000005 records only that itd-3 shipped ahead of it.
- iss-2610262030410509Invariant 17 makes the intent-to-spec relation 1:n; the intent chapter's kinds table maps a standalone intent to one spec (1:1)
- iss-2610262031560983itd-39 extends a Python hook, hooks/prompt_router_hook.py; 02-dependencies says abcd ships as a single Go binary with no required external tools.
- iss-2610262033124129itd-39 extends a Python prompt-router hook; the ahoy chapter says every hook event command is a shim that finds and runs the abcd binary
- iss-2610262033557020The intent chapter's kinds table maps a standalone intent to one spec (1:1); the record-families map says an intent has one or more specs.
- iss-2610262038154778The configuration chapter has the binary read a prompt-counting refresh backstop; the verification matrix says nothing counts prompts.
- iss-2610262039325628itd-39 extends a Python hook file `hooks/prompt_router_hook.py`; the configuration chapter says the prompt router is the binary's `abcd hook prompt-router` entrypoint
- iss-2610262039330569itd-39 reuses the rules loader's context brackets (itd-3); the configuration chapter's loader has no context brackets and attributes the budget bracket to itd-39 itself
- iss-2610262040213900The memory chapter gives the prompt router a refresh every N prompts; the verification matrix says the refresh is event-driven and nothing counts prompts.
- iss-2610262041234230itd-39 extends a Python script `hooks/prompt_router_hook.py`; the memory chapter says the prompt router is a binary sub-command, not a script file in `hooks/`
- iss-2610262044572933itd-39 plans to extend a Python prompt-router hook; itd-105 says the plugin's hooks call the abcd binary
- iss-2610262045091104itd-39 extends a Python hook, hooks/prompt_router_hook.py; itd-66 says the payload has no Python and hooks/ ships shell
- iss-2610262045100283The prompt router is the Python file hooks/prompt_router_hook.py in itd-39 and the shipped hidden verb `hook prompt-router` in itd-89
- itd-1Every Intent Has Acceptance Criteria; Every Audit Verifies Them
- itd-29Domain Experts Run A Spec Autonomously And Recover From Anything Without Touching Git
- itd-39abcd Surfaces the Right Memory at the Right Moment
- itd-41abcd Tells You, Honestly, What a Phasing Choice Costs
- itd-80An Intent Ships Itself: `planned → shipped` Follows Its Spec Closing, With A Fidelity Audit Attached
- itd-100Alice Reads One Page and Knows Exactly Where abcd Stands in the Agentic-AI Landscape
- itd-117A Machine's Shared Conventions Are Declared Once In The User Scope — And Every Managed Repo Inherits Them
- itd-160Eight typed cross-references point at targets absent from the tree — adr-22 supersedes adr-14, adr-15 and adr-17; adr-25 supersedes adr-8; adr-27 supersedes adr-16; adr-28 supersedes adr-18; adr-35 supersedes adr-4 (all retired under retire-the-name); itd-3 names spec_id spc-1 which has no file — and nothing checks supersedes targets today. The 2026-08-21 site investigation counted six; the in-session grep found eight. The planned site build arms the detector via the .abcd/site-baseline.json ratchet, seeded with what the build finds, and the tombstones-or-stubs question (itd-136/itd-137) decides how they render
- itd-2609150819445595A shipped intent's owed fidelity review is listed, so the debt is paid
- itd-2609212103572513An intent names the release it must land by, and the cut says whether it did
- itd-2610030814013772abcd's projects keep one conventions file, AGENTS.md, and Claude Code reads it directly
- itd-2610031348087517Setup asks whether abcd keeps parts of the README current, and which
- spc-4start-the-transcript-clock
- spc-23development-instructions-live-in-the-right-tier-abcd-manages
- spc-38alice-opens-the-record-explorer-and-reads-the-whole-developm
- spc-52dangling-supersedes-and-spec-targets-nothing-checks-them
- spc-2610031156364295evaluate-whether-claude-md-can-be-removed-safely-now-that
- spc-2610031704338417when-abcd-sets-up-a-project-it-asks-the-owner-whether-abcd
- spc-2610111627213138memory-recall