Everything repowise knows about a repository lives in a .repowise/
directory at the repo root, created on the first
repowise init. 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
.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.
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.
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| Parameter | 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 |
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.
Provider keys and a few runtime knobs are set via environment
variables, not config.yaml. See
Environment variables. Code-health rules
are configured separately in .repowise/health-rules.json.
The distill: block
Controls output distillation for this repo.
Everything defaults sensibly when the block is absent; repowise doctor
validates it.
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 firstpermission: 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).
The hooks: block
Opt-in behaviour for the agent hooks. Absent means every key below is off.
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 runread_skeletonlets 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_rereadlets 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_digestserves multi-file grep floods as a digest instead of raw matches.coverage_reingestrefreshes the stored coverage in the background after an agent runs the whole test suite (passing or failing), soget_change_riskreads 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=1turns it on for one session.- Both
read_skeletonandread_rereadrequire Claude Code 2.1.218+; older clients are left untouched. - Saying yes to the rewrite-hook question in
repowise initturns these on too;--no-editor-setupand--no-distill-hookturn them off with everything else. To change your mind for one repo without re-runninginit, userepowise 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 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.
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/-nameentries add to or remove from the default set; an unprefixed list is treated as an explicit allowlist.leanselects the agent-lean profile:get_answer,get_context,get_symbol,search_codebase,get_risk,get_why(pluslist_reposin 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-onlyget_blast_radius,get_architectureandget_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.
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.
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: false # after a commit, ask the agent to record the decision
agent_acceptance: false # let an agent accept a decision, not only propose oneEvery 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 ship off and no preset turns them
on. With capture_prompt: true, after a git commit whose message carries
decision signals, the agent is offered the repowise decision add line once
per session. It only proposes: what the agent records lands as a candidate
for review.
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
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 --offEvery 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.
Coverage and test selection
The coverage: block feeds repowise coverage and the
CI gates; tests: extends
repowise impacted-tests.
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.
The security: block
Secret shapes of your own for repowise security check:
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 omittedEach 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:
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: checkoutrepowise.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.
.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.
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
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.
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
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, cursorThe 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.
To recognise your own bot or footer, add it under agent_provenance::
agent_provenance:
service_emails:
mybot@example.com: {agent: mybot, tier: 1}
footer_patterns:
- {pattern: "Generated by MyBot", agent: mybot}The events: block
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 sinkA 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.
The assertions: block
Names your own assertion helpers, so a test built from them is not flagged as checking nothing:
assertions:
extra_names: [ensureInvariant, mustMatch] # exact, case-insensitive; never prefixesThese 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.
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 disableThe 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.
{
"profile": null,
"disabled_biomarkers": [],
"severity_overrides": {
"high_churn": "low"
},
"rules": [
{
"path": "legacy/**",
"disabled_biomarkers": ["long_function"],
"severity_overrides": {
"duplication": "critical"
}
}
]
}| Parameter | 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.
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:
.gitignore: respected automatically, using the samegitwildmatchformat git uses. repowise reads the repo-root.gitignoreand nested.gitignorefiles in subdirectories, just like git: a.gitignorein any directory applies to that directory's contents..repowiseIgnore: same syntax as.gitignore, for repowise-only exclusions you don't want in git. Honoured at the repo root and in any subdirectory.exclude_patterns/--exclude: extra patterns from the CLI, persisted toconfig.yamland reapplied on everyupdate.- Built-in exclusions (always applied):
.git/,.repowise/,node_modules/,.venv/,dist/,build/,__pycache__/, binary files, lockfiles, and minified assets (*.min.js,*.min.css).
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:
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:
# packages/web/.repowiseIgnore
generated/
*.stories.tsxYou 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:
repowise init --include-submodules