# os-adr invariants

Behavioral invariants of the plugin. Changing any of these requires explicit human approval and
a matching update to the tests named beside it. Suite entry points: `ruby tests/all.rb` and
`python3 tests/hook_test.py` (both model-free).

1. **Non-destructiveness (migration).** Migration writes only new files under `docs/adr/`;
   existing source content is never deleted, moved, or edited by any tooling path. Old-system
   removal is a separate, explicit, user-approved step (skill-mediated, never automatic).
   — `migrator_test.rb#test_sources_stay_byte_identical`, pilot sha256 checks.

2. **Index regeneration, never accretion.** `docs/adr/README.md`'s table is regenerated in full
   from directory contents on every ADR write; it is never appended to or hand-edited, and
   self-heals after manual edits. Content outside the markers is preserved.
   — `index_test.rb` (golden fixture, self-heal, prose preservation, generated-header cases).

3. **IDs come from the directory, never the index.** Next ID = max existing filename ID + 1,
   derived by scanning `docs/adr/`; a stale or missing index never influences numbering.
   — `repository_test.rb#test_next_id_ignores_{missing,stale}_index`.

4. **No LLM in the hook.** The SessionStart hook is a pure function of filesystem state (ADR
   dir, index, suppression state, snooze stamp) — no model call, no network, no Ruby spin-up;
   always exits 0; emits nothing (and creates no state) outside a git project. Its existence
   rule is identical to `Adr::Repository#exists?`.
   — `hook_test.py` (all cases; `PythonRubyAgreementTest` pins the shared rule).

5. **Single uncertainty-flagging mechanism.** Migration uncertainty surfaces only via
   `migration_confidence: low|medium|high` frontmatter plus `docs/adr/migration-report.md`.
   No scattered inline uncertainty comments.
   — `migrator_test.rb#test_report_lists_files_confidence_sources_and_flag_rate`.

6. **Core fields are never invented.** A Decision or Context absent from a migration source is
   written as `_not stated in source_` and flags the file low-confidence; LLM fills apply only
   to pending-marker fields (enforced structurally by `apply_fills!` — a stray fill key is a
   no-op), and an LLM-filled file stays flagged (≤ medium).
   — `migrator_test.rb#test_missing_core_fields_*`, `#test_apply_fills_only_touches_pending_fields`.

7. **Unrecognized content is reported, never auto-converted.** Content matching no known shape
   — including multi-decision files without mechanically splittable unit headings — is listed
   in the report for manual handling only.
   — `detector_test.rb#test_multi_decision_file_without_unit_headings_is_unrecognized`,
   `migrator_test.rb#test_unrecognized_content_is_reported_never_converted`.

8. **Deterministic-first retrieval.** `adr-find` narrows by frontmatter path/component match,
   then Accepted-only status filter (explicit override), then optional one-hop graph expansion
   that degrades gracefully (absent or corrupt graph → layers 1–2). AI judgment happens only in
   the `find` skill, only over the CLI's returned candidates.
   — `finder_test.rb` (all cases, including both degradation paths).

9. **Skills delegate mechanics to `bin/` CLIs.** Numbering, templating, indexing, supersession
   bookkeeping, detection, conversion, and retrieval filtering are never re-implemented in
   prompt instructions.
   — by construction of `skills/*/SKILL.md`; `cli_test.rb` pins the CLI contracts.
