This repository has been archived on 2026-07-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
reuse-surface/reuse_surface/maintain_llm.py
tegwick 00b7eab154
Some checks failed
ci / validate-registry (push) Has been cancelled
REUSE-WP-0019-T01: forge host abstraction + URL migration inventory
reuse_surface/forge_host.py: parse/derive/rewrite raw index URLs across
Gitea and Forgejo (handles both the legacy /raw/<branch>/... form and the
canonical /raw/branch/<branch>/... form both forges serve without a 303
redirect). migrate_source_host() verifies the new URL resolves via HTTP
HEAD before writing -- refuses to point a repo at a host it hasn't
actually migrated to.

New CLI: reuse-surface federation migrate-host --repo <slug> --to
<base-url> [--from <check>] [--dry-run] [--no-verify] [--update-hub].

Inventory: cross-referenced sources.yaml against each repo's actual git
origin. 11/61 repos already on Forgejo; found 2 with stale sources.yaml
entries (activity-core, state-hub) despite having migrated. Fixed for
real: local sources.yaml + production hub registration (hub update),
verified against GET /v1/federated post-migration. config-atlas's
WP-0017-T06 303 confirmed NOT a host-transition symptom (already diagnosed
there as something else).

Also fixed two host-agnostic gaps found while inventorying:
registry_update.py and maintain_llm.py only recognized .gitea/workflows/,
missing repos already on .forgejo/workflows/. Fixed a stale copy-paste
example (state-hub's now-wrong old URL) in docs/RegistryFederation.md, and
a pre-existing unrelated port typo (8088 vs the real llm-connect default
8080) in tools/README.md and registry/README.md.

17 new pytest cases (tests/test_forge_host.py), 106 total pass.
Recomposed federated.yaml post-migration: still 61 capabilities.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:14:30 +02:00

161 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import json
import subprocess
import textwrap
from pathlib import Path
from typing import Any
import yaml
from jsonschema import Draft202012Validator
from reuse_surface.llm_bridge import execute_prompt, extract_json_object
from reuse_surface.registry import ROOT, load_index_at, parse_front_matter, registry_paths
PATCH_SCHEMA_PATH = ROOT / "schemas" / "registry-patch.schema.json"
MATURITY_SUMMARY = """
| Dimension | Levels | Question |
|---|---|---|
| discovery | D0D7 | Planning/orientation reuse strength |
| availability | A0A7 | Consumption mode and delivery artifacts |
| completeness | C0C6 | Scope vs intent and expectations |
| reliability | R0R6 | Consumer quality signals |
Promotion rules:
- Cite repo-relative evidence paths for every maturity_promote patch.
- Prefer single-step promotions (one level per dimension).
- Do not invent files; only cite paths visible in git diff or context.
"""
def load_patch_schema() -> dict[str, Any]:
return json.loads(PATCH_SCHEMA_PATH.read_text(encoding="utf-8"))
def _git_diff(repo_root: Path, git_since: str | None) -> str:
if not git_since:
return ""
proc = subprocess.run(
[
"git",
"-C",
str(repo_root),
"diff",
git_since,
"HEAD",
"--",
"registry/",
"reuse_surface/",
"tests/",
"docs/",
".gitea/",
".forgejo/",
"pyproject.toml",
],
capture_output=True,
text=True,
check=False,
)
return proc.stdout[:12000]
def build_maintain_prompt(
repo_root: Path,
capability_id: str,
*,
git_since: str | None = None,
context_files: list[str] | None = None,
) -> str:
paths = registry_paths(repo_root)
index = load_index_at(paths["index"])
row = next((item for item in index["capabilities"] if item["id"] == capability_id), None)
if not row:
raise ValueError(f"capability not in index: {capability_id}")
entry = parse_front_matter(repo_root / row["path"])
diff = _git_diff(repo_root, git_since)
context_chunks: list[str] = []
for rel in context_files or []:
path = repo_root / rel
if path.is_file():
context_chunks.append(f"### {rel}\n{path.read_text(encoding='utf-8')[:4000]}")
schema_hint = json.dumps(
{
"patches": [
{
"capability_id": capability_id,
"kind": "maturity_promote",
"confidence": "medium",
"rationale": "CI gate added",
"dimension": "reliability",
"from_level": "R2",
"to_level": "R3",
"evidence_citations": ["tests/test_example.py"],
"promotion_history_entry": {
"date": "2026-06-16",
"dimension": "reliability",
"from": "R2",
"to": "R3",
"rationale": "pytest coverage for consumption path",
},
}
],
"notes": ["optional human review items"],
},
indent=2,
)
return textwrap.dedent(
f"""
Propose structured registry maintenance patches for `{capability_id}`.
Return ONLY JSON matching this shape (no markdown fences):
{schema_hint}
Allowed patch kinds: vector_sync, evidence_append, artifact_append,
maturity_promote, consumer_feedback, relation_add, index_row_add,
index_updated_bump.
Maturity reference:
{MATURITY_SUMMARY}
Current entry YAML:
{yaml.safe_dump(entry, sort_keys=False)}
Git diff since {git_since or 'N/A'}:
{diff or '(none)'}
Context files:
{chr(10).join(context_chunks) if context_chunks else '(none)'}
"""
).strip()
def request_maintain_patches(
repo_root: Path,
capability_id: str,
*,
git_since: str | None = None,
context_files: list[str] | None = None,
llm_url: str | None = None,
) -> dict[str, Any]:
prompt = build_maintain_prompt(
repo_root,
capability_id,
git_since=git_since,
context_files=context_files,
)
content = execute_prompt(
prompt,
base_url=llm_url,
config={"temperature": 0.2, "max_tokens": 3000},
)
payload = extract_json_object(content)
validator = Draft202012Validator(load_patch_schema())
errors = sorted(validator.iter_errors(payload), key=lambda err: list(err.path))
if errors:
messages = "; ".join(error.message for error in errors[:3])
raise ValueError(f"patch schema validation failed: {messages}")
return payload