# Configuration: config.yaml & ignore files

The .repowise/ directory, the config.yaml written on first init, the policy and component files you own, and how .gitignore and --exclude pick files.

Everything repowise knows about a repository lives in a `.repowise/`
directory at the repo root, created on the first
[`repowise init`](https://docs.repowise.dev/cli/indexing). The main configuration file is
`.repowise/config.yaml`. repowise writes it for you during `init`, and
you can edit it by hand afterwards.

## The `.repowise/` directory

```text
.repowise/
├── wiki.db          # SQLite: pages, symbols, graph, git metadata, decisions
├── lancedb/         # Vector search index
├── omissions/       # Distill omission store + savings ledger (omissions.db)
├── config.yaml      # Provider, model, embedder, exclude patterns
├── health-rules.json # Per-file code-health marker overrides
├── state.json       # Last sync commit, page counts, token usage
├── mcp.json         # MCP server configuration
├── events.jsonl     # Typed component events (CloudEvents JSON lines)
├── events.key       # Random key for the owner digests in component snapshots
├── attribution/     # Opt-in edit capture: line hashes only
└── .env             # API keys (gitignored automatically)
```

repowise adds `.repowise/` to your `.gitignore` automatically. It's a
local cache, not a source of truth, so don't commit it.

A few files under `.repowise/` are written by you, not by repowise, and are
meant to be reviewed and committed like code: `policies.yaml`,
`components.yaml`, `teams.yaml` and `decisions.yaml`. If a `.repowise/` rule
hides them, add `!.repowise/policies.yaml` (and so on) to your `.gitignore`.
A repo-root `catalog-info.yaml` is read the same way as `components.yaml`.

## `config.yaml`

Generated after the first `init` and updated when you pass flags like
`--commit-limit` or `--follow-renames`. You can also edit it directly;
changes take effect on the next `init`, `update`, `serve`, or `mcp` run.

> **Warning**
>
> Most of `config.yaml` has no schema validation. It's loaded as a plain
> YAML dict, so an unknown or misspelled key is usually ignored; it won't
> error and won't take effect. The exceptions: `decisions:` warns about
> unknown keys, `distill:` is checked by `repowise doctor`, and an invalid
> `coverage:` gate or an unknown key in `tests:` or `security:` fails the
> command that reads it (exit `2`). If a setting doesn't seem to take effect, check spelling
> and indentation first.

```yaml
provider: anthropic                  # LLM provider
model: claude-sonnet-5-5             # Model identifier
embedder: gemini                     # Embedding provider for semantic search
embedding_model: text-embedding-3-small  # Embedding model (provider default if omitted)
reasoning: auto                      # auto | off | none | minimal | low | medium | high | xhigh | max
max_tokens: 16384                    # Max output tokens for each generated documentation page
wiki_style: comprehensive            # comprehensive | caveman | reference | tutorial | custom
language: en                         # Output language for generated pages (en, zh, ru, hi, ...)
max_file_pages: 2000                 # Cap file pages (omit = size policy, 0 = one page per file)
generation_context:                  # Optional source evidence for synthesis pages
  token_budget: 8000
  files:
    repo_overview:
      - docs/ARCHITECTURE.md
exclude_patterns:                    # Gitignore-style patterns
  - vendor/
  - "*.generated.*"
  - proto/
commit_limit: 5000                   # Newest commits walked for git analysis (1-10000)
follow_renames: false                # Track file renames in git history
enable_onboarding: true              # Show first-run onboarding prompts
openai:                              # Per-provider base URL, e.g. LM Studio
  base_url: http://localhost:1234/v1
changes:
  outcome_fix_days: 30               # Window for "files that received fixes" on Change Records
```

| Name | Type | Default | Description |
|---|---|---|---|
| `provider` | string | `auto-detected` | LLM provider: anthropic, openai, gemini, openrouter, deepseek, kimi, edenai, ollama, litellm, codex_cli, copilot_cli, claude_cli, opencode |
| `model` | string |  | Model identifier override for the provider |
| `embedder` | string | `mock` | Embedding provider: openai, gemini, ollama, openrouter, edenai, mock. Unset: see the embedder order below |
| `embedding_model` | string |  | Embedding model identifier, provider default if omitted |
| `reasoning` | string | `auto` | Reasoning mode for capable models: auto \| off \| none \| minimal \| low \| medium \| high \| xhigh \| max |
| `max_tokens` | number | `16384` | Max output tokens requested for each model-written documentation page. Must be a positive integer when documentation is generated |
| `wiki_style` | string | `comprehensive` | Voice and density of generated wiki pages: comprehensive \| caveman \| reference \| tutorial \| custom. Editing this key and running update does not retranslate existing pages, use repowise restyle |
| `language` | string | `en` | Output language for generated wiki prose (code, paths, and symbol names stay untranslated). Changing it later doesn't retranslate existing pages, use init --force --language <code> |
| `max_file_pages` | number |  | Most file pages a run emits, highest importance first. Unset lets a size policy decide, 0 means one page per eligible file, a positive value is a hard cap |
| `generation_context` | object |  | Repository-source evidence appended to model-written overview and onboarding prompts (see below) |
| `exclude_patterns` | string[] |  | Extra gitignore-style patterns to skip (persisted from --exclude) |
| `commit_limit` | number | `5000` | Newest commits walked for git analysis and stored as commit rows, clamped to 1-10000. Sets how far back churn, co-change, Change Records and suspects see. A lower value an existing config records is kept, and update names it once |
| `follow_renames` | boolean | `false` | Track file renames across git history (slower) |
| `enable_onboarding` | boolean | `true` | Show first-run onboarding prompts (CLI and web) |
| `<provider>.base_url` | string |  | Base URL for that provider, e.g. openai: {base_url: http://localhost:1234/v1} for LM Studio. Takes precedence over OPENAI_BASE_URL and the other *_BASE_URL variables |
| `changes.outcome_fix_days` | number | `30` | Days after a change in which fixes to its files are counted on its Change Record (1 to 365) |
| `forges.hosts` | object | `{}` | Maps a self-managed git host to its forge: github, gitlab, azure, bitbucket or generic |

`exclude_patterns` and `wiki_style` live in this file for every surface: the
web UI's repository settings write both keys here (other keys kept), and the
CLI, the indexer, health reads and server jobs read them from here. A value
saved before on the server applies while the file lacks the key and moves
into the file on the next save.

Provider, model and base URL resolve in one order across the CLI, the server
and MCP: flag or web UI choice, then `config.yaml`, then the web UI's global
default, then `REPOWISE_PROVIDER` / `REPOWISE_MODEL`. Choosing a model or base
URL for a repo in the web UI writes this file. Because `config.yaml` may be
committed, a non-local `base_url` receives an API key from outside the repo's
`.repowise/.env` only after you confirm it in Settings or with
`repowise init`. A loopback base URL needs no key.

The embedder resolves separately. First match wins: `--embedder`, then
`REPOWISE_EMBEDDER`, then `embedder` in `.repowise/config.yaml`, then the first
key present in the order gemini, openai, openrouter, ollama, edenai, then
`embedder` in `~/.repowise/config.yaml`, then `mock`. `reindex` skips a `mock`
pin when a real embedder can be found. Agent CLI providers (`claude_cli`,
`codex_cli`, `copilot_cli`, `opencode`) have no embedder of their own.

`generation_context.files` supplies explicit evidence for model-written
synthesis pages when the page's assembled structural context doesn't
normally include a fact you want it to have. Keys name the target page:
`repo_overview` or `onboarding/<slot>` for `getting_started`,
`key_concepts`, `how_it_works`, and `active_landscape`. Each value is an
ordered list of repository-relative paths; a file is only eligible if it
was included in the indexed source map and contains non-empty UTF-8 text.
`token_budget` (default `8000`) is a per-page cap shared across configured
files, estimated with repowise's normal four-characters-per-token
heuristic. Existing pages aren't regenerated just by editing
`config.yaml`, run `repowise generate --all` or request the affected page.

> **Note**
>
> Provider keys and a few runtime knobs are set via environment
> variables, not `config.yaml`. See
> [Environment variables](https://docs.repowise.dev/installation/env-vars). Code-health rules
> are configured separately in `.repowise/health-rules.json`.

## The `distill:` block

Controls [output distillation](https://docs.repowise.dev/cli/distill) for this repo.
Everything defaults sensibly when the block is absent; `repowise doctor`
validates it.

```yaml
distill:
  enabled: true                  # master switch for this repo
  commands:
    enabled: true                # the command path (CLI + hook rewrites)
    permission: allow            # ask | allow | off: rewrite-hook posture
    families:                    # per-filter overrides
      test_output: allow         #   auto-allow rewrites for test runs
      git_diff: deny             #   never rewrite git diff here
    disabled_filters: []         # filters to skip entirely, e.g. [logs]
  omission_store:
    ttl_days: 7                  # prune stored omissions after this many days
    max_mb: 50                   # size cap; oldest entries pruned first
```

`permission: allow` is the default: it auto-approves rewrites uniformly
across the main agent and every subagent. This is not a permission
escalation, a rewrite is always `repowise distill <one recognized
command>` from a closed family set, never an arbitrary command smuggled
behind the wrapper. Set `ask` to have each rewritten command shown for
approval instead, or `off` to disable rewrites in this repo.

`families` keys are filter names (`test_output`, `build_output`,
`lint_output`, `git_status`, `git_log`, `git_diff`, `search_results`,
`file_listing`, `logs`) and accept `ask | allow | off | deny`. Declining
the `repowise init` opt-in prompt writes `commands.enabled: false`, so a
rewrite hook installed globally from another repo stays inert here.
Under Codex, only families set to `allow` are rewritten; its hook
protocol has no ask-with-mutation (see [Distill](https://docs.repowise.dev/cli/distill)).

## The `hooks:` block

Opt-in behaviour for the agent hooks. Absent means every key below is off.

```yaml
hooks:
  read_skeleton: false           # serve large indexed files as skeletons
  read_reread: false             # serve unchanged re-reads as a pointer
  search_digest: false           # serve multi-file grep floods as a digest
  coverage_reingest: false       # re-ingest coverage after an agent's full test run
  health_gate: false             # gate an agent's git commit / git push on repowise health check
```

- `read_skeleton` lets the PostToolUse Read hook return the *skeleton* of
  a large indexed file instead of the full file, once per file per
  session. Signatures stay; bodies become `... N lines (a-b)` markers, and
  any elided span can be pulled back with a ranged Read. Reading the file
  a second time returns it whole.
- `read_reread` lets the PostToolUse Read hook answer a *repeat* Read of
  the same range with a short notice instead of the content, when nothing
  changed the bytes since the last time this session served them. A file
  whose content differs on disk is always served in full.
- `search_digest` serves multi-file grep floods as a digest instead of
  raw matches.
- `coverage_reingest` refreshes the stored coverage in the background after
  an agent runs the whole test suite (passing or failing), so
  [`get_change_risk`](https://docs.repowise.dev/mcp/get-change-risk) reads current coverage. It
  acts only in a repository that has ingested coverage before, and never on a
  run that targets some tests. `REPOWISE_HOOK_COVERAGE_REINGEST=1` turns it on
  for one session.
- `health_gate` blocks an agent's `git commit` or `git push` that fails
  `repowise health check`. Set it with
  [`repowise hook health-gate install`](https://docs.repowise.dev/cli/hook#repowise-hook-health-gate-installuninstallstatus),
  which also writes the hook entry.
- Both `read_skeleton` and `read_reread` require Claude Code 2.1.218+;
  older clients are left untouched.
- Saying yes to the rewrite-hook question in `repowise init` turns these
  on too; `--no-editor-setup` and `--no-distill-hook` turn them off with
  everything else. To change your mind for one repo without re-running
  `init`, use `repowise hook read-skeleton install | uninstall | status`.

## The `mcp:` block

Controls which tools the MCP server advertises. The default surface is
curated, not every tool: 10 tools in single-repo mode, plus
[`list_repos`](https://docs.repowise.dev/mcp/list-repos) in workspace mode. This block lets you opt extra tools in
or trim the set down; the `repowise mcp --tools` / `--all` flags override
it for a single launch.

```yaml
mcp:
  tools: ["+get_execution_flows", "-get_dead_code"]   # adjust the default set
  # tools: ["get_answer", "get_context"]              # or an explicit allowlist
  # tools: all                                        # or everything available
  # tools: lean                                       # or the agent-lean profile
```

- `+name` / `-name` entries add to or remove from the default set; an
  unprefixed list is treated as an explicit allowlist.
- `lean` selects the agent-lean profile: `get_answer`, `get_context`,
  `get_symbol`, `search_codebase`, `get_risk`, `get_why` (plus
  `list_repos` in workspace mode), small enough that Claude Code can keep
  every schema always loaded.
- Opt-in tools, off by default everywhere, are `get_dependency_path`,
  `get_execution_flows`, `generate_refactoring_code`, `set_finding_status`,
  and the workspace-only [`get_blast_radius`](https://docs.repowise.dev/multi-repo/blast-radius),
  [`get_architecture`](https://docs.repowise.dev/multi-repo/architecture-metrics) and
  [`get_conformance`](https://docs.repowise.dev/multi-repo/conformance).

## The `editor_files:` block

Controls the generated block in `.claude/CLAUDE.md` and `AGENTS.md`. The block
is read into the agent's context on every turn, so the default is lean.
`sections` adjusts it with the `mcp.tools` grammar: `+name` / `-name` change
the default set, unprefixed names replace it.

```yaml
editor_files:
  claude_md: true
  sections: ["+commands", "+fix_first"]
```

| Sections | Contents |
|---|---|
| Default | `architecture`, `key_modules`, `entry_points`, `hotspots`, `health` (score and worst file), `decisions` (top three titles), `how_to_work` (three bullets), `tools` |
| Opt-in | `code_health` (every health signal, replaces `health`), `fix_first`, `commands` (build, test and lint read from manifests), `how_to_work_full` |

## The `decisions:` block

Controls decision capture: whether it runs at all, which sources it uses,
and whether any of them may call a model. One resolved policy backs the
CLI, the API, and the index pipeline, so all three agree about what will
run.

```yaml
decisions:
  enabled: true             # master switch for automatic capture
  llm: true                 # master switch for decision-extraction model calls
  preset: balanced          # default | off | local_only | balanced | full
  sources:
    inline_marker: true     # WHY:/DECISION: markers
    git_archaeology: true   # commit messages
    adr: true               # ADR files
    pr: true                # PR / squash-merge bodies
    comment: false          # comment archaeology on top central files
    session:                # off by default; long form runs the source, skips its model stage
      enabled: true
      llm: false
    session_discovery: true # one broad model pass over new transcript prose
    conventions: false      # import patterns the graph proves, no model
  discovery:                # budget for that one pass, per update
    max_sessions: 12        # 1-24
    max_input_tokens: 30000 # 2000-60000
  harnesses: [claude_code]  # whose transcripts the session source reads; add codex
  capture_prompt: true      # after a commit, ask the agent to record the decision
  agent_acceptance: true    # let an agent accept a decision, signed as itself
```

Every key is optional. A config with no `decisions:` block resolves to
the `default` policy: `inline_marker`, `git_archaeology`, `adr`, `pr` and
`comment` on with their model stages; `session`, `session_discovery` and
`conventions` off until you turn them on
(`repowise decision source set session --on`). Upgrading never starts a
model call nobody enabled. A config that names a preset *and* lists its
sources keeps exactly that list; re-apply the preset to pick up a source
the preset gained since.

A source is a bare boolean or an `{enabled, llm}` mapping. The long form
only matters for a source with both a deterministic and a model stage
(`inline_marker`, `adr`, `session`): it keeps the deterministic parse and
skips the model call. A source that is model-only (`git_archaeology`,
`pr`, `comment`, `session_discovery`) is skipped entirely when its model
stage is off, because running it would produce a zero indistinguishable
from an empty repository.

Unknown keys are reported as warnings rather than discarded, so a typo'd
source name shows up instead of silently reading as a working switch.
Unrecognised names such as `code_comment`, `changelog` and
`readme_mining` load with a warning naming the key that was ignored.

### Presets

Conveniences that write the same keys. Nothing downstream branches on
the preset name.

| Preset | Effect |
|---|---|
| `default` | What a config with no `decisions:` block resolves to. Every long-standing source on; session mining and broad discovery off. |
| `off` | No automatic capture. Stored decisions and manual entry keep working. |
| `local_only` | Deterministic capture only, session mining included. Zero decision-extraction model calls. |
| `balanced` | The high-signal sources plus session mining and broad session discovery; comment archaeology off. |
| `full` | Every source, every model stage. |

Editing any individual key after applying a preset drops the `preset`
line, and the resolved policy reads as `custom`.

`llm: false` is a complete mode, not a degraded one: transcripts are
still read, markers and ADRs are still parsed, episodes are still
recorded, manual decisions still work, and already-accepted decisions
keep governing. It is the one switch that proves no decision extraction
reaches a model.

### Session mining

The `session` source lets `repowise update` mine coding-agent session
transcripts (Claude Code's `~/.claude/projects/`) for durable decisions:
user corrections, explicit choices with a stated reason, and failed
approaches replaced by working ones. Candidates pass deterministic gates
first, then one batched LLM structuring call per update, and every
produced field must quote the transcript verbatim or it is dropped.
Everything stays local: transcripts are read from your machine, staging
lives in `.repowise/sessions/sessions.db`, and only the distilled
decision text about the codebase is stored.

`session_discovery` is the broader reading of the same transcripts: one
model pass over new prose each update, bounded by the `discovery:`
budget above. It is off in the `default` policy and on in `balanced` and
`full`.

`capture_prompt` and `agent_acceptance` are on by default, and a
non-boolean value reads as off. With `capture_prompt` on, after a `git commit`
whose message carries decision signals, the agent is offered the
`repowise decision add --agent` line once per session. It only proposes: what
the agent records lands as a candidate for review. With `agent_acceptance` on,
an agent accepts by signing as itself; set it to `false` to keep granting to
people.

> **Warning**
>
> `decisions.session_mining: true|false` is an alias for the `session`
> source in both directions. The first write through the CLI or the API
> replaces it with `sources.session`, which is the key to write.

### Setting it from the CLI

```bash
repowise decision config show          # the resolved policy, per source
repowise decision config preset local_only
repowise decision source set comment --off
repowise decision source set adr --no-llm     # keep the parse, skip the model
repowise decision source set session_discovery --on
repowise decision config discovery --max-sessions 6 --max-input-tokens 12000
repowise decision llm --off
```

Every mutating command takes `--dry-run` to print the change without
writing, and `--format json` for scripts. Writes are atomic and preserve
every unrelated key in `config.yaml`. See
[`repowise decision`](https://docs.repowise.dev/cli/decision).

## Coverage and test selection

The `coverage:` block feeds [`repowise coverage`](https://docs.repowise.dev/cli/gates) and the
[CI gates](https://docs.repowise.dev/ci); `tests:` extends
[`repowise impacted-tests`](https://docs.repowise.dev/cli/gates#repowise-impacted-tests).

```yaml
coverage:
  paths:                         # reports or globs (skips discovery)
    - coverage/lcov.info
    - {path: "web/coverage/*.info", path_prefix: web}
  strip_prefix: "/build/src/"    # trim a prefix from report paths
  ignore: ["**/*_pb2.py"]        # gitignore-style globs coverage leaves out
  fail_under: 80                 # patch-coverage gate for coverage check (0-100)
  min_coverable_lines: 5         # smaller changes are reported, never failed
  fail_under_risky: 90           # optional gate over hotspots and bug magnets
  fail_under_branches: 70        # optional gate on branches taken on changed lines
  max_drop: 0.5                  # most project coverage may fall from the base, in points
  gates:                         # path-scoped gates (repowise coverage suggest-gates)
    - {name: api, paths: ["/services/api/"], fail_under: 85}
    - {name: scripts, paths: ["/scripts/"], fail_under: 50, informational: true}
tests:
  full_run_on: ["migrations/**"] # changes here select every test
  always_run: ["tests/test_smoke.py"]
```

Discovery keys (`auto_discover`, `artifacts`, `format`,
`reingest_on_update`) are on [Test intelligence](https://docs.repowise.dev/intelligence/test-intelligence#configuration).

## The `security:` block

Secret shapes of your own for `repowise security check`:

```yaml
security:
  patterns:
    - name: internal_token        # 1 to 40 of a-z, 0-9, _ and -, unique
      regex: 'itk_[A-Za-z0-9]{32}'
      severity: high              # low, med or high; high when omitted
```

Each reports as the kind `custom:<name>`, masked like every other secret.
A regex that does not compile or can match zero characters fails the
check with exit `2`. At most 50 patterns.

## `.repowise/components.yaml`

Components name the parts of a repository that health, hotspots, bus factor,
test reach, dead code and Change Records roll up to. The file is
multi-document Backstage YAML (`Component`, `System` and `Group` on
`backstage.io/v1alpha1`) with repowise data in annotations:

```yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: payments-api
  annotations:
    repowise.dev/paths: "services/payments/**, libs/billing/**"
    repowise.dev/criticality: high          # low, medium, high or critical
    repowise.dev/tags: "pci, api"
spec:
  type: service
  owner: group:default/payments
  system: checkout
```

`repowise.dev/paths` holds gitignore-style globs written with `/`, matched
case-sensitively; where two components match a file, the first declared wins.
A repo-root `catalog-info.yaml` is read the same way, so a developer portal
and repowise share one file. With no file, components are derived from
workspace services, then module pages, then top-level directories. Validate
with [`repowise components check`](https://docs.repowise.dev/cli/components#repowise-components).

## `.repowise/teams.yaml`

Declared teams and the identities that count as each team's activity, for
`repowise components ownership`. Members are used only to match commits and
never appear in output.

```yaml
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
  name: payments
  annotations:
    repowise.dev/member-emails: "dev1@example.com, dev2@example.com"
spec:
  type: team
  children: []
  members: [dev1-login, dev2-login]
```

A CODEOWNERS `@org/team` (GitHub or GitLab, sections included) and a catalog
`spec.owner` of `group:default/team` both resolve to the Group named `team`.

## `.repowise/policies.yaml`

The change policy [`repowise check`](https://docs.repowise.dev/cli/changes#repowise-check)
evaluates. Each rule selects components (`match`, `exclude`), tests
conditions over the change's evidence (`when`, all must hold) and names an
action. A rule can instead wrap an existing gate with that command's options
under `with`.

```yaml
version: 1
rules:
  - name: agent-pci-needs-owner
    match:
      components: {tags: [pci]}          # also names, criticality; each list is any-of
    when:
      attribution: agent                 # agent, human_attested or unknown
      reaching_tests: missing            # a changed file in scope has no reaching test
    action: require_owner_review
  - name: no-new-secrets
    gate: security                       # security, coverage, doc_drift or health
    with:
      fail_on: high
      baseline: .security-baseline.json
    action: fail                         # the default for a gate
```

| Key | Values |
|---|---|
| Conditions (`when`) | `attribution`, `agent_tier` (1, 2 or 3), `reaching_tests` (`missing` or `present`), `contract_break`, `decision_crossed`, `bus_factor_below` |
| Actions | `warn`, `require_owner_review` and `require_tests` (reported, exit 0), `fail` (exit 1) |
| Gates | `security`, `coverage`, `doc_drift` (scoped to the change unless the rule sets `since`) |

There is no risk-score condition. A condition whose evidence is unavailable
leaves the rule not evaluated, and it never fires. An unknown key, condition
or gate option, a bad action or a duplicate rule name exits `2` with every
problem listed.

## The `attribution:` block

```yaml
attribution:
  enabled: true      # read public AI markers: bot authors, trailers, footers, git-ai notes, Agent Trace files
  capture: off       # off, local or notes
  agents: []         # agent ids to capture, e.g. claude-code, codex, cursor
```

The same block in `~/.repowise/config.yaml` applies to every repository, and
the stricter value wins per key: `enabled: false` in either file, the lower
capture level, and only the agents both files list. With `enabled: false`
nothing about AI is stored or shown anywhere, and `DO_NOT_TRACK=1` keeps
capture off. `capture: local` records the listed agents' edits as line hashes
in `.repowise/attribution/`; `notes` also writes git-ai notes on
`refs/notes/ai`. See [`repowise attribution`](https://docs.repowise.dev/cli/components#repowise-attribution).

To recognise your own bot or footer, add it under `agent_provenance:`:

```yaml
agent_provenance:
  service_emails:
    mybot@example.com: {agent: mybot, tier: 1}
  footer_patterns:
    - {pattern: "Generated by MyBot", agent: mybot}
```

## The `events:` block

```yaml
events:
  enabled: true            # snapshot components at each index run and diff them
  retention_days: 365      # older runs and events are dropped
  compact_after_days: 30   # older runs keep one per ISO week
  kinds: {new_hotspot: true}
  sinks: [jsonl]           # jsonl (.repowise/events.jsonl), stdout, or a plugin's sink
```

A run is diffed against the run right before it when both ran under the same
analyzer and parser stamp; a stamp change starts a new baseline. `new_hotspot`
is off by default and every other kind is on. `repowise doctor` names any
value here that fell back to its default. Read events with
[`repowise events`](https://docs.repowise.dev/cli/changes#repowise-events).

## The `assertions:` block

Names your own assertion helpers, so a test built from them is not flagged
as checking nothing:

```yaml
assertions:
  extra_names: [ensureInvariant, mustMatch]   # exact, case-insensitive; never prefixes
```

These names reach only the advisory test-quality markers. They never change
a health score.

## The `refactoring:` block

Controls the refactoring-intelligence layer: the structured Extract
Class / Extract Helper / Move Method / Break Cycle / Split File plans
surfaced by `repowise health --refactoring-targets`,
`get_health(include=["refactoring"])`, and the web Refactoring tab.

```yaml
refactoring:
  enabled: true               # the deterministic plans (zero LLM, in the health pass)
  detectors:
    disabled: []              # e.g. [move_method] to silence one detector
  min_confidence: null        # low | medium | high (confidence floor; null = no floor)
  llm:
    enabled: true             # code generation, on by default; set false to disable
```

The deterministic layer is zero-LLM and runs in the `init` / `update`
health pass. Code generation is the only part that calls a provider.
It's on by default but never runs during indexing, only on an explicit
request (set `llm.enabled: false` to disable it). `enabled: false`
skips the whole deterministic detector pass; `detectors.disabled`
silences named detectors while the rest keep running.

## The `health-rules.json` file

A separate JSON file (not part of `config.yaml`) that tunes code-health
biomarkers: which ones run, their severity, and per-path overrides.
Malformed JSON never raises, repowise warns and falls back to an empty
config.

```json
{
  "profile": null,
  "disabled_biomarkers": [],
  "severity_overrides": {
    "high_churn": "low"
  },
  "rules": [
    {
      "path": "legacy/**",
      "disabled_biomarkers": ["long_function"],
      "severity_overrides": {
        "duplication": "critical"
      }
    }
  ]
}
```

| Name | Type | Default | Description |
|---|---|---|---|
| `profile` | string | `null` | Named calibration profile; only "small-team" is defined today |
| `disabled_biomarkers` | string[] | `[]` | Biomarker names to skip repo-wide |
| `severity_overrides` | object | `{}` | Maps a biomarker name to a severity label: low \| medium \| high \| critical |
| `rules` | object[] | `[]` | Per-path overrides, evaluated in order. Each entry takes path (glob, aliases path_glob / glob also accepted), disabled_biomarkers, and severity_overrides |

Only the severity **label** is overridable this way, never the
underlying numeric weights or caps that produce the health score.

> **Note**
>
> `skip_tests`, `skip_infra`, and `include_submodules` are CLI-flag-only
> (`--skip-tests`, `--skip-infra`, `--include-submodules`) and they don't
> persist to `config.yaml`, so pass them again on each `init`/`update`
> run if you want the same exclusions.

## Controlling what gets indexed

repowise decides which files to document using, in order:

1. **`.gitignore`**: respected automatically, using the same
   `gitwildmatch` format git uses. repowise reads the **repo-root
   `.gitignore` *and* nested `.gitignore` files** in subdirectories, just
   like git: a `.gitignore` in any directory applies to that directory's
   contents.
2. **`.repowiseIgnore`**: same syntax as `.gitignore`, for repowise-only
   exclusions you don't want in git. Honoured at the repo root and in any
   subdirectory.
3. **`exclude_patterns` / `--exclude`**: extra patterns from the CLI,
   persisted to `config.yaml` and reapplied on every `update`.
4. **Built-in exclusions** (always applied): `.git/`, `.repowise/`,
   `node_modules/`, `.venv/`, `dist/`, `build/`, `__pycache__/`, binary
   files, lockfiles, and minified assets (`*.min.js`, `*.min.css`).

> **Note**
>
> **Monorepos & yarn/npm workspaces**: because nested `.gitignore` files
> are honoured, a workspace package that keeps its own `.gitignore`
> (excluding that package's build output, generated bundles, coverage,
> etc.) is respected without any extra configuration. You don't need to
> duplicate those patterns at the repo root.

Add extra patterns on the command line; they're saved to `config.yaml`
and reused by later `update` runs:

```bash
repowise init -x vendor/ -x "*.generated.ts" -x proto/ -x "**/*.pb.go"
```

Or drop a `.repowiseIgnore` file at the repo root or in any subdirectory
for granular control without touching `.gitignore`:

```text
# packages/web/.repowiseIgnore
generated/
*.stories.tsx
```

You can also skip categories of files with flags: `--skip-tests`
excludes test files, and `--skip-infra` excludes Dockerfiles, Makefiles,
and shell scripts.

## Submodules

Git submodule directories are excluded by default (repowise reads
`.gitmodules` to detect them). Include them with:

```bash
repowise init --include-submodules
```
