# Plan Check **Repository:** `reuse-surface` **Artifact:** `specs/PlanCheck.md` **Status:** Implemented (T02 deterministic matching, T03 LLM rerank, T04 State Hub bridge, T05 ecosystem convention, T06 docs/CI all shipped) **Schema:** `schemas/plan-check-result.schema.json` --- ## 1. Purpose `plan-check` closes the consumption loop the registry has lacked since inception: nothing today nudges an agent planning work in a sibling repo to query the federated capability index before building. The registry has been write-mostly. `plan-check` is the query-before-build step — advisory, not a gate — that matches a draft workplan or a free-text intent against the federated index and returns a verdict: reuse it, extend it, or it's genuinely new. This spec covers the deterministic matching core (T02) and its interfaces with the optional LLM rerank (T03) and the State Hub capability-request bridge (T04). It does not cover the ecosystem rollout (T05) — the session-protocol convention is designed once this spec is implemented and dogfooded in this repo. ## 2. Design principles 1. **Deterministic core, LLM assist optional.** Keyword/scope/relation matching against `registry/indexes/federated.yaml` works with no external dependency. `LLM_CONNECT_URL` unlocks a semantic rerank pass; its absence degrades gracefully, never blocks. 2. **Advisory, not blocking.** `plan-check` never fails a workplan into existence or refuses to let one be created. Adoption comes from the tool being useful and from convention (REUSE-WP-0018-T05), not from a gate. 3. **Deterministic matches always rank first.** Whatever the LLM rerank proposes, it is listed after — and clearly labeled apart from — the deterministic candidates, so a human or agent can trust the base result even with `--no-llm`. 4. **Every invocation is a data point.** `--record-outcome` writes an append-only fact. This is the raw material for REUSE-WP-0019's reuse telemetry — `plan-check` and telemetry share one event schema from day one so nothing needs migrating later. ## 3. Input model Two input shapes, mutually exclusive: | Input | How it's read | |---|---| | Workplan file | Parsed like `registry_update.py`'s git-diff signal collector: YAML frontmatter (`---`-delimited) plus the Markdown body. `title`, the one-liner in the intro paragraph, and any `## Problem statement` / `## Core Idea` heading content feed the match blob. | | `--intent "free text"` | Used verbatim as the match blob. | Both normalize to the same internal `MatchQuery { text: str, tokens: set[str] }` before matching — the matcher does not care which input shape it came from. ## 4. Matching pipeline Reuses the token-Jaccard approach already proven in `overlaps.py` (`TOKEN_RE`, `_tokens`) rather than inventing a second scoring method in the same codebase: 1. **Tokenize** the query blob with the existing `TOKEN_RE`. 2. **Score** against every federated capability's blob (`name` + `summary` + `tags` + `discovery.intent` + `discovery.includes`, mirroring `overlaps._entry_blob`) via Jaccard similarity. 3. **Rank signal, not just tie-break:** among candidates within `--tie-window` (default 0.05) of the top score, prefer the entry with the higher discovery/availability vector (a more mature capability is a safer reuse bet at equal textual match). 4. **Relation expansion:** if a top candidate has `relations.supports` or `relations.related_to` entries, surface them as secondary candidates labeled `related` rather than silently dropped. ## 5. Verdict model | Verdict | Condition | Meaning | |---|---|---| | `reuse` | top score ≥ `--reuse-threshold` (default 0.45) | An existing capability already covers this need — link it, don't rebuild it. | | `extend` | top score in `[--extend-threshold, --reuse-threshold)` (default 0.22–0.45) | Scope overlaps a capability closely enough that extending it is very likely cheaper than a new one — surfaced with an explicit "consider extending" framing. | | `new` | top score < `--extend-threshold`, or no federated capabilities exist | No good match. Proceed; optionally file a capability request (T04). | Thresholds are CLI flags, not hardcoded, because the right cutoff will drift as coverage and entry quality improve (see REUSE-WP-0017). Defaults come from the same threshold reasoning as `overlaps.py`'s `--threshold 0.28` default, shifted since plan-check matches short intent text against short summaries rather than long entry-to-entry blobs. ## 6. Output ### Markdown (TTY default) ```text # Plan check: reuse | extend | new **Query:** ## Top match - `capability.infotech.issue-tracking` (score 0.52, D4/A2/C2/R1) — issue-core > Unified Python/CLI interface for issue tracking across Gitea, GitHub, and GitLab... ## Other candidates - ... ## Related (via relations.*) - ... Verdict: REUSE — link this capability instead of building new. ``` ### JSON (`--format json`, agent-consumable) Validated against `schemas/plan-check-result.schema.json`: ```json { "query": {"source": "workplan|intent", "text": "...", "workplan_id": "..."}, "verdict": "reuse|extend|new", "top_score": 0.52, "matches": [ {"id": "capability.infotech.issue-tracking", "score": 0.52, "vector": "D4/A2/C2/R1", "owner": "issue-core", "kind": "deterministic|llm|related"} ], "federated_index_updated": "2026-07-06", "federated_index_stale_warning": null } ``` `federated_index_stale_warning` is set when `federated.yaml`'s `updated` field is older than 14 days — a cheap freshness signal until REUSE-WP-0019's automatic recompose lands. ## 7. Outcome recording (`--record-outcome`) Append-only JSONL under `registry/telemetry/plan-check-events.jsonl` (reuse-surface's own copy; sibling repos get their own via the same schema when they adopt T05). One line per invocation: ```json {"ts": "2026-07-07T10:00:00Z", "consumer_repo": "reuse-surface", "capability_id": "capability.infotech.issue-tracking", "verdict": "reuse", "outcome": "reused|extended|new|skipped", "source": "plan-check"} ``` This schema is deliberately identical to the reuse-event schema REUSE-WP-0019-T04 will implement server-side — `plan-check`'s local JSONL file is the fallback path when the hub is unreachable, and the same record shape posts to `POST /v1/reuse-events` once T04/WP-0019 exist. No local telemetry write ever blocks the primary command. ## 8. Relationship to T03 (LLM rerank) and T04 (State Hub bridge) - **T03** adds an optional post-pass: send the deterministic top-N candidates plus the query text to `llm-connect` for a semantic confidence score and possible reordering *within* the deterministic candidate set. It does not invent new candidates outside what deterministic matching already found — keeps the "deterministic matches always rank first" guarantee simple to reason about. Schema-constrained JSON response, mirrors the `maintain_llm.py` pattern (graceful skip on missing `LLM_CONNECT_URL`, reject malformed responses rather than guess). - **T04** wires `new` verdicts to `POST /messages/` (State Hub `request_capability`-equivalent) and reads back open capability requests for `report gaps` to list unmatched. Deferred to its own task since it depends on State Hub API shapes this spec does not need to pin down yet. ## 9. Non-goals - Blocking or gating workplan creation (§2.2). - Embedding-based / vector-similarity matching — token-Jaccard is the deterministic core for consistency with `overlaps.py`; embeddings are an LLM-rerank concern (T03), not a second deterministic path. - Editing sibling repos' rules files directly — that's the template propagation mechanism's job (T05), not this tool's.