REUSE-WP-0018 T01/T02/T04/T06: plan-check deterministic matching + State Hub bridge
Some checks failed
ci / validate-registry (push) Has been cancelled

T01: specs/PlanCheck.md design doc, plan-check-result.schema.json and
reuse-event.schema.json (the latter shared with WP-0019's reuse telemetry).

T02: reuse_surface/plan_check.py + 'reuse-surface plan-check' CLI command.
Deterministic token-Jaccard matching against registry/indexes/federated.yaml
(reuses overlaps.py's TOKEN_RE rather than a second scoring method), with
reuse/extend/new verdicts, markdown and --format json output, staleness
warning, and --record-outcome JSONL telemetry.

T04: reuse_surface/statehub_bridge.py bridges plan-check 'new' verdicts to
State Hub capability requests (--file-request) and surfaces open requests
with no matching capability (report gaps --check-capability-requests, opt-in
to stay offline-safe). Verified against the live local State Hub API;
status field (not catalog_entry_id presence) is the correct open/closed
signal, and the list endpoint needs a longer timeout than the health check
(~7s observed with 5 rows).

T06: docs (tools/README.md, RegistryFederation.md, SCOPE.md,
IntentScopeGapAnalysis.md priority 29) and an informational CI smoke step.

T03 (LLM rerank) not started -- llm-connect isn't running on this
workstation. T05 (ecosystem rollout) remains blocked: WP-0017 has drafted
entries for all 61 repos but they're still local-only pending its own T05
push/publish pass, so the federated index isn't yet worth rolling out
plan-check as ecosystem convention.

16 new tests, all mocked -- no network calls in the default test run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 00:57:18 +02:00
parent 2a6818d4fe
commit e72966a658
15 changed files with 1179 additions and 29 deletions

View File

@@ -20,6 +20,16 @@ from reuse_surface.hub_sync import (
write_sources_manifest,
)
from reuse_surface.overlaps import find_overlaps
from reuse_surface.statehub_bridge import list_open_capability_requests
from reuse_surface.plan_check import (
format_plan_check_json,
format_plan_check_markdown,
load_query_from_intent,
load_query_from_workplan,
maybe_file_capability_request,
record_outcome,
run_plan_check,
)
from reuse_surface.reports import (
cohort_filters_from_args,
collect_gap_report,
@@ -578,16 +588,85 @@ def cmd_report_cohorts(args: argparse.Namespace) -> int:
return 0
def cmd_plan_check(args: argparse.Namespace) -> int:
if args.workplan and args.intent:
print("error: pass either a workplan file or --intent, not both", file=sys.stderr)
return 1
if args.workplan:
path = Path(args.workplan).resolve()
if not path.exists():
print(f"error: workplan not found: {path}", file=sys.stderr)
return 1
query = load_query_from_workplan(path)
elif args.intent:
query = load_query_from_intent(args.intent)
else:
print("error: provide a workplan file or --intent", file=sys.stderr)
return 1
result = run_plan_check(
query,
reuse_threshold=args.reuse_threshold,
extend_threshold=args.extend_threshold,
)
if args.record_outcome:
record_outcome(result, args.record_outcome, consumer_repo=args.consumer_repo)
filed = None
if args.file_request and result["verdict"] == "new":
filed = maybe_file_capability_request(
result,
requesting_domain=args.requesting_domain,
requesting_agent=args.consumer_repo,
)
if args.format == "json":
if filed is not None:
result["filed_capability_request"] = filed
print(format_plan_check_json(result))
else:
print(format_plan_check_markdown(result), end="")
if args.file_request and result["verdict"] == "new":
if filed:
print(f"\nFiled State Hub capability request: {filed.get('id')}")
else:
print("\nState Hub unreachable — capability request not filed.")
return 0
def cmd_report_gaps(args: argparse.Namespace) -> int:
roster_path = Path(args.roster).resolve() if args.roster else default_roster_path()
if not roster_path.exists():
print(f"error: roster not found: {roster_path}", file=sys.stderr)
return 1
report = collect_gap_report(roster_path)
if args.check_capability_requests:
open_requests = list_open_capability_requests()
report["open_capability_requests"] = (
[
{"id": r["id"], "title": r["title"], "requesting_domain": r.get("requesting_domain_slug")}
for r in open_requests
]
if open_requests is not None
else None
)
if args.format == "json":
print(format_gap_json(report))
else:
print(format_gap_markdown(report), end="")
if args.check_capability_requests:
requests_ = report["open_capability_requests"]
print("\n## Open State Hub capability requests (no matching federated capability)\n")
if requests_ is None:
print("_State Hub unreachable — skipped._\n")
elif not requests_:
print("- none\n")
else:
for r in requests_:
print(f"- `{r['id']}` ({r['requesting_domain']}): {r['title']}")
return 0
@@ -697,6 +776,44 @@ def main(argv: list[str] | None = None) -> int:
)
overlaps.set_defaults(func=cmd_overlaps)
plan_check = subparsers.add_parser(
"plan-check",
help="match a draft workplan or intent against the federated capability index",
)
plan_check.add_argument(
"workplan", nargs="?", help="path to a workplan file (frontmatter + body)"
)
plan_check.add_argument("--intent", help="free-text intent instead of a workplan file")
plan_check.add_argument(
"--format", choices=["markdown", "json"], default="markdown"
)
plan_check.add_argument(
"--reuse-threshold", type=float, default=0.45,
help="score at or above which the verdict is 'reuse'",
)
plan_check.add_argument(
"--extend-threshold", type=float, default=0.22,
help="score at or above which the verdict is 'extend'",
)
plan_check.add_argument(
"--record-outcome",
choices=["reused", "extended", "new", "skipped"],
help="append this outcome to registry/telemetry/plan-check-events.jsonl",
)
plan_check.add_argument(
"--consumer-repo", default="reuse-surface",
help="repo slug recorded with --record-outcome and --file-request",
)
plan_check.add_argument(
"--file-request", action="store_true",
help="on a 'new' verdict, file a State Hub capability request for the gap",
)
plan_check.add_argument(
"--requesting-domain", default="infotech",
help="domain slug recorded on a filed capability request",
)
plan_check.set_defaults(func=cmd_plan_check)
catalog = subparsers.add_parser(
"catalog", help="generate human-readable capability catalog"
)
@@ -808,6 +925,12 @@ def main(argv: list[str] | None = None) -> int:
choices=["markdown", "json"],
default="markdown",
)
gaps.add_argument(
"--check-capability-requests",
action="store_true",
help="also list open State Hub capability requests with no matching "
"federated capability (requires State Hub reachable at 127.0.0.1:8000)",
)
gaps.set_defaults(func=cmd_report_gaps)
stats = subparsers.add_parser("stats", help="registry maturity and federation stats")

299
reuse_surface/plan_check.py Normal file
View File

@@ -0,0 +1,299 @@
from __future__ import annotations
import json
import re
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
import yaml
from reuse_surface.federation import FEDERATED_INDEX_PATH
from reuse_surface.overlaps import TOKEN_RE
from reuse_surface.registry import ROOT, load_index
from reuse_surface.statehub_bridge import file_capability_request
TELEMETRY_PATH = ROOT / "registry" / "telemetry" / "plan-check-events.jsonl"
STALE_DAYS = 14
DEFAULT_REUSE_THRESHOLD = 0.45
DEFAULT_EXTEND_THRESHOLD = 0.22
DEFAULT_TIE_WINDOW = 0.05
@dataclass
class MatchQuery:
source: str
text: str
workplan_id: str | None = None
workplan_path: str | None = None
tokens: set[str] = field(default_factory=set)
@dataclass
class Match:
id: str
score: float
kind: str
vector: str | None = None
owner: str | None = None
summary: str | None = None
def _tokens(text: str) -> set[str]:
return set(TOKEN_RE.findall(text.lower()))
def load_query_from_workplan(path: Path) -> MatchQuery:
text = path.read_text(encoding="utf-8")
match = re.match(r"^---\n(.*?)\n---\n?(.*)$", text, re.DOTALL)
if not match:
raise ValueError(f"{path}: missing YAML front matter")
front = yaml.safe_load(match.group(1)) or {}
body = match.group(2)
parts = [str(front.get("title") or front.get("id") or path.stem)]
for heading in ("Core Idea", "Problem statement", "One-liner"):
section = re.search(
rf"^##\s+{re.escape(heading)}\s*\n(.*?)(?:\n##\s|\Z)",
body,
re.DOTALL | re.MULTILINE,
)
if section:
parts.append(section.group(1).strip())
if len(parts) == 1:
intro = body.strip().split("\n\n", 1)[0]
parts.append(intro)
blob = "\n".join(p for p in parts if p)
return MatchQuery(
source="workplan",
text=blob,
workplan_id=front.get("id"),
workplan_path=str(path),
tokens=_tokens(blob),
)
def load_query_from_intent(intent: str) -> MatchQuery:
return MatchQuery(source="intent", text=intent, tokens=_tokens(intent))
def _federated_entry_blob(item: dict[str, Any]) -> str:
parts = [
item.get("name", ""),
item.get("summary", ""),
" ".join(item.get("tags", [])),
]
return " ".join(str(p) for p in parts if p)
def load_federated_capabilities() -> tuple[list[dict[str, Any]], str | None]:
"""Returns (capabilities, updated_date). Falls back to the local index
if the composed federated index doesn't exist yet."""
if FEDERATED_INDEX_PATH.exists():
data = yaml.safe_load(FEDERATED_INDEX_PATH.read_text(encoding="utf-8"))
return data.get("capabilities", []), data.get("updated")
data = load_index()
return data.get("capabilities", []), data.get("updated")
def _staleness_warning(updated: str | None) -> str | None:
if not updated:
return None
try:
updated_date = datetime.strptime(updated, "%Y-%m-%d").replace(
tzinfo=timezone.utc
)
except ValueError:
return None
age_days = (datetime.now(timezone.utc) - updated_date).days
if age_days > STALE_DAYS:
return f"federated index is {age_days} days old (last composed {updated})"
return None
def _vector_rank(vector: str | None) -> tuple[int, int]:
"""Higher discovery/availability levels rank first among near-tied scores."""
if not vector:
return (0, 0)
m = re.match(r"D(\d+)\s*/\s*A(\d+)", vector)
if not m:
return (0, 0)
return (int(m.group(1)), int(m.group(2)))
def match_query(
query: MatchQuery,
capabilities: list[dict[str, Any]],
*,
tie_window: float = DEFAULT_TIE_WINDOW,
) -> list[Match]:
if not query.tokens or not capabilities:
return []
scored: list[Match] = []
for item in capabilities:
blob = _federated_entry_blob(item)
tokens = _tokens(blob)
if not tokens:
continue
score = len(query.tokens & tokens) / len(query.tokens | tokens)
if score <= 0:
continue
scored.append(
Match(
id=item["id"],
score=round(score, 4),
kind="deterministic",
vector=item.get("vector"),
owner=item.get("owner"),
summary=item.get("summary"),
)
)
if not scored:
return []
scored.sort(key=lambda m: m.score, reverse=True)
top_score = scored[0].score
tied = [m for m in scored if top_score - m.score <= tie_window]
rest = [m for m in scored if top_score - m.score > tie_window]
tied.sort(key=lambda m: (_vector_rank(m.vector), m.score), reverse=True)
return tied + rest
def verdict_for_score(
top_score: float,
*,
reuse_threshold: float = DEFAULT_REUSE_THRESHOLD,
extend_threshold: float = DEFAULT_EXTEND_THRESHOLD,
) -> str:
if top_score >= reuse_threshold:
return "reuse"
if top_score >= extend_threshold:
return "extend"
return "new"
def run_plan_check(
query: MatchQuery,
*,
reuse_threshold: float = DEFAULT_REUSE_THRESHOLD,
extend_threshold: float = DEFAULT_EXTEND_THRESHOLD,
tie_window: float = DEFAULT_TIE_WINDOW,
top_n: int = 5,
) -> dict[str, Any]:
capabilities, updated = load_federated_capabilities()
matches = match_query(query, capabilities, tie_window=tie_window)
top_score = matches[0].score if matches else 0.0
verdict = verdict_for_score(
top_score, reuse_threshold=reuse_threshold, extend_threshold=extend_threshold
)
return {
"query": {
"source": query.source,
"text": query.text,
"workplan_id": query.workplan_id,
"workplan_path": query.workplan_path,
},
"verdict": verdict,
"top_score": top_score,
"matches": [
{
"id": m.id,
"score": m.score,
"vector": m.vector,
"owner": m.owner,
"summary": m.summary,
"kind": m.kind,
}
for m in matches[:top_n]
],
"federated_index_updated": updated,
"federated_index_stale_warning": _staleness_warning(updated),
}
def format_plan_check_markdown(result: dict[str, Any]) -> str:
lines = [f"# Plan check: {result['verdict']}", ""]
query = result["query"]
label = query.get("workplan_id") or query["text"][:80]
lines.append(f"**Query ({query['source']}):** {label}")
lines.append("")
matches = result.get("matches", [])
if matches:
lines.append("## Matches")
for m in matches:
vec = f" ({m['vector']})" if m.get("vector") else ""
owner = f"{m['owner']}" if m.get("owner") else ""
lines.append(f"- `{m['id']}`{vec}{owner} — score {m['score']:.2f} [{m['kind']}]")
if m.get("summary"):
lines.append(f" > {m['summary']}")
else:
lines.append("_No matches found in the federated index._")
lines.append("")
verdict = result["verdict"]
if verdict == "reuse":
lines.append("**Verdict: REUSE** — an existing capability already covers this; link it instead of building new.")
elif verdict == "extend":
lines.append("**Verdict: EXTEND** — scope overlaps closely enough that extending the top match is likely cheaper than a new capability.")
else:
lines.append("**Verdict: NEW** — no close match in the federated index; proceed.")
warning = result.get("federated_index_stale_warning")
if warning:
lines.append("")
lines.append(f"{warning}")
return "\n".join(lines) + "\n"
def format_plan_check_json(result: dict[str, Any]) -> str:
return json.dumps(result, indent=2, sort_keys=True)
def maybe_file_capability_request(
result: dict[str, Any],
*,
requesting_domain: str,
requesting_agent: str,
) -> dict[str, Any] | None:
"""On a 'new' verdict, file a State Hub capability request for the gap.
Returns None (and does nothing) for any other verdict, or if the hub is
unreachable -- this never blocks plan-check's primary output."""
if result["verdict"] != "new":
return None
query = result["query"]
title = (query.get("workplan_id") or query["text"])[:120]
return file_capability_request(
title=f"plan-check gap: {title}",
description=query["text"],
requesting_domain=requesting_domain,
requesting_agent=requesting_agent,
requesting_workplan_id=query.get("workplan_id"),
)
def record_outcome(
result: dict[str, Any],
outcome: str,
*,
consumer_repo: str = "reuse-surface",
) -> Path:
TELEMETRY_PATH.parent.mkdir(parents=True, exist_ok=True)
top_match = result["matches"][0]["id"] if result.get("matches") else None
event = {
"ts": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"consumer_repo": consumer_repo,
"capability_id": top_match,
"verdict": result["verdict"],
"outcome": outcome,
"source": "plan-check",
}
with TELEMETRY_PATH.open("a", encoding="utf-8") as handle:
handle.write(json.dumps(event, sort_keys=True) + "\n")
return TELEMETRY_PATH

View File

@@ -0,0 +1,103 @@
from __future__ import annotations
import json
import urllib.error
import urllib.parse
import urllib.request
from typing import Any
DEFAULT_BASE_URL = "http://127.0.0.1:8000"
TIMEOUT_SECONDS = 5
LIST_TIMEOUT_SECONDS = 20
# Observed status vocabulary (2026-07): "requested", "completed". No fixed
# enum is published by the API (free-form string field) -- treat anything
# not in this terminal set as still-open rather than assuming a closed list.
TERMINAL_STATUSES = {"completed", "cancelled", "rejected", "resolved", "closed"}
def _request(
method: str,
path: str,
base_url: str,
*,
body: dict[str, Any] | None = None,
params: dict[str, str] | None = None,
timeout: int = TIMEOUT_SECONDS,
) -> dict[str, Any] | list[Any]:
url = f"{base_url.rstrip('/')}{path}"
if params:
query = "&".join(
f"{k}={urllib.parse.quote(v)}" for k, v in params.items() if v
)
if query:
url = f"{url}?{query}"
data = json.dumps(body).encode("utf-8") if body is not None else None
request = urllib.request.Request(
url,
data=data,
method=method,
headers={"Content-Type": "application/json", "User-Agent": "reuse-surface/0.1"},
)
with urllib.request.urlopen(request, timeout=timeout) as response:
return json.loads(response.read().decode("utf-8"))
def state_hub_reachable(base_url: str = DEFAULT_BASE_URL) -> bool:
try:
_request("GET", "/state/health", base_url)
return True
except (urllib.error.URLError, TimeoutError, OSError):
return False
def file_capability_request(
*,
title: str,
description: str,
requesting_domain: str,
requesting_agent: str,
capability_type: str = "reuse-surface-new-verdict",
requesting_workplan_id: str | None = None,
base_url: str = DEFAULT_BASE_URL,
) -> dict[str, Any] | None:
"""Files a State Hub capability request for a plan-check 'new' verdict.
Returns the created request record, or None if the hub is unreachable
(degrades gracefully -- plan-check never blocks on this)."""
if not state_hub_reachable(base_url):
return None
body = {
"title": title,
"description": description,
"capability_type": capability_type,
"requesting_domain": requesting_domain,
"requesting_agent": requesting_agent,
"requesting_workplan_id": requesting_workplan_id,
}
try:
return _request("POST", "/capability-requests/", base_url, body=body)
except (urllib.error.URLError, TimeoutError, OSError):
return None
def list_open_capability_requests(
base_url: str = DEFAULT_BASE_URL,
) -> list[dict[str, Any]] | None:
"""Returns capability requests whose status is not terminal (see
TERMINAL_STATUSES), or None if the hub is unreachable. A request can
carry a catalog_entry_id while still 'requested' (routed, not
fulfilled), so status -- not catalog_entry_id presence -- is the open
signal. The list endpoint can be slow (observed ~7s with a handful of
rows); a longer timeout than the health check is used deliberately."""
if not state_hub_reachable(base_url):
return None
try:
requests_ = _request(
"GET", "/capability-requests/", base_url, timeout=LIST_TIMEOUT_SECONDS
)
except (urllib.error.URLError, TimeoutError, OSError):
return None
if not isinstance(requests_, list):
return []
return [r for r in requests_ if r.get("status") not in TERMINAL_STATUSES]