ADR-0057: Term-Pruner Loop (Dark-Factory Glossary Hygiene)¶
Status¶
Accepted
Date¶
2026-05-07
Enforced by¶
tests/test_term_pruner_loop.py, tests/architecture/test_term_pruner_wiring.py
Context¶
ADR-0054 established TermProposerLoop to grow the ubiquitous-language glossary from live src/. Without a pruner, deletions and renames in the codebase produce permanent dangling terms — make lint-ul's anchor-resolution lint (chunk 1) fails until a human edits the term file. The dark-factory contract (CLAUDE.md, docs/wiki/dark-factory.md) requires both grow and prune to settle without humans.
PR #8681 shipped the first autonomous Term. The next class delete or rename in src/ would block every unrelated PR via the lint until pruned. This ADR closes that loop.
Decision¶
A new caretaker loop (TermPrunerLoop) periodically scans existing terms; for any term whose code_anchor no longer resolves in the live symbol index, opens an auto-merging bot PR flipping confidence to deprecated with a superseded_reason documenting the broken anchor.
Detection — purely structural¶
Eligibility (all required):
confidence == "accepted"(proposed shouldn't exist in steady-state per the chunk-2.5 simplification; deprecated already pruned)code_anchordoes NOT resolve viaresolve_anchorsuperseded_by is None(already-superseded terms are pruned via that mechanism)
No LLM call. No rename detection.
Output — auto-merged bot PRs as deprecated¶
For each tick, the loop bundles all eligible terms into ONE PR labelled hydraflow-ul-deprecated. DependabotMergeLoop auto-merges on CI green. ReviewPhase routing exception extends to skip this label (alongside hydraflow-ul-proposed).
Rename handling — two-tick flow¶
When a class is renamed, two ticks settle the glossary: 1. Tick A: Pruner detects the OLD anchor doesn't resolve → deprecates the old term. 2. Tick B (later): TermProposerLoop detects the NEW class is uncovered → proposes a new term.
The system reaches a steady state without rename-specific logic. If observed churn ever justifies it, a future ADR can add LLM-judged rename detection as an optimization.
Config + dashboard¶
Standard caretaker shape: 2 config fields with bounds, registered in BACKGROUND_WORKERS, manual trigger via dashboard, kill-switch via term_pruner_enabled.
Consequences¶
- The
lint_anchor_resolutionhard-fail lint is autonomously satisfied —make lint-ulstays green continuously. - The glossary self-heals: deletes and renames flow through the pruner+proposer pair without human intervention.
- LLM cost: zero — this loop is structural.
- The four-loop UL fleet is now complete for the grow/prune cycle; remaining (Edge-Proposer, Entry→Term Migrator) extend the graph rather than maintain it.
Alternatives considered¶
- Deprecate + auto-rename detection in one PR. Rejected: rename detection is LLM-judgmental; complexity for marginal value (the two-tick flow handles it).
- Hard-delete the term file. Rejected: loses provenance and audit trail; supersession+
deprecatedis the right archive shape. - Issue-only. Rejected: doesn't autonomously close the lint failure; human work required.