Skip to content

Cross-Project Sibling Detection with Annotate-Only Output

Problem

When a spec is authored in isolation, two failure modes appear at scale: overlap (the new spec touches the same surface as an existing one — a bundling opportunity missed) and contradiction (the new spec defines a behavior another spec already defined differently — an implementation divergence waiting to happen). The motivating incident: a developer built a ticket detail divergently because he had seen a different intent in another ticket, with no early signal that "das beißt sich hier gerade." It reproduced in our own workflow — a spec was authored that duplicated an existing one, caught only by luck.

Pattern

At spec-creation time, run a hybrid rule → LLM sibling-scan and annotate the source (the new spec) — never mutate the sibling. Three properties make it safe and useful:

  1. Deterministic rule pre-filter, LLM classifier. A dependency-free scorer ranks existing specs by toolkit-native structural signals and emits a small top-K; the LLM reads only those K and classifies each overlap|contradiction. The rule half is unit-testable with zero LLM context; the LLM half never sees the whole corpus. (Mirrors [[annotate-only-cross-cutting-features]] / SPEC-069.)

  2. Signals that actually exist in the corpus. Do not lift signals from another domain. In a prose spec corpus, "shared data models / endpoints" fire on almost nothing; the live signals are shared depends_on, explicit SPEC-NNN mentions, shared skill-name mentions, tag intersection, and title-token similarity. Weight structural signals above lexical ones.

  3. Two-tier anti-wallpaper emission gate. Emit a candidate only if it has ≥1 structural signal OR ≥2 soft (categorical/lexical) signals. A single incidental token overlap must never surface — otherwise the annotations become ignorable wallpaper and authors reflexively delete the section. Ship the gate with a calibration fixture set + explicit false-positive budget ([[heuristic-calibration-gate]] / SPEC-127): a near-duplicate positive, a structural-only match, and a semantic-only near-miss that must not surface — pinning the thresholds from both sides.

  4. Annotate-only, soft, human-decides. The new spec gets a ## Sibling-Candidates section (ID | Title | Severity | Begründung) only after an interactive, non-blocking prune (accept / edit / drop). The sibling spec is byte-identical to what it would be with the feature off. Never a hard block — a conflict is a suggestion.

When to apply

  • Relationship detection has any non-trivial false-positive rate, AND
  • The downstream effect (bundling / closing / diverging) is a human judgment call, AND
  • The corpus is prose (specs, tickets, docs) where structural cross-references exist.

When NOT to apply

  • The relationship is structurally certain (a foreign key) — just link it.
  • The corpus is small enough to eyeball, or matches are needed with high semantic recall on un-cross-referenced text — that needs an embedding index, deliberately out of scope here.

Known limitation (state it explicitly)

This catches overlap and structural contradiction. A purely semantic divergence — two specs describing the same behavior differently with no shared dep, SPEC-ID, skill mention, or near-identical title — falls below the gate and is not surfaced. Therefore an empty ## Sibling-Candidates section is not a "no conflict" guarantee. Say so in the output and the docs so an empty result is never read as false assurance. Closing this gap requires an embedding index (a future increment).

Evidence

  • Rule engine + calibration: plugins/core-standards/skills/spec-from-requirements/scripts/sibling_scan.py, plugins/core-standards/tests/test_sibling_scan.py (SPEC-127-style FP-budget gate).
  • Integration: spec-from-requirements SKILL Steps 3c / 4a / 5 / 6.
  • Source decision: project_annotate_only_cross_cutting memory; SPEC-132 decisions.md / research.md.