compound · git:20260817.c0bbbb7 · 2026-08-17 · sha256 1a072251bbab39e5

compound git:20260817.c0bbbb7A

Immutable. This exact content is served forever at /api/v1/blob/1a072251bbab39e5.

---
name: compound
description: "This skill should be used when documenting a recently solved problem to compound your team's knowledge."
---

<!-- lifecycle-handoff-protocol:start -->
**Lifecycle handoff (standalone `/compound` before ship):** When compound runs as the pre-ship step in the implementation tail, invoke `/ship` next — artifacts archived here are a checkpoint, not completion. Parent orchestrators (`work`, `one-shot`) own progression when active.
<!-- lifecycle-handoff-protocol:end -->

# /compound

Coordinate multiple subagents working in parallel to document a recently solved problem.

## Purpose

Captures problem solutions while context is fresh, creating structured documentation in `knowledge-base/project/learnings/` with YAML frontmatter for searchability and future reference. Uses parallel subagents for maximum efficiency.

**Why "compound"?** Each documented solution compounds your team's knowledge. The first time you solve a problem takes research. Document it, and the next occurrence takes minutes. Knowledge compounds.

## Usage

```bash
skill: soleur:compound               # Document the most recent fix
skill: soleur:compound [brief context]  # Provide additional context hint
skill: soleur:compound --headless    # Headless mode: auto-approve all prompts
```

## Headless Mode Detection

If `$ARGUMENTS` contains `--headless`, set `HEADLESS_MODE=true`. Strip `--headless` from `$ARGUMENTS` before processing remaining args.

**Branch safety check (defense-in-depth):** Run `git branch --show-current`. If the result is `main` or `master`, abort immediately with: "Error: compound cannot run on main/master. Checkout a feature branch first." This check fires in all modes (headless and interactive) as defense-in-depth alongside PreToolUse hooks -- it fires even if hooks are unavailable (e.g., in CI).

When `HEADLESS_MODE=true`, forward `--headless` to the `compound-capture` invocation (e.g., `skill: soleur:compound-capture --headless`).

## Phase 0: Setup

**Load project conventions:**

```bash
# Load project conventions
if [[ -f "CLAUDE.md" ]]; then
  cat CLAUDE.md
fi
```

Read `CLAUDE.md` if it exists - apply project conventions during documentation.

## Phase 0.5: Session Error Inventory (MANDATORY)

HARD RULE: Before writing any learning, enumerate ALL errors encountered in this session. Output a numbered list to the user. This step cannot be skipped even if the session felt clean.

**Check for session-state.md:** Run `git branch --show-current`. If on a `feat-*` branch, check if `knowledge-base/project/specs/feat-<name>/session-state.md` exists. If it does, read it and include any forwarded errors from `### Errors` in the inventory. These errors occurred in preceding pipeline phases (e.g., plan+deepen subagent) whose context was compacted.

Include:

- Errors forwarded from session-state.md (if present)
- Skill or command not found errors (e.g., wrong plugin namespace)
- Wrong file paths, directories, or branch confusion
- Failed bash commands or unexpected exit codes
- API errors or unexpected responses
- Wrong assumptions that required backtracking
- Tools or agents that returned errors
- Permission denials or hook rejections
- **A framing INHERITED from a sibling artifact, pasted into a context whose premise you never re-checked.** We already re-derive inherited *numbers*; inherited *sentences* get no such treatment, and they are the ones that read as established — because they were established, somewhere else. The shape recurs when a new entry joins an existing set (a fourth scanned directory, a third vendor row, a second host) and the justification is copied from a sibling entry: the words survive the move, the evidence does not. Gate: for every causal or universal claim the diff's prose ADDS, name the command that would falsify it and run it. **Why:** #7310 — the PR extending the ADR-166 lint (which forbids naming a cause the job did not measure) wrote "operator-facing shell gates that no lint read" into that lint's own header, reusing ADR-166's true-of-`.github/actions/` framing for a directory `lint-trap-tempfile-ownership.py` walks in full; and asserted a message "spent a production-recovery window" when the issue predated it by six hours. Both refuted in ~15s by `gh pr view --json mergedAt` and one `grep`. See `knowledge-base/project/learnings/2026-08-06-i-shipped-two-unmeasured-causal-claims-inside-the-lint-that-forbids-them.md`.
- Blanket search-replace sweeps (ADR renumbers, identifier renames) that rewrote files outside your own diff — scope every sweep to `git diff --name-only origin/main...HEAD` and assert the SENTENCE, because a residual-zero count (`grep -c '<old>' == 0`) is structurally blind to a new string written where it does not belong, including inside the very note explaining the rename. **Why:** #7162 — a `sed` guarded by a negative lookahead on the sibling ADR's *filename* rewrote 10 files of other work's *bare* `ADR-159` citations, and an earlier sweep turned "ADR-155 was claimed by a sibling plan" into a false sentence that the count read as green. Ordinals collided three times on one branch, each surfaced by a fetch or rebase and never by a gate — treat a branch-picked ordinal as provisional and re-check against freshly-fetched `origin/main` immediately before merge. See `knowledge-base/project/learnings/workflow-issues/2026-08-03-blanket-renumber-rewrote-other-work-and-a-count-certified-it.md`.
- A CORRECTION sweep keyed on the claim's PHRASING rather than its SUBJECT. The sibling bullets above cover sweeps that rewrite too MUCH; this is the inverse — a remediation that rewrites too LITTLE, and whose residual-zero count certifies the miss. Grep the noun the claim is ABOUT (the resource, the anchor comment, the count's referent), read every hit, and decide each one; then replace with derived-set language so the site cannot go stale again. A residual-zero count is evidence about a string, never about a claim. **Why:** #7539 — having measured a fabricated "six lines beneath" as an actual gap of 138 lines, the sweep for `beneath` came back clean while `tasks.md:51` said "six lines BELOW". Indexing by remembered wording is the same defect one level up from the fabrication it was correcting. See `knowledge-base/project/learnings/2026-08-17-i-corrected-a-fabricated-claim-by-grepping-its-phrasing-and-missed-a-site.md`.

