feat: itd-3 modular rules loader (loader core, hook, verb, OPINIONS) - #18
Merged
Conversation
…sign-off) Records the design of record for the itd-3 modular rules loader, reached via the prefer-sota process: an adversary challenged generic just-in-time rule-injection SOTA (CARL, AGENTS.md, Cursor rules, Skills) for repo fit, then a sota-researcher verdict was taken on the fit-surviving hypothesis. Surviving shape: a transport-agnostic core capability (internal/core/rules) with two front doors — the vendor-neutral `abcd rules [domain]` verb and a Claude Code hook (`abcd hook prompt-router`) — not an adapter seam. The verdict confirms this is current SOTA (JIT progressive disclosure) and refines CARL in four ways: event-driven refresh on SessionStart(compact) over fixed-N; per- domain rendered-content dedup with a ledger cleared on reset; keyword+alias recall (zero-dep, no ML); zero model-facing tokens on no-match with out-of-band observability. No build begins until the maintainer signs off the four intent deltas (D1 N- refresh, D2 schema shape, D3 no-match budget, D4 .abcdignore) enumerated in the STOP section. Assisted-by: Claude:claude-opus-4-8
The transport-agnostic core of the modular rules loader: binary-embedded default domains (COMMITTING, DOCUMENTATION, ROADMAP, ISSUES, INTENTS, LIFEBOAT, PII), per-repo .abcd/rules.json merge (per-field domain override, sticky kill switch), hand-rolled zero-dep validation, word-bounded recall matching with multi-word aliases, RE2-safe *<DOMAIN> star-command parsing, one shared renderer, and per-domain FNV-1a dedup signatures. Fail-closed: a malformed or invalid rules.json is an error the caller surfaces, never a silent fall-back. No model-facing tokens on a no-match (D3): Render(nil) is zero bytes. Not yet wired to a front door — the `abcd rules` verb (phase 3) and the prompt-router hook (phase 2) land next; this core does nothing on the live path until they do. Tests watched fail then pass; make preflight green (build/vet/test/race). Assisted-by: Claude:claude-opus-4-8
Wires the rules loader core to its vendor-neutral front door. Bare `abcd rules` renders the active rule set; a positional DOMAIN (case-insensitive) scopes to one domain; an unknown domain exits 2; --json emits the structured envelope. A malformed .abcd/rules.json fails closed. Adds core Active()/Lookup() helpers. The capability is now reachable and demonstrable from the production CLI entry point without any harness. The prompt-router hook (phase 2) injects the same core just-in-time next. Tests watched fail then pass; make preflight + record-lint green. Assisted-by: Claude:claude-opus-4-8
The Claude Code injection front door onto internal/core/rules. Two operator- internal entrypoints — `abcd hook prompt-router` (UserPromptSubmit) and `abcd hook prompt-router-reset` (SessionStart/PreCompact) — read the hook stdin payload, recall-match, dedup per session, and inject only fresh matched rules. Core additions (pure, transport-free): Inject() with per-domain signature dedup and a fixed-N refresh backstop (default 15; event-driven reset is primary per D1), and session-state persistence (LoadState/SaveState/ResetState) keyed on a sha256 of the session id so a hostile id cannot traverse the state dir. Trust boundary hardening (security-reviewer: PASS-WITH-NOTES, no blocker): Load now refuses a symlinked/non-regular/oversized rules.json; stdin (1 MiB), prompt (64 KiB), and state file (256 KiB) are capped; the injected text is abcd's own rule content only and never reflects prompt bytes; the reset diagnostic quotes the untrusted event name. Every path is fail-closed and non-blocking (exit 0 + out-of-band stderr diagnostic); a no-match renders zero model-facing tokens (D3). Tests watched fail then pass; make preflight + record-lint green. Assisted-by: Claude:claude-opus-4-8
…cile) Makes the loader a live install, not built scaffolding. - Add hooks/hooks.json: UserPromptSubmit -> `abcd hook prompt-router`, SessionStart/PreCompact -> `abcd hook prompt-router-reset`, invoking the plugin's own binary ($CLAUDE_PLUGIN_ROOT/abcd) — Go-only, no shell shim. - Reconcile store.go requiredHookCommand to the Go-subcommand substrings (`hook prompt-router` / `hook prompt-router-reset`), superseding the stale prompt_router_hook/_reset script names; update the two manifest test fixtures. - Add TestShippedHookManifestVerifies so the shipped manifest and the verifier cannot drift. - Rewrite the CLAUDE.md marker block present-tense-accurate: real domains (not placeholders), the override-skeleton shape, star-command + kill-switch + dormant semantics, event-driven reset; drop the retired scripts/abcd schema path and the fn- reference. - Clarify stepRules writes the empty-domains override skeleton (defaults live once in the binary; per-repo overrides per-field — one-canonical-primitive), not a copy of the bundled defaults. Behaviour-preserving manifest reconciliation kept in this wiring commit; make preflight + record-lint green. Assisted-by: Claude:claude-opus-4-8
Adds the OPINIONS/CONVENTIONS default domain to the binary-bundled defaults, so every abcd-managed repo inherits it via ahoy. Its rules are short directives that POINT AT the canonical conventions under .abcd/development/principles/ (prefer-sota, one-canonical-primitive, evaluator-outside-the-loop, verifier-selects-gates-decide, loud-staging, script-first-mvp) plus an index rule naming the directory — never a copy of the principle text (one-canonical-primitive). Recall triggers on opinion/convention/principle/ approach/SOTA prompts. No path-resolution surface: the rule text names the file for the agent to read on demand; the hook never resolves or reads it, so there is no traversal vector. Marker block domain list + CHANGELOG updated. Tests watched fail then pass; make preflight + record-lint green. Assisted-by: Claude:claude-opus-4-8
Ruthless-reviewer verdict was SHIP (no FIX-FIRST); this closes the two integrity findings and the cheap nits. - Wire the fixed-N backstop to config: rules.LoadBackstop reads rules.force_refresh_every_n from .abcd/config.json (default 15 when unset), and the hook passes it instead of a hardcoded 0 — the documented config field now does something. Corrects the comment that claimed it was already wired, and updates the config brief to the D1 backstop semantics (default 15, event-driven reset is primary). - Rewrite the vacuous reset test into TestInjectReinjectsOnClearedLedger, which threads session state and asserts dedup-then-reinject; add TestLoadBackstop. - SaveState uses os.CreateTemp for a unique temp name (no orphaned fixed .tmp, no same-session writer race; cleans up on rename failure). - LoadState hoists the double session-id hash; comments clarify the id-less session collapse and that scoped `abcd rules DOMAIN` inspects content independent of the kill switch. make preflight + record-lint green. Assisted-by: Claude:claude-opus-4-8
End-of-run record of the design forks, non-obvious decisions, and process learnings from the itd-3 build, so a future session need not re-derive them. Assisted-by: Claude:claude-opus-4-8
Implements the three follow-ups rather than deferring them, per maintainer direction. - Recall stemming: inflected forms recall their keyword (commits->commit, pushes->push, issues->issue) via a conservative suffix stemmer. Short tokens are left unstemmed and `-es` only strips after a sibilant root, so the test->attestation over-match the SOTA verdict warned about is closed by a regression test rather than accepted. - PruneState sweeps session-state files older than StateTTL (one week) on SessionStart, bounding temp-dir growth. - Load refuses a symlinked .abcd directory component, not just the rules.json leaf (the residual the security review flagged as out-of-remit). Learnings note updated to present-tense; tests watched fail then pass; make preflight + record-lint green. Assisted-by: Claude:claude-opus-4-8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements itd-3, the modular rules loader, per the SOTA-verified design of record (
.abcd/development/plans/2026-07-11-itd-3-rules-loader.md). Maintainer signed off the four intent deltas (D1 event-driven refresh, D2 shipped schema shape, D3 zero no-match tokens, D4 no.abcdignore).Shape
Rule-loading is a transport-agnostic core (
internal/core/rules) with two front doors — the vendor-neutralabcd rules [domain]verb and a Claude Code hook (abcd hook prompt-router/-reset) — not an adapter seam. Default domains are bundled in the binary; a per-repo.abcd/rules.jsonoverrides them per-field.What's here
*<DOMAIN>star-commands, per-domain FNV dedup, fail-closedLoad.UserPromptSubmitinjection with per-session signature dedup; event-driven refresh (ledger cleared onSessionStart/PreCompact), fixed-N backstop fromrules.force_refresh_every_n(default 15). Fail-closed and non-blocking; zero model-facing tokens on a no-match, diagnostics out-of-band.DOMAINscopes;--jsonparity.hooks/hooks.json(invokes the plugin's own binary — Go-only), verifier reconcile, present-tense marker block..abcd/development/principles/, never copies.Reviews
security-reviewer: PASS-WITH-NOTES, no blocker (prompt-injection, path traversal, resource caps, fail-closed all handled).ruthless-reviewer: SHIP, no FIX-FIRST.Verification
make preflight(build + vet + test + race) andmake record-lintgreen. New behaviour is TDD (test watched fail then pass).The hook only fires in an installed plugin where
$CLAUDE_PLUGIN_ROOT/abcdexists (release packaging); in-repo it is exercised via the CLI and tests.Assisted-by: Claude:claude-opus-4-8