generated from coulomb/repo-seed
Coevolution extension
This commit is contained in:
17
tests/expectations/llm_connect_provider_expectations.json
Normal file
17
tests/expectations/llm_connect_provider_expectations.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"repository": "llm-connect-like",
|
||||
"expected_facts": [
|
||||
{"kind": "llm_provider", "name": "OpenRouter"},
|
||||
{"kind": "llm_provider", "name": "Claude"},
|
||||
{"kind": "credential_config", "name": "OpenRouter API key"},
|
||||
{"kind": "provider_registry", "name": "LLM provider registry"},
|
||||
{"kind": "fallback_policy", "name": "LLM provider fallback policy"}
|
||||
],
|
||||
"expected_candidates": [
|
||||
"Route LLM Requests Across Providers",
|
||||
"Use OpenRouter Models",
|
||||
"Use Claude Models",
|
||||
"Configure LLM Provider Credentials",
|
||||
"Apply LLM Provider Fallback Policy"
|
||||
]
|
||||
}
|
||||
@@ -246,3 +246,35 @@ def test_candidate_generator_groups_many_interface_facts_into_behavioral_feature
|
||||
assert feature.type == "API"
|
||||
assert feature.location == "src/api.py"
|
||||
assert len(feature.source_refs) == 3
|
||||
|
||||
|
||||
def test_candidate_generator_maps_llm_provider_facts_to_capability():
|
||||
repository = Repository(
|
||||
id=1,
|
||||
name="LLMConnect",
|
||||
url="/tmp/llm-connect",
|
||||
description=None,
|
||||
branch="main",
|
||||
status="analyzed",
|
||||
)
|
||||
facts = [
|
||||
fact(1, "documentation", "README", "README.md"),
|
||||
fact(2, "llm_provider", "OpenRouter", "providers.py", "openrouter"),
|
||||
fact(3, "llm_provider", "Claude", "providers.py", "claude"),
|
||||
fact(4, "credential_config", "OpenRouter API key", ".env.example", "OPENROUTER_API_KEY"),
|
||||
fact(5, "provider_registry", "LLM provider registry", "providers.py"),
|
||||
fact(6, "fallback_policy", "LLM provider fallback policy", "providers.py"),
|
||||
]
|
||||
|
||||
graph = CandidateGraphGenerator().generate(repository, facts)
|
||||
|
||||
capability = next(
|
||||
capability
|
||||
for capability in graph[0].capabilities
|
||||
if capability.name == "Route LLM Requests Across Providers"
|
||||
)
|
||||
feature_names = {feature.name for feature in capability.features}
|
||||
assert {"Use OpenRouter Models", "Use Claude Models"} <= feature_names
|
||||
assert "Configure LLM Provider Credentials" in feature_names
|
||||
assert "Maintain LLM Provider Registry" in feature_names
|
||||
assert "Apply LLM Provider Fallback Policy" in feature_names
|
||||
|
||||
@@ -65,3 +65,20 @@ def test_content_extractor_ignores_unindexed_and_missing_paths(tmp_path):
|
||||
)
|
||||
|
||||
assert chunks == []
|
||||
|
||||
|
||||
def test_content_extractor_chunks_provider_related_config(tmp_path):
|
||||
repo = tmp_path / "repo"
|
||||
repo.mkdir()
|
||||
(repo / ".env.example").write_text("OPENROUTER_API_KEY=\n", encoding="utf-8")
|
||||
|
||||
chunks = ContentExtractor().extract(
|
||||
repo,
|
||||
[
|
||||
fact(1, "credential_config", "OpenRouter API key", ".env.example"),
|
||||
],
|
||||
)
|
||||
|
||||
assert len(chunks) == 1
|
||||
assert chunks[0].path == ".env.example"
|
||||
assert "OPENROUTER_API_KEY" in chunks[0].text
|
||||
|
||||
@@ -87,3 +87,32 @@ def test_scanner_javascript_typescript_package_records_package_facts(tmp_path):
|
||||
assert ("framework", "Vitest", "package.json") in facts
|
||||
assert ("interface", "possible API surface", "src/api/routes.ts") in facts
|
||||
assert ("test", "routes.spec.ts", "src/api/routes.spec.ts") in facts
|
||||
|
||||
|
||||
def test_scanner_records_llm_provider_and_fallback_facts(tmp_path):
|
||||
repo = tmp_path / "llm-connect-like"
|
||||
repo.mkdir()
|
||||
(repo / "README.md").write_text(
|
||||
"# LLM Connect\nSupports OpenRouter and Claude fallback.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(repo / ".env.example").write_text(
|
||||
"OPENROUTER_API_KEY=\nANTHROPIC_API_KEY=\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(repo / "providers.py").write_text(
|
||||
"provider_registry = {'openrouter': OpenRouterAdapter, 'anthropic': ClaudeAdapter}\n"
|
||||
"fallback_provider = 'claude'\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = DeterministicScanner().scan(repo)
|
||||
|
||||
facts = {(fact.kind, fact.name, fact.path) for fact in result.facts}
|
||||
assert ("llm_provider", "OpenRouter", "README.md") in facts
|
||||
assert ("llm_provider", "Claude", "README.md") in facts
|
||||
assert ("llm_provider", "Anthropic", ".env.example") in facts
|
||||
assert ("credential_config", "OpenRouter API key", ".env.example") in facts
|
||||
assert ("credential_config", "Anthropic API key", ".env.example") in facts
|
||||
assert ("provider_registry", "LLM provider registry", "providers.py") in facts
|
||||
assert ("fallback_policy", "LLM provider fallback policy", "README.md") in facts
|
||||
|
||||
65
tests/test_scanner_coevolution.py
Normal file
65
tests/test_scanner_coevolution.py
Normal file
@@ -0,0 +1,65 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from repo_registry.core.service import RegistryService
|
||||
from repo_registry.repo_ingestion.git import GitIngestionService
|
||||
from repo_registry.storage.sqlite import RegistryStore
|
||||
|
||||
|
||||
def test_llm_connect_provider_expectations_are_detected_without_llm(tmp_path):
|
||||
expectation_path = (
|
||||
Path(__file__).parent
|
||||
/ "expectations"
|
||||
/ "llm_connect_provider_expectations.json"
|
||||
)
|
||||
expectations = json.loads(expectation_path.read_text(encoding="utf-8"))
|
||||
source = tmp_path / expectations["repository"]
|
||||
source.mkdir()
|
||||
(source / "README.md").write_text(
|
||||
"# LLM Connect\nSupports OpenRouter and Claude fallback for hard prompts.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(source / ".env.example").write_text(
|
||||
"OPENROUTER_API_KEY=\nANTHROPIC_API_KEY=\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(source / "providers.py").write_text(
|
||||
"provider_registry = {'openrouter': object(), 'claude': object()}\n"
|
||||
"fallback_provider = 'claude'\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
store = RegistryStore(tmp_path / "registry.sqlite3")
|
||||
store.initialize()
|
||||
service = RegistryService(
|
||||
store,
|
||||
ingestion=GitIngestionService(tmp_path / "checkouts"),
|
||||
)
|
||||
repository = service.register_repository(name="LLM Connect", url=str(source))
|
||||
|
||||
summary = service.analyze_repository(
|
||||
repository.id,
|
||||
use_llm_assistance=False,
|
||||
)
|
||||
facts = service.list_observed_facts(repository.id, summary.analysis_run.id)
|
||||
graph = service.candidate_graph(repository.id, summary.analysis_run.id)
|
||||
|
||||
fact_pairs = {(fact.kind, fact.name) for fact in facts}
|
||||
for expected in expectations["expected_facts"]:
|
||||
assert (expected["kind"], expected["name"]) in fact_pairs
|
||||
|
||||
candidate_names = {
|
||||
graph.abilities[0].name,
|
||||
*[
|
||||
capability.name
|
||||
for ability in graph.abilities
|
||||
for capability in ability.capabilities
|
||||
],
|
||||
*[
|
||||
feature.name
|
||||
for ability in graph.abilities
|
||||
for capability in ability.capabilities
|
||||
for feature in capability.features
|
||||
],
|
||||
}
|
||||
for expected in expectations["expected_candidates"]:
|
||||
assert expected in candidate_names
|
||||
@@ -35,6 +35,7 @@ def test_initialize_is_idempotent_and_applies_expected_columns(tmp_path):
|
||||
assert "source_refs" in feature_columns
|
||||
assert "source_refs" in evidence_columns
|
||||
assert "content_chunks" in tables
|
||||
assert "expectation_gaps" in tables
|
||||
|
||||
|
||||
def test_approved_registry_schema_allows_future_nullable_vocabulary_ref(tmp_path):
|
||||
@@ -132,6 +133,13 @@ def test_delete_repository_cascades_registry_and_review_rows(tmp_path):
|
||||
action="manual_test",
|
||||
notes="Cascade review decision.",
|
||||
)
|
||||
service.store.create_expectation_gap(
|
||||
repository.id,
|
||||
run.id,
|
||||
expected_type="capability",
|
||||
expected_name="Expected Cascade Capability",
|
||||
source="human",
|
||||
)
|
||||
|
||||
service.delete_repository(repository.id)
|
||||
|
||||
@@ -143,6 +151,7 @@ def test_delete_repository_cascades_registry_and_review_rows(tmp_path):
|
||||
"approved_evidence",
|
||||
"analysis_runs",
|
||||
"content_chunks",
|
||||
"expectation_gaps",
|
||||
"review_decisions",
|
||||
):
|
||||
count = connection.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0]
|
||||
|
||||
@@ -252,6 +252,10 @@ def test_openapi_contract_snapshot_for_stable_agent_paths():
|
||||
"/repos/{repository_id}/export": {
|
||||
"get": {"tags": ["discovery"], "success_schema": "application/x-yaml"}
|
||||
},
|
||||
"/repos/{repository_id}/expectation-gaps": {
|
||||
"get": {"tags": ["review"], "success_schema": "list[ExpectationGapResponse]"},
|
||||
"post": {"tags": ["review"], "success_schema": "ExpectationGapResponse"},
|
||||
},
|
||||
"/repos/{repository_id}/features": {
|
||||
"post": {"tags": ["registry"], "success_schema": "IdResponse"}
|
||||
},
|
||||
@@ -759,6 +763,24 @@ def test_api_analysis_run_loop(tmp_path):
|
||||
assert run_decisions_response.json()[0]["notes"] == (
|
||||
"Reject once to exercise review correction."
|
||||
)
|
||||
gap_response = client.post(
|
||||
f"/repos/{repository_id}/expectation-gaps",
|
||||
json={
|
||||
"analysis_run_id": run["analysis_run"]["id"],
|
||||
"expected_type": "capability",
|
||||
"expected_name": "Use OpenRouter Models",
|
||||
"source": "human",
|
||||
"notes": "Expected provider capability was missing.",
|
||||
},
|
||||
)
|
||||
assert gap_response.status_code == 201
|
||||
assert gap_response.json()["expected_name"] == "Use OpenRouter Models"
|
||||
gaps_response = client.get(
|
||||
f"/repos/{repository_id}/expectation-gaps",
|
||||
params={"analysis_run_id": run["analysis_run"]["id"]},
|
||||
)
|
||||
assert gaps_response.status_code == 200
|
||||
assert gaps_response.json()[0]["source"] == "human"
|
||||
|
||||
run_response = client.post(f"/repos/{repository_id}/analysis-runs", json={})
|
||||
assert run_response.status_code == 201
|
||||
@@ -1154,6 +1176,23 @@ def test_ui_register_analyze_and_approve_loop(tmp_path):
|
||||
assert "README.md:1-2" in run_detail.text
|
||||
assert "ID " in run_detail.text
|
||||
assert "No review decisions yet." in run_detail.text
|
||||
assert "Expectation Gaps" in run_detail.text
|
||||
assert "Record Gap" in run_detail.text
|
||||
|
||||
gap_response = client.post(
|
||||
f"{run_path}/expectation-gaps",
|
||||
data={
|
||||
"expected_type": "capability",
|
||||
"expected_name": "Use OpenRouter Models",
|
||||
"source": "human",
|
||||
"notes": "Expected from provider docs.",
|
||||
},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert gap_response.status_code == 303
|
||||
run_detail = client.get(run_path)
|
||||
assert "Use OpenRouter Models" in run_detail.text
|
||||
assert "Expected from provider docs." in run_detail.text
|
||||
|
||||
approve_response = client.post(
|
||||
f"{run_path}/candidate-graph/approve",
|
||||
|
||||
Reference in New Issue
Block a user