reuse-surface/specs/PlanCheck.md
tegwick 50077c9bea
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
ci / validate-registry (push) Successful in 1m40s
Build and Publish Container Image / build-and-push (push) Successful in 40s
Fix short-intent discovery and publish verified browser provider guidance
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13690-1c84-77a2-aeee-7f517ec60f5d
2026-10-13 20:48:05 +02:00

8.1 KiB
Raw Blame History

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 with the maximum of two token-Jaccard similarities: query versus capability name, and query versus name + summary + tags. Scoring the name separately prevents detailed summaries and consumption instructions from diluting a short direct query such as "headless browser". Summary/tag matches and longer intents retain the full-blob signal.
  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) A close lexical match exists; review its consumption contract and availability before adoption.
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.

Verdicts describe relevance, not runtime readiness. A source-only candidate or an unavailable endpoint is an availability gap on the existing capability, not evidence that a second capability should be built. The match summary carries provider consumption guidance where published; callers must run its preflight and respect any local-only or managed-admission conditions.

6. Output

Markdown (TTY default)

# Plan check: reuse | extend | new

**Query:** <workplan title or intent text>

## 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:

{
  "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:

{"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.