If genuinely no errors occurred (including no forwarded errors), output: "Session error inventory: none detected."

This list feeds directly into the Session Errors section of the learning document. Every item on this list MUST appear in the final output unless the user explicitly excludes it.

FAILURE MODE THIS PREVENTS: Compound runs in pipeline mode, the model judges the session as "clean," and silently drops errors that happened earlier in the conversation (e.g., a skill-not-found error from one-shot Step 1 gets omitted because compound focuses only on the main implementation task).

### Recurring-vs-One-Off Tech-Debt Triage (MANDATORY)

HARD RULE: Immediately after the Session Error Inventory, classify EVERY inventory item (plus any non-error friction the session hit — a flaky gate, a slow manual workaround, a surprising tool behavior) as **recurring** or **one-off**. Do not skip this even when the inventory is short. The operator should never have to ask "are there any of these we could fix now to avoid stumbling on them again?" — this step answers it by default, separating genuine recurring tech debt from one-offs.

For each item, decide:

- **recurring** — the same class will bite again on a future session/PR (a flaky blocking gate, a missing guard, a foot-gun in a shared script, an inaccurate comment that misleads the next reader). Then pick a disposition:
  - **fix-now-inline** — ≤100 lines AND ≤4 files AND in a subsystem this PR already touches → fold the fix into THIS work. (The first two conjuncts mirror the cost-of-filing gate in `review/SKILL.md` §5; the subsystem clause is the scope-discipline overlay — a different-subsystem defect is file-tracked, never inlined here.)
  - **file-tracked** — larger, a different subsystem, or security/infra-sensitive → file a `deferred-scope-out` GitHub issue via the `review/SKILL.md` §5 mechanics (cost-of-filing + net-issue-flow: consolidate sibling follow-ups into one tracker; do not net-grow the backlog for trivia). A discovered defect in a DIFFERENT subsystem MUST stay its own issue and PR — never bundle it into an unrelated feature branch (scope discipline).
- **one-off** — a typo, a transient flake that already self-resolved, or a context-specific misstep with no recurrence vector. Note it in the learning's Session Errors and move on (no fix, no issue).

Output a short triage table (`item | recurring? | disposition`) to the user before writing the learning. `fix-now-inline` items feed the Error-to-Workflow Feedback step below; `file-tracked` items file directly via the review §5 mechanics above; and any recurring item that is a missing *workflow rule* (not a subsystem bug) additionally feeds Constitution Promotion / Route-to-Definition. **Why:** a flaky blocking gate left untriaged re-flakes for every future PR in that path — surfacing the recurring-vs-one-off split by default (instead of on request) turns each session's friction into a fix rather than shelf-ware.

### Post-Documentation Verification Gate (MANDATORY)

After the learning file is written (by compound-capture Step 6), read it back and verify:

1. If Phase 0.5 produced a non-empty error inventory, the learning file MUST contain a `## Session Errors` section with at least as many items as the inventory.
2. For each session error, the learning MUST include a `**Prevention:**` line proposing how to avoid it in future sessions.
3. If the verification fails (section missing or item count mismatch), append the missing errors to the learning file immediately. Do not proceed to Constitution Promotion until the learning is complete.

This gate closes the gap where errors were enumerated in conversation but never made it into the persisted document.

### Error-to-Workflow Feedback (MANDATORY)

After verifying session errors are in the learning, determine if any error warrants a workflow change. For each session error, ask: "Could a rule, hook, or skill instruction have prevented this?"

- If yes, produce a proposal in the same format as Phase 1.5 Deviation Analyst (rule text + enforcement tier) and feed it into Constitution Promotion alongside any deviation proposals.
- If no (the error was a one-off or already covered by existing rules), skip.

This ensures session errors don't just get documented — they feed back into the rules and definitions that govern future sessions. The goal is a closed loop: error happens → gets documented → workflow changes → error cannot recur.

## Execution Strategy: Parallel Subagents

This command launches multiple specialized subagents IN PARALLEL to maximize efficiency:

### 1. **Context Analyzer** (Parallel)

- Extracts conversation history
- Identifies problem type, component, symptoms
- Validates against solution schema
- Returns: YAML frontmatter skeleton

### 2. **Solution Extractor** (Parallel)

- Analyzes all investigation steps
- Identifies root cause
- Extracts working solution with code examples
- Returns: Solution content block

### 3. **Related Docs Finder** (Parallel)

- Searches `knowledge-base/project/learnings/` for related documentation
- Identifies cross-references and links
- Finds related GitHub issues
- Returns: Links and relationships

### 4. **Prevention Strategist** (Parallel)

- Develops prevention strategies
- Creates best practices guidance
- Generates test cases if applicable
- Returns: Prevention/testing content

### 5. **Documentation Writer** (Parallel)

- Determines optimal `knowledge-base/project/learnings/` category
- Validates category against schema
- Suggests filename based on slug
- Assembles complete markdown file
- Validates YAML frontmatter
- Formats content for readability
- Creates the file in correct location

