MusePi

Rulebook Matching Pipeline

This document describes how coding-agent discovers rules from supported config formats, normalizes them into a single Rule shape, resolves precedence conflicts, and splits the result into:

It reflects the current implementation, including partial semantics and metadata that is parsed but not enforced.

Implementation files

1. Canonical rule shape

All providers normalize source files into Rule:

interface Rule {
  name: string;
  path: string;
  content: string;
  globs?: string[];
  alwaysApply?: boolean;
  description?: string;
  condition?: string[];
  astCondition?: string[];
  scope?: string[];
  interruptMode?: "never" | "prose-only" | "tool-only" | "always";
  _source: SourceMeta;
}

Capability identity is rule.name (ruleCapability.key = rule => rule.name).

Consequence: precedence and deduplication are name-based only. Two different files with the same name are considered the same logical rule.

2. Discovery sources and normalization

src/discovery/index.ts auto-registers providers. For rules, current providers are:

Native provider (builtin.ts)

Loads .musepi rules from:

Normalization:

Important caveat: condition values that look like file globs are converted into tool:edit(...) / tool:write(...) scope shorthands with catch-all condition .*.

Agents provider (agents.ts)

Loads from both .agent and .agents directories:

Normalization uses the shared buildRuleFromMarkdown path: filename-derived name, stripped frontmatter body, and parsed globs, alwaysApply, description, condition/legacy ttsr_trigger, astCondition, scope, and interruptMode.

Cursor provider (cursor.ts)

Loads from:

Normalization (transformMDCRule):

Windsurf provider (windsurf.ts)

Loads from:

Normalization:

Cline provider (cline.ts)

Searches upward from cwd for nearest .clinerules:

Normalization:

3. Frontmatter parsing behavior and ambiguity

All providers use parseFrontmatter (utils/frontmatter.ts) with these semantics:

  1. Frontmatter is parsed only when content starts with --- and has a closing \n---.
  2. Body is trimmed after frontmatter extraction.
  3. If whole-document YAML parsing fails:
    • a warning is logged,
    • the parser falls back to simple key: value line parsing (^([\w-]+):\s*(.*)$),
    • each captured value is reparsed independently as YAML, and only values that still fail parsing remain raw trimmed strings.

Fallback limitations:

4. Provider precedence and deduplication

loadCapability("rules") (capability/index.ts) merges provider outputs and then deduplicates by rule.name.

Precedence model

Effective rule provider order is currently:

  1. native (100)
  2. omp-plugins (90)
  3. agents (70)
  4. cursor (50)
  5. windsurf (50)
  6. cline (40)
  7. builtin-defaults (1)

Intra-provider ordering caveat

Within a provider, item order comes from loadFilesFromDir glob result ordering plus explicit push order. This is deterministic enough for normal use but not explicitly sorted in code.

Notable source-order differences:

5. Split into Rulebook, Always-Apply, and TTSR buckets

After rule discovery in createAgentSession (sdk.ts), bucketRules(...) applies session-level filtering and bucket assignment:

  1. Drop rules listed in ttsr.disabledRules.
  2. Drop rules from the builtin-defaults provider when ttsr.builtinRules === false.
  3. Register rules with a non-empty condition or astCondition into TtsrManager; if registration succeeds, the rule is TTSR-only.
  4. Put remaining alwaysApply === true rules into alwaysApplyRules.
  5. Put remaining rules with description into rulebookRules.

Bucket behavior

6. How metadata affects runtime surfaces

description

globs

alwaysApply

condition, astCondition, scope, and interruptMode

7. System prompt inclusion path

buildSystemPromptInternal receives both rules (rulebook) and alwaysApplyRules.

Always-apply rules are deduped against custom prompt sources (dedupeAlwaysApplyRules drops a rule whose content already appears in the SYSTEM/APPEND_SYSTEM customization) and rendered first, injecting their raw content directly into the prompt (inside a <generic-rules> block in the default template).

Rulebook rules are rendered in a <domain-rules> block as - <name> (<globs>): <description> lines; the URL list in the prompt documents rule://<name> and the workflow section tells the model to read relevant rules first. The custom-prompt template (custom-system-prompt.md) instead renders <rule name="..."> entries with <glob> children under an explicit “You MUST read rule://<name>” instruction.

This is advisory/contextual: prompt text asks the model to read applicable rules, but code does not enforce glob applicability.

8. rule:// internal URL behavior

RuleProtocolHandler resolves against the process-global active-rule snapshot installed once per top-level session in sdk.ts:

setActiveRules([...rulebookRules, ...alwaysApplyRules, ...ttsrManager.getRules()]);

Implications:

9. Known partial / non-enforced semantics

  1. The rule providers currently loaded for rules are native, omp-plugins, agents, cursor, windsurf, cline, and embedded builtin-defaults; provider files for other tools may parse other config formats but do not register rule loaders.
  2. globs metadata is surfaced to prompt/UI and is used as a global path gate for TTSR matching, but it is not used to automatically select rulebook rules for rule://.
  3. Rule selection for rule:// includes rulebook, always-apply, and registered TTSR rules (so a triggered TTSR rule can be re-read), but not rules that registered no condition and carry neither a description nor alwaysApply.
  4. Discovery warnings (loadCapability("rules").warnings) are produced but createAgentSession does not currently surface/log them in this path.