### 6. **Optional: Specialized Agent Invocation** (Post-Documentation)

Based on problem type detected, automatically invoke applicable agents:

- **performance_issue** --> `performance-oracle`
- **security_issue** --> `security-sentinel`
- **database_issue** --> `data-integrity-guardian`
- Any code-heavy issue --> `kieran-rails-reviewer` + `code-simplicity-reviewer`

## Phase 1.5: Deviation Analyst (Sequential)

After all parallel subagents complete and before Constitution Promotion, scan the session for workflow deviations against hard rules. This phase runs sequentially (not as a parallel subagent) to respect the max-5 parallel subagent limit.

### Purpose

Close the gap between "we learned X" and "X is now enforced." The project has proven that hooks beat documentation — all existing PreToolUse hooks were added after prose rules failed. This phase detects deviations and proposes the strongest viable enforcement.

### Procedure

1. **Gather rules.** Read `AGENTS.md` and extract only `## Hard Rules` and `## Workflow Gates` items (Always/Never). Skip Prefer rules — they are advisory and flagging them adds noise.

2. **Gather session evidence.** Two sources:
   - **session-state.md** (if present): read `knowledge-base/project/specs/feat-<name>/session-state.md` for forwarded errors from preceding pipeline phases (pre-compaction deviations)
   - **Current context**: scan the conversation for post-compaction actions — tool calls, command outputs, file edits

3. **Detect deviations.** For each hard rule, check if session evidence shows a violation. Common examples:
   - Editing files in main repo when a worktree is active
   - Committing directly to main
   - Running `git stash` in a worktree
   - Skipping compound before commit
   - Treating a failed command as success
   - **Manual browser steps in prose output:** Scan all text output (summaries, handoffs, "next steps" lists) for browser tasks labeled as manual without a preceding Playwright MCP attempt. Phrases like "set up X in the browser", "go to the portal and configure", "manually create an account" are violations of the Playwright-first rule unless the session log shows a `mcp__plugin_playwright_playwright__browser_navigate` call for that task. This catches laziness in handoff text that hooks cannot detect.

3.5. **Ingest recent hook incidents.** Read `.claude/.rule-incidents.jsonl` if present (gitignored single-file log written by `.claude/hooks/lib/incidents.sh`). Filter to events emitted since the session started (use the earliest timestamp in the session log, or the last 30 minutes if no anchor is available). Filter to `event_type ∈ {deny, bypass}` **OR** `kind == "hook_self_fault"`, AND ignore lines where `error` is set — the latter are telemetry-drop sentinels (issue #3509), not deviation evidence. Treat each recent `deny` and `bypass` as evidence for the Deviation Analyst — denies confirm a hook caught a violation; bypasses signal a rule the user actively skipped. A `hook_self_fault` row (issue #7164, ADR-157) is the third class and is admitted explicitly because it carries `event_type: "warn"`, which the `{deny, bypass}` filter excludes by construction: it records a PreToolUse hook that could not parse its own stdin and therefore ran that tool call **with its guards disarmed**. That is deviation evidence of the strongest kind — not a rule the user skipped, but a rule that silently did not run — so it must never be filtered out as advisory noise. Per plan ADR-1, this step **does NOT mutate any learning's frontmatter** — counter aggregation lives exclusively in `knowledge-base/project/rule-metrics.json` (written by the local compound aggregation in Phase 1.5 step 8 — ADR-091). If the file is absent or empty, note "no recent incidents" and continue.

4. **Propose enforcement.** For each detected deviation, first check if an existing PreToolUse hook already covers it by scanning `.claude/hooks/*.sh` comment headers. If a hook already enforces the rule, note "already hook-enforced" and skip the proposal. If no hook covers it, propose enforcement following the hierarchy:
   - **PreToolUse hook** (preferred) — mechanical prevention, cannot be bypassed
   - **Skill instruction** — checked when skill runs, can be overridden
   - **Prose rule** (last resort) — requires agent compliance, weakest enforcement

5. **Format output.** For each deviation, produce:

   ```text
   ### Deviation: [short description]
   - **Rule violated:** [exact text from AGENTS.md or constitution.md]
   - **Evidence:** [what happened in the session]
   - **Existing enforcement:** [hook name if already covered, or "none"]
   - **Proposed enforcement:** [hook/skill_instruction/prose_rule]
   ```

   For hook proposals, include an inline draft script following `.claude/hooks/` conventions:

   ```bash
   #!/usr/bin/env bash
   # PreToolUse hook: [what it blocks]
   # Source rule: [AGENTS.md or constitution.md reference]
   set -euo pipefail
   INPUT=$(cat)
   # [detection logic]
   # If violation detected:
   # jq -n '{ hookSpecificOutput: { permissionDecision: "deny", permissionDecisionReason: "BLOCKED: [reason]" } }'
   ```

6. **Feed into learning document.** For each detected deviation, add it to the learning file's `## Session Errors` section (if not already present from Phase 0.5). Format: `**[description]** — Recovery: [what fixed it] — Prevention: [proposed enforcement]`. This ensures workflow violations are documented in the learning, not just proposed as hooks.

7. **Feed into Constitution Promotion.** Present each deviation to the user via the existing Accept/Skip/Edit gate in the Constitution Promotion section below. Accepted hook proposals should be manually copied to `.claude/hooks/` after testing — never auto-install.

8. **Rule budget count.** After deviation analysis, get the always-loaded verdict from the linter, then measure the registry statistics the linter does not compute.

   **[lint-agents-rule-budget.py](../../../../scripts/lint-agents-rule-budget.py) is the authority and the pre-commit reject. This step does NOT restate its thresholds — it runs it and quotes the verdict.** Restating the numbers here is exactly how they went stale (issue #6461: this rubric claimed a reject value the linter had not used for months, and the wrong measurement method besides). If a threshold matters, read it from the linter. Agreement across every restatement site is enforced by [lint-agents-compound-sync.sh](../../../../scripts/lint-agents-compound-sync.sh).

   Emit rule-application telemetry (records that the byte-cap / why-single-line policy ran — see AGENTS.md `cq-agents-md-why-single-line`):

   ```bash
   echo 'SOLEUR_RULE_APPLIED rule=cq-agents-md-why-single-line note=AGENTS.md rules cap at ~600 bytes; `**Why:**` is o'
   ```

   **Always-loaded verdict — run the linter and capture its exit code:**

   ```bash
   cd "$(git rev-parse --show-toplevel)" && \
     python3 scripts/lint-agents-rule-budget.py \
       AGENTS.md AGENTS.rules.md 2>&1
   echo "linter exit=$?"
   ```

   Two things about this invocation are load-bearing:

   - **The `2>&1` is not cosmetic.** `[WARN]` and `[REJECT]` print to **stderr**; only `[OK]` goes to stdout. The repo routinely sits in the WARN tier, where stdout is empty and the exit code is 0 — so without the redirect you capture nothing and report "no signal" when there is one.
   - **Do not hand-roll the byte math.** The thresholds are defined over **frontmatter-stripped** bytes; a `wc -c` sum overstates the payload by the frontmatter size and misreports headroom in precisely the near-cap regime where the number decides whether a rule can land.

   Read the result off **both** the exit code and the output — the exit code is the source of truth for "is the commit blocked", the verdict line only reports the always-loaded tier:

   | Exit | Output | Report |
   |---|---|---|
   | 0 | an `[OK]` / `[WARN]` verdict line | quote that line verbatim into the template's `always-loaded:` slot |
   | non-zero | a verdict line **is** present (`[OK]` / `[WARN]` / `[REJECT]`) | the commit is **blocked**. Quote the verdict line **and** every `ERROR:` line. A `[REJECT] B_ALWAYS>…` means the payload is over budget → apply the shrink ladder below. An `ERROR: rule body exceeds …` alongside an `[OK]` verdict means the always-loaded payload is fine but one rule body is too large → the fix is trimming that **one named rule**, not the payload. Do not quote the `[OK]` alone and call it healthy — the non-zero exit means something is blocking the commit. |
   | non-zero | **no** verdict line at all | the linter errored before emitting a verdict — a missing always-loaded file (exit 2) or malformed frontmatter (exit 1). Report `always-loaded: linter errored — <first ERROR: line>`; never infer a tier from silence. |

   **Consumer-repo degrade.** This skill is distributed and also runs against repos that have no rule-budget linter. Pre-check before invoking:

   | Condition | Detect | Report |
   |---|---|---|
   | Linter not present | `[ -f scripts/lint-agents-rule-budget.py ]` false | `always-loaded: not measured (no rule-budget linter in this repo)` |
   | `python3` not on PATH | `command -v python3` empty | `always-loaded: not measured (python3 unavailable)` |

   Registry statistics the linter does not compute:

   - `B_TOTAL=$(cat AGENTS.md AGENTS.rules.md 2>/dev/null | wc -c)` — full registry (informational; since ADR-151 `B_TOTAL == B_ALWAYS`)
   - Rules: `A=$(grep -hE '^- .*\[id: ' AGENTS.rules.md 2>/dev/null | wc -l)` — corpus BODIES only; an `AGENTS*.md` glob also matches the index (one pointer per body) and doubles the count (#7008)
   - Longest: `L=$(grep -h '^- ' AGENTS.rules.md 2>/dev/null | awk '{print length}' | sort -n | tail -1)` (chars; the linter measures BYTES — it is the authority for the per-rule cap)
   - Constitution: `C=$(grep -c '^- ' knowledge-base/project/constitution.md 2>/dev/null)` (tracked separately; counts every bullet, NOT just rules — a ceiling, not a rule count)

   Output:
   ```
   Rule budget:
     always-loaded:          <the linter's verdict line, verbatim>
     registry total:         B_TOTAL bytes / A rules (longest rule: L bytes)
     constitution.md:        C rules
   ```

   Append warnings:
   - If the linter reported **`[WARN]`** — the payload is approaching the ceiling, and this is the tier where remediation still has room to work, so act on it now rather than waiting for the reject:
     - Apply the placement gate (see Route Learning to Definition) and the discoverability litmus (`wg-every-session-error-must-produce-either`) **before adding any new rule**. Already-enforced and domain-scoped insights MUST route to a skill/agent, NOT `AGENTS.rules.md`.
     - Retire an existing rule via [retired-rule-ids.txt](../../../../scripts/retired-rule-ids.txt) (rule IDs are immutable — retire, never renumber or reuse).
     - *(The demote-to-a-conditional-sidecar rung was REMOVED by ADR-151 — there is no class to demote into. The ladder now offers trim-prose and retire-a-rule only, and per #6794 the retirement rung needs usage evidence the telemetry cannot currently supply.)*
     - When trimming `**Why:**` lines to fit, preserve per-issue mechanism labels (the text after each `#N`); strip redundant prose only. Correct: `**Why:** #2618 per-command-ack; #2880 non-interactive exec.` Over-trimmed: `**Why:** #2618; #2880.` (loses the per-issue mechanism distinction downstream readers use to map a rule to its triggering incident class).
   - If the linter **exited non-zero** — the commit is already blocked, and the fix depends on *why*:
     - a `[REJECT] B_ALWAYS>…` verdict means the always-loaded payload is over budget → shrink is mandatory before anything else lands; apply the same remediation ladder above, and do not attempt to add a rule first.
     - an `ERROR: rule body exceeds …` (which can appear alongside an `[OK]` always-loaded verdict) means one rule body is over the per-rule cap → trim that **single named rule** by moving its context to a learning file; the payload-shrink ladder does not address it.
   - If `L > 600`: `"[WARNING] longest rule is L bytes — cap per-rule length at ~600 (see cq-agents-md-why-single-line) by moving context to learning files."`
   - If `A > 115`: `"[ADVISORY] rule count (A/115) — bytes-first policy per cq-agents-md-why-single-line; count is informational."` <!-- rule-threshold: 115 -->
   - If `C > 300`: `"[WARNING] constitution.md is large (C/300) — consider migrating narrow rules to skill/agent instructions."`

   B_TOTAL is informational only — the per-turn cost is `AGENTS.md`, the per-session-first-turn cost is the always-loaded payload the linter reports. Since ADR-151 there are no conditional sidecars, so `B_TOTAL == B_ALWAYS` and every rule is a first-turn cost on every session.

   Additionally, if the repo has a rule-metrics aggregator at `./scripts/rule-metrics-aggregate.sh`, run it **for real** — compound is the authoritative local producer of `knowledge-base/project/rule-metrics.json` (ADR-091): it runs on the operator's machine where `.claude/.rule-incidents.jsonl` actually exists, so it, not a fresh-checkout CI cron, generates the metric. Stage the aggregate **only if it changed** (`git diff --quiet -- <OUT> || git add <OUT>`) so it lands in this session's compound commit; then parse `summary.rules_unused_over_8w` for the pruning hint. Only the redaction-safe aggregate (rule_id + counts + a 50-char public prefix) is committed — never the raw `command_snippet` log. On zero rule-carrying lines the aggregator no-ops (issue #6042), leaving the committed file untouched. Do not fail the phase if the aggregator is missing or errors, but do NOT silently swallow a crash — a stderr line tells the reader why the write/hint is absent:

   ```bash
   if [[ -x ./scripts/rule-metrics-aggregate.sh ]]; then
     OUT=knowledge-base/project/rule-metrics.json
     if bash ./scripts/rule-metrics-aggregate.sh >/dev/null 2>&1; then
       # Conditional stage: skip unchanged (jq refactor no-diff) and no-op
       # (zero rule-carrying lines) runs; stage only a real content change.
       if git diff --quiet -- "$OUT"; then
         echo "rule-metrics: $OUT unchanged; not staged." >&2
       else
         git add "$OUT"
         echo "rule-metrics: $OUT changed; staged for the compound commit." >&2
       fi
       unused=$(jq -r '.summary.rules_unused_over_8w // "unknown"' "$OUT" 2>/dev/null || echo unknown)
       if [[ -n "$unused" && "$unused" != "0" && "$unused" != "unknown" ]]; then
         echo "[INFO] $unused rules have zero hits over 8 weeks. Run /soleur:sync rule-prune to surface pruning candidates."
       fi
     else
       # The aggregator's orphan gate exits AFTER writing (CI forensic context),
       # so a failed run may have left a partial/orphan-flagged rule-metrics.json
       # in the working tree. Revert it so a later blanket `git add -A
       # knowledge-base/` (compound-capture consolidation) cannot stage a
       # rejected aggregate.
       git checkout -- "$OUT" 2>/dev/null || true
       echo "[WARN] rule-metrics-aggregate.sh failed; reverted any partial write, skipped the unused-rules hint." >&2
     fi
   fi
   ```

### Empty Case

If no deviations are detected, output: "Deviation Analyst: no violations found." followed by the rule budget count from step 8, then proceed to Phase 1.6.

<!-- phase-1.6-start -->
## Phase 1.6: Token-Efficiency Analysis (sequential, advisory)

Run the cost-efficiency report (bare anchor — ADR-179):

```bash
bash "${CLAUDE_PLUGIN_ROOT}/skills/compound/scripts/token-efficiency-report.sh"
```

Prints top-3 cost table; emits `te-*` `warn` to `.claude/.rule-incidents.jsonl` on outliers (rolled up into `knowledge-base/project/rule-metrics.json` by the local compound aggregation in Phase 1.5 step 8 — ADR-091). Proposals route through Phase 1.5 step 7's gate.

### Rubric

- Floor: `wc -c AGENTS.md` × 25 turns.
- Payload: SKILL.md bytes from `.skill-invocations.jsonl` (session_id).
- Envelopes: `total_tokens` from `.session-tokens.jsonl` (R6 self-exclusion).
- Lines: `git diff --shortstat`. Skip <50.
- Triggers: subagent >100k → `te-subagent-overshoot`; payload >200k → `te-skill-payload-floor`; ratio >2k/line → `te-agents-md-turn-cost` (gated, #3497).
- Budget: ≤1.5k tokens/fire (script bash-exec).

### Sharp Edges

> Advisory. Only large outliers (subagent >100k OR payload >200k) warrant follow-up. Activation lag: PostToolUse hook fires on next session restart post-merge.
<!-- phase-1.6-end -->

## Knowledge Base Integration

**If knowledge-base/ directory exists, compound saves learnings there and offers constitution promotion:**

### Cross-Session Promotion Loop (Layer 2)

A weekly cron (`.github/workflows/scheduled-compound-promote.yml`) consumes accumulated learnings and proposes skill or `AGENTS.rules.md` edits via draft PR when N=5 learnings cluster around the same root cause. Default OFF; opt in via `knowledge-base/project/promotion-config.yml`. See `knowledge-base/engineering/operations/runbooks/compound-promote-runbook.md`. Issue: #2720.

### Save Learning to Knowledge Base

If `knowledge-base/` directory exists, save the learning file to `knowledge-base/project/learnings/YYYY-MM-DD-<topic>.md` (using today's date). Otherwise, fall back to `knowledge-base/project/learnings/<category>/<topic>.md`.

**Learning format for knowledge-base/project/learnings/:**

```markdown
# Learning: [topic]

## Problem
[What we encountered]

## Solution
[How we solved it]

## Key Insight
[The generalizable lesson]

## Tags
category: [category]
module: [module]
```

### Constitution Promotion (Manual or Auto)

HARD RULE: This phase MUST run even when compound is invoked inside an automated pipeline (one-shot, ship). The model has historically rationalized skipping this as "pipeline mode optimization" -- that is a protocol violation. Constitution promotion and route-to-definition are the phases that prevent repeated mistakes across sessions. If the pipeline is time-constrained, present proposals with a 5-second timeout per item, but never skip entirely.

**Headless mode:** If `HEADLESS_MODE=true`, auto-promote using LLM judgment. Review recent learnings, determine if any warrant constitution promotion, select the domain and category using LLM judgment, generate the principle text, and check for duplicates via substring match against existing rules in `constitution.md`. Skip any principle that is already covered. Append non-duplicate principles and commit. Do not prompt the user. For deviation analyst proposals, auto-accept hook proposals that have clear rule-to-hook mappings and skip ambiguous ones.

**Interactive mode:** After saving the learning, present two categories of proposals:

**1. Deviation Analyst proposals (if any):** If Phase 1.5 produced deviations, present each one with Accept/Skip/Edit. For accepted hook proposals, display the draft script and instruct the user to manually copy it to `.claude/hooks/` after testing. For accepted skill instruction or prose rule proposals, apply the edit to the target file.

**2. Constitution promotion:** Prompt the user:

**Question:** "Promote anything to constitution?"

**If user says yes:**

1. Show recent learnings (last 5 from `knowledge-base/project/learnings/`)
2. User selects which learning to promote
3. Ask: "Which domain? (Code Style / Architecture / Testing)"
4. Ask: "Which category? (Always / Never / Prefer)"
5. User writes the principle (one line, actionable)
6. Append to `knowledge-base/project/constitution.md` under the correct section
7. Commit: `git commit -m "constitution: add <domain> <category> principle"`

**If user says no:** Continue to next step

### Route Learning to Definition

HARD RULE: This phase MUST run even in automated pipelines. See constitution promotion rule above.

After constitution promotion, compound routes the captured learning to the skill, agent, or command definition that was active in the session. This feeds insights back into the instructions that directly govern behavior, preventing repeated mistakes.

**AGENTS.md placement gate (mandatory).** Before proposing any edit that targets AGENTS.md, classify each insight. These placement classes are distinct from `rule-audit.sh`'s enforcement tiers (hooks/AGENTS.md/constitution/agents/skills) — they govern *where a new rule lives*, not the enforcement layer count.

- **Already-enforced:** A hook or skill step already prevents the violation. Action: ensure the target skill/hook carries the Why; do NOT add to AGENTS.md. If an equivalent AGENTS.md rule already exists, collapse it to a one-line pointer (`[<id>] [skill-enforced: <skill> <step>]. Full rule: <path>`) and append the full body to the skill.
- **Domain-scoped:** The violation only happens inside a specific skill, tool, or file pattern (tests, Terraform, CF, Playwright, Pencil, CI workflows, Next.js route files, content/docs). Action: edit the owning skill/agent/reference file. AGENTS.md is OUT OF SCOPE regardless of impact.
- **Cross-cutting session invariant:** The violation can happen on any turn without a specific trigger (e.g., blast-radius safety, silent-failure traps that span every code path, environment constraints loaded by every session). Only these qualify for AGENTS.md.

Routing mechanics:

1. Detect which skills, agents, or commands were invoked in this conversation. Also check session-state.md `### Components Invoked` for components from preceding pipeline phases.
2. Route **two categories** of insights:
   - **Solution insight:** The main learning (what was solved and how). Classify with the placement gate above, then propose a one-line bullet edit to the target file.
   - **Error prevention:** For each session error that could have been prevented by a skill instruction, classify with the placement gate, then propose a one-line bullet to the target. Example: if a plan skill prescribed wrong paths, add a bullet to the plan skill's Sharp Edges saying "Verify relative paths by tracing each `../` step before prescribing them."
3. **Default action (interactive and headless):** Apply the edit directly to the
   target skill/agent/AGENTS.md file. **Always use worktree-absolute paths**
   (`<worktree-root>/plugins/soleur/skills/<skill>/SKILL.md`) for Edit/Write
   calls — never `../../plugins/...` relatives from inside a worktree, which
   escape the worktree and resolve to the bare repo root where tracked files
   exist as stale synced copies. Verify after the edit with `git status
   --short` in the worktree: if the expected file is not listed as modified,
   the edit landed outside the working tree and must be re-applied. Commit
   with `skill: route <basename> <summary>`. Sanitize `<basename>` and
   `<summary>` before interpolation —
   `BASENAME=$(basename "$TARGET" | tr -cd '[:alnum:]._-')` — or pass the
   message via a heredoc (`git commit -m "$(cat <<EOF\nskill: route ...\nEOF\n)"`)
   so backticks or `$(...)` in a learning-file-derived basename cannot
   command-substitute. The edit surface is BOUNDED **per file**: each
   affected file gets a single bullet-point append, a single Sharp Edges
   entry, or a ≤3-line instruction clarification. Multi-file edits are
   allowed when the insight is **convergent** — one rule with parallel
   per-skill bullets (e.g., "when an enum-gate exists, plan must enumerate
   AND review must verify"). Each file's surface still has to satisfy the
   bounded-surface budget on its own. Edits that change existing bullet
   semantics or modify AGENTS.md rule wording remain OUT OF SCOPE for
   direct edit — file an issue instead.

4. **File-issue exception:** File a GitHub issue when the edit meets one of:
   contested-design (competing valid approaches), agents-md-semantic-change
   (modifies existing rule text), tier-ambiguous (insight straddles Tier 2
   and Tier 3 and a reviewer's judgment is load-bearing), **divergent
   multi-file** (two or more unrelated edits across skills that don't share
   a single insight — these warrant separate scrutiny), or any single file's
   edit exceeds the bounded-surface budget (>1 bullet, >1 Sharp Edges entry,
   or >3 instruction lines). "Cross-skill" alone is NOT a trigger — a
   convergent insight with parallel per-skill bullets applies inline.
   Title: `compound: route-to-definition proposal for <target-basename>`.
   Body: proposed edit text + target path + source learning path + `## Scope-Out
   Justification` naming the criterion. Flags: `--label deferred-scope-out
   --milestone "Post-MVP / Later"`.

5. **Interactive confirmation for direct edits:** If HEADLESS_MODE is unset,
   show the proposed diff and ask Accept/Skip/Edit-then-Accept before committing.
   In headless mode, apply directly without prompting — the bounded surface
   (single bullet append) is safe without per-edit approval.

See compound-capture Step 8 for the full flow.

**Graceful degradation:** Skips if `plugins/soleur/` does not exist or no components detected in the session.

### Managing Learnings (Update/Archive/Delete)

**Update an existing learning:**
Read the file in `knowledge-base/project/learnings/`, apply changes, and commit with `git commit -m "learning: update <topic>"`.

**Archive an outdated learning:**
Move it to `knowledge-base/project/learnings/archive/`: `mkdir -p knowledge-base/project/learnings/archive && git add knowledge-base/project/learnings/<category>/<file>.md && git mv knowledge-base/project/learnings/<category>/<file>.md knowledge-base/project/learnings/archive/`. The `git add` ensures the file is tracked before `git mv`.Commit with `git commit -m "learning: archive <topic>"`.

**Delete a learning:**
Only with user confirmation. `git rm knowledge-base/project/learnings/<category>/<file>.md` and commit.

### Managing Constitution Rules (Edit/Remove)

**Edit a rule:** Read `knowledge-base/project/constitution.md`, find the rule, modify it, commit with `git commit -m "constitution: update <domain> <category> rule"`.

**Remove a rule:** Read `knowledge-base/project/constitution.md`, remove the bullet point, commit with `git commit -m "constitution: remove <domain> <category> rule"`.

### Automatic Consolidation & Archival (feature branches)

On feature branches (`feat-*`, `feat/*`, `fix-*`, or `fix/*`), consolidation runs automatically after the learning is documented and before the decision menu. This ensures artifacts are always cleaned up as part of the standard compound flow, rather than relying on a manual menu choice.

The automatic consolidation:

1. **Discovers artifacts** -- extracts the feature slug by stripping `feat/`, `feat-`, `fix/`, or `fix-` prefix from the branch name, then globs `knowledge-base/project/{brainstorms,plans}/*<slug>*` and `knowledge-base/project/specs/feat-<slug>/` (excluding `*/archive/`)
2. **Extracts knowledge** -- a single agent reads all artifacts and proposes updates to `constitution.md`, component docs, and project `README.md`
3. **Approval flow** -- **Headless mode:** auto-accept all proposals (idempotency still checked via substring match). **Interactive mode:** proposals presented one at a time with Accept/Skip/Edit; idempotency checked via substring match
4. **Archives sources** -- runs `bash ${CLAUDE_PLUGIN_ROOT:-./plugins/soleur}/skills/archive-kb/scripts/archive-kb.sh` to move all discovered artifacts to `archive/` subdirectories via `git mv` with `YYYYMMDD-HHMMSS` timestamp prefix. **Headless mode:** auto-confirm archival without prompting
5. **Single commit** -- project edits and archival moves committed together for clean `git revert`

If no artifacts are found for the feature slug, consolidation is skipped silently. See the `compound-capture` skill for full implementation details.

**Archival renames need no `secret-scan-allow-rename` label.** `archive-kb.sh` `git mv`s plans/specs into their own `archive/` subdirectory, so BOTH sides of the rename match the gitleaks path allowlist. `rename-guard` exempts allowlist -> allowlist renames by construction (laundering requires the source to be OUTSIDE the allowlist), and the exemption is per rename pair, so a genuine laundering rename in the same PR is still caught. Do not pre-apply the label to silence it — that would disarm the guard for the whole PR. Rationale: [secret-scanning.md](../../../../knowledge-base/engineering/operations/secret-scanning.md).

**Do not skip this consolidation when driving compound's phases by hand.** The mechanism that durably archives is `archive-kb.sh` (`git mv` + a commit); this consolidation is its *automatic, unprompted* invoker, and `soleur:archive-kb` is a first-class manual one. `cleanup-merged` is NOT one — ship/SKILL.md Phase 7 Step 4 explains why — so an artifact left at a live path here stays there and costs a follow-up PR. The failure mode is specific: an agent invoking `soleur:compound` and then executing the phases itself gets everything except Auto-Consolidation **Step E**. In headless mode that step has no prompt to surface it (interactively it does ask). If you ran the phases manually, run archival explicitly (`bash ${CLAUDE_PLUGIN_ROOT:-./plugins/soleur}/skills/archive-kb/scripts/archive-kb.sh`) before handing off to `/ship`.

**Two known gaps in that script, so verify rather than assume:** it discovers plans by a `*<slug>*` glob (a topic-named plan whose name does not carry the branch slug is missed — #7373's plan was), and it probes specs only at `specs/feat-<slug>` (a `fix-*` branch's spec dir is missed; there are 27 live ones). When it reports "No artifacts found" but artifacts are visibly live, archive by hand with `git mv`.

### Worktree Cleanup (Manual)

**Headless mode:** If `HEADLESS_MODE=true`, skip worktree cleanup entirely (cleanup-merged handles this post-merge).

**Interactive mode:** At the end, if on a feature branch:

**Question:** "Feature complete? Clean up worktree?"

**If user says yes:**

```bash
git worktree remove .worktrees/feat-<name>
```

**If user says no:** Done

## What It Captures

- **Problem symptom**: Exact error messages, observable behavior
- **Investigation steps tried**: What didn't work and why
- **Root cause analysis**: Technical explanation
- **Working solution**: Step-by-step fix with code examples
- **Prevention strategies**: How to avoid in future
- **Session errors**: Process mistakes, failed commands, and wrong approaches from the session
- **Cross-references**: Links to related issues and docs

## Preconditions

<preconditions enforcement="advisory">
  <check condition="problem_solved">
    Problem has been solved (not in-progress)
  </check>
  <check condition="solution_verified">
    Solution has been verified working
  </check>
  <check condition="non_trivial">
    Non-trivial problem (not simple typo or obvious error)
  </check>
</preconditions>

## What It Creates

**Organized documentation:**

- File: `knowledge-base/project/learnings/[category]/[filename].md`

**Categories auto-detected from problem:**

- build-errors/
- test-failures/
- runtime-errors/
- performance-issues/
- database-issues/
- security-issues/
- ui-bugs/
- integration-issues/
- logic-errors/

## Success Output

```text
✓ Parallel documentation generation complete

Primary Subagent Results:
  ✓ Context Analyzer: Identified performance_issue in brief_system
  ✓ Solution Extractor: Extracted 3 code fixes
  ✓ Related Docs Finder: Found 2 related issues
  ✓ Prevention Strategist: Generated test cases
  ✓ Documentation Writer: Classified to performance-issues/, created complete markdown

Specialized Agent Reviews (Auto-Triggered):
  ✓ performance-oracle: Validated query optimization approach
  ✓ kieran-rails-reviewer: Code examples meet Rails standards
  ✓ code-simplicity-reviewer: Solution is appropriately minimal
  ✓ every-style-editor: Documentation style verified

File created:
- knowledge-base/project/learnings/performance-issues/n-plus-one-brief-generation.md

This documentation will be searchable for future reference when similar
issues occur in the Email Processing or Brief System modules.

What's next?  (Headless mode: auto-selects "Continue workflow")
1. Continue workflow (recommended)
2. Add to Required Reading
3. Link related documentation
4. Update other references
5. View documentation
6. Other
```

## The Compounding Philosophy

This creates a compounding knowledge system:

1. First time you solve "N+1 query in brief generation" → Research (30 min)
2. Document the solution → knowledge-base/project/learnings/performance-issues/n-plus-one-briefs.md (5 min)
3. Next time similar issue occurs → Quick lookup (2 min)
4. Knowledge compounds → Team gets smarter

The feedback loop:

```text
Build → Test → Find Issue → Research → Improve → Document → Validate → Deploy
    ↑                                                                      ↓
    └──────────────────────────────────────────────────────────────────────┘
```

**Each unit of engineering work should make subsequent units of work easier—not harder.**

## Auto-Invoke

<auto_invoke> <trigger_phrases> - "that worked" - "it's fixed" - "working now" - "problem solved" </trigger_phrases>

<manual_override> Use `skill: soleur:compound` [context] to document immediately without waiting for auto-detection. </manual_override> </auto_invoke>

## Routes To

`compound-capture` skill

## Applicable Specialized Agents

Based on problem type, these agents can enhance documentation:

### Code Quality & Review

- **kieran-rails-reviewer**: Reviews code examples for Rails best practices
- **code-simplicity-reviewer**: Ensures solution code is minimal and clear
- **pattern-recognition-specialist**: Identifies anti-patterns or repeating issues

### Specific Domain Experts

- **performance-oracle**: Analyzes performance_issue category solutions
- **security-sentinel**: Reviews security_issue solutions for vulnerabilities
- **data-integrity-guardian**: Reviews database_issue migrations and queries

### Enhancement & Documentation

- **best-practices-researcher**: Enriches solution with industry best practices
- **every-style-editor**: Reviews documentation style and clarity
- **framework-docs-researcher**: Links to Rails/gem documentation references

### When to Invoke

- **Auto-triggered** (optional): Agents can run post-documentation for enhancement
- **Manual trigger**: User can invoke agents after `soleur:compound` completes for deeper review

## Related Commands

- `/research [topic]` - Deep investigation (searches knowledge-base/project/learnings/ for patterns)
- `soleur:plan` skill - Planning workflow (references documented solutions)