generated from coulomb/repo-seed
profile drill-down/source-link preservation
This commit is contained in:
@@ -135,6 +135,7 @@ CREATE TABLE IF NOT EXISTS approved_features (
|
||||
type TEXT NOT NULL,
|
||||
location TEXT NOT NULL DEFAULT '',
|
||||
confidence REAL NOT NULL DEFAULT 1.0,
|
||||
source_refs TEXT NOT NULL DEFAULT '[]',
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
@@ -145,6 +146,7 @@ CREATE TABLE IF NOT EXISTS approved_evidence (
|
||||
type TEXT NOT NULL,
|
||||
reference TEXT NOT NULL,
|
||||
strength TEXT NOT NULL DEFAULT 'medium',
|
||||
source_refs TEXT NOT NULL DEFAULT '[]',
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
|
||||
@@ -189,6 +189,7 @@ class CandidateGraphGenerator:
|
||||
path=fact.path,
|
||||
kind=fact.kind,
|
||||
name=fact.name,
|
||||
line=fact.metadata.get("line"),
|
||||
)
|
||||
for fact in facts
|
||||
]
|
||||
|
||||
@@ -62,6 +62,7 @@ class SourceReference:
|
||||
path: str
|
||||
kind: str
|
||||
name: str
|
||||
line: int | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -123,6 +124,7 @@ class Evidence:
|
||||
type: str
|
||||
reference: str
|
||||
strength: str
|
||||
source_refs: list[SourceReference] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -132,6 +134,7 @@ class Feature:
|
||||
type: str
|
||||
location: str
|
||||
confidence: float
|
||||
source_refs: list[SourceReference] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
@@ -162,6 +162,7 @@ class RegistryService:
|
||||
type=feature.type,
|
||||
location=feature.location,
|
||||
confidence=feature.confidence,
|
||||
source_refs=feature.source_refs,
|
||||
)
|
||||
for evidence in capability.evidence:
|
||||
if evidence.status != "candidate":
|
||||
@@ -172,6 +173,7 @@ class RegistryService:
|
||||
type=evidence.type,
|
||||
reference=evidence.reference,
|
||||
strength=evidence.strength,
|
||||
source_refs=evidence.source_refs,
|
||||
)
|
||||
|
||||
if pending_abilities:
|
||||
|
||||
@@ -40,6 +40,7 @@ class RegistryStore:
|
||||
migration_path = Path(__file__).parents[3] / "migrations" / "0001_initial.sql"
|
||||
with self.connect() as connection:
|
||||
connection.executescript(migration_path.read_text(encoding="utf-8"))
|
||||
self._ensure_approved_source_ref_columns(connection)
|
||||
|
||||
def connect(self) -> sqlite3.Connection:
|
||||
connection = sqlite3.connect(self.database_path)
|
||||
@@ -47,6 +48,20 @@ class RegistryStore:
|
||||
connection.execute("PRAGMA foreign_keys = ON")
|
||||
return connection
|
||||
|
||||
def _ensure_approved_source_ref_columns(
|
||||
self,
|
||||
connection: sqlite3.Connection,
|
||||
) -> None:
|
||||
for table in ("approved_features", "approved_evidence"):
|
||||
columns = {
|
||||
row["name"]
|
||||
for row in connection.execute(f"PRAGMA table_info({table})").fetchall()
|
||||
}
|
||||
if "source_refs" not in columns:
|
||||
connection.execute(
|
||||
f"ALTER TABLE {table} ADD COLUMN source_refs TEXT NOT NULL DEFAULT '[]'"
|
||||
)
|
||||
|
||||
def create_repository(
|
||||
self,
|
||||
*,
|
||||
@@ -1153,15 +1168,24 @@ class RegistryStore:
|
||||
type: str,
|
||||
location: str,
|
||||
confidence: float,
|
||||
source_refs: list[SourceReference] | None = None,
|
||||
) -> int:
|
||||
with self.connect() as connection:
|
||||
cursor = connection.execute(
|
||||
"""
|
||||
INSERT INTO approved_features
|
||||
(repository_id, capability_id, name, type, location, confidence)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
(repository_id, capability_id, name, type, location, confidence, source_refs)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(repository_id, capability_id, name, type, location, confidence),
|
||||
(
|
||||
repository_id,
|
||||
capability_id,
|
||||
name,
|
||||
type,
|
||||
location,
|
||||
confidence,
|
||||
self._source_refs_to_json(source_refs or []),
|
||||
),
|
||||
)
|
||||
return int(cursor.lastrowid)
|
||||
|
||||
@@ -1173,15 +1197,23 @@ class RegistryStore:
|
||||
type: str,
|
||||
reference: str,
|
||||
strength: str,
|
||||
source_refs: list[SourceReference] | None = None,
|
||||
) -> int:
|
||||
with self.connect() as connection:
|
||||
cursor = connection.execute(
|
||||
"""
|
||||
INSERT INTO approved_evidence
|
||||
(repository_id, capability_id, type, reference, strength)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
(repository_id, capability_id, type, reference, strength, source_refs)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(repository_id, capability_id, type, reference, strength),
|
||||
(
|
||||
repository_id,
|
||||
capability_id,
|
||||
type,
|
||||
reference,
|
||||
strength,
|
||||
self._source_refs_to_json(source_refs or []),
|
||||
),
|
||||
)
|
||||
return int(cursor.lastrowid)
|
||||
|
||||
@@ -1208,7 +1240,7 @@ class RegistryStore:
|
||||
).fetchall()
|
||||
feature_rows = connection.execute(
|
||||
"""
|
||||
SELECT id, capability_id, name, type, location, confidence
|
||||
SELECT id, capability_id, name, type, location, confidence, source_refs
|
||||
FROM approved_features
|
||||
WHERE repository_id = ?
|
||||
ORDER BY id
|
||||
@@ -1217,7 +1249,7 @@ class RegistryStore:
|
||||
).fetchall()
|
||||
evidence_rows = connection.execute(
|
||||
"""
|
||||
SELECT id, capability_id, type, reference, strength
|
||||
SELECT id, capability_id, type, reference, strength, source_refs
|
||||
FROM approved_evidence
|
||||
WHERE repository_id = ?
|
||||
ORDER BY id
|
||||
@@ -1234,6 +1266,7 @@ class RegistryStore:
|
||||
type=row["type"],
|
||||
location=row["location"],
|
||||
confidence=row["confidence"],
|
||||
source_refs=self._source_refs_from_json(row["source_refs"]),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1245,6 +1278,7 @@ class RegistryStore:
|
||||
type=row["type"],
|
||||
reference=row["reference"],
|
||||
strength=row["strength"],
|
||||
source_refs=self._source_refs_from_json(row["source_refs"]),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1608,6 +1642,7 @@ class RegistryStore:
|
||||
"path": source_ref.path,
|
||||
"kind": source_ref.kind,
|
||||
"name": source_ref.name,
|
||||
"line": source_ref.line,
|
||||
}
|
||||
for source_ref in source_refs
|
||||
]
|
||||
@@ -1620,6 +1655,7 @@ class RegistryStore:
|
||||
path=item.get("path", ""),
|
||||
kind=item.get("kind", ""),
|
||||
name=item.get("name", ""),
|
||||
line=item.get("line"),
|
||||
)
|
||||
for item in json.loads(value)
|
||||
]
|
||||
|
||||
@@ -927,11 +927,11 @@ def render_ability_map(ability_map: dict) -> str:
|
||||
capabilities = []
|
||||
for capability in ability["capabilities"]:
|
||||
features = "".join(
|
||||
f'<li>{escape(feature["name"])} <span class="pill">{escape(feature["type"])}</span> <span class="source">{escape(feature["location"])}</span></li>'
|
||||
render_approved_feature(feature)
|
||||
for feature in capability["features"]
|
||||
)
|
||||
evidence = "".join(
|
||||
f'<li>{escape(item["type"])} <span class="pill">{escape(item["strength"])}</span> <span class="source">{escape(item["reference"])}</span></li>'
|
||||
render_approved_evidence(item)
|
||||
for item in capability["evidence"]
|
||||
)
|
||||
capabilities.append(
|
||||
@@ -955,6 +955,28 @@ def render_ability_map(ability_map: dict) -> str:
|
||||
return f'<div class="tree"><ul>{"".join(items)}</ul></div>'
|
||||
|
||||
|
||||
def render_approved_feature(feature: dict) -> str:
|
||||
return f"""
|
||||
<li>
|
||||
{escape(feature["name"])}
|
||||
<span class="pill">{escape(feature["type"])}</span>
|
||||
<span class="source">{escape(feature["location"])}</span>
|
||||
{render_sources(feature.get("source_refs", []))}
|
||||
</li>
|
||||
"""
|
||||
|
||||
|
||||
def render_approved_evidence(evidence: dict) -> str:
|
||||
return f"""
|
||||
<li>
|
||||
{escape(evidence["type"])}
|
||||
<span class="pill">{escape(evidence["strength"])}</span>
|
||||
<span class="source">{escape(evidence["reference"])}</span>
|
||||
{render_sources(evidence.get("source_refs", []))}
|
||||
</li>
|
||||
"""
|
||||
|
||||
|
||||
def search_result_href(result: dict) -> str:
|
||||
href = f"/ui/repos/{result['repository_id']}"
|
||||
if result.get("capability_id"):
|
||||
@@ -968,7 +990,7 @@ def render_sources(source_refs: list[dict]) -> str:
|
||||
if not source_refs:
|
||||
return ""
|
||||
sources = ", ".join(
|
||||
f'<span class="source">{escape(ref["kind"])}:{escape(ref["path"] or ref["name"])}</span>'
|
||||
f'<span class="source">{escape(ref["kind"])}:{escape(source_ref_label(ref))}</span>'
|
||||
for ref in source_refs[:5]
|
||||
)
|
||||
if len(source_refs) > 5:
|
||||
@@ -976,6 +998,13 @@ def render_sources(source_refs: list[dict]) -> str:
|
||||
return f"<p>{sources}</p>"
|
||||
|
||||
|
||||
def source_ref_label(ref: dict) -> str:
|
||||
label = ref["path"] or ref["name"]
|
||||
if ref.get("line"):
|
||||
label = f"{label}:{ref['line']}"
|
||||
return label
|
||||
|
||||
|
||||
def render_search_context(result: dict) -> str:
|
||||
details = []
|
||||
if result.get("ability_name"):
|
||||
|
||||
@@ -297,6 +297,9 @@ def test_approve_candidate_graph_publishes_ability_map_once(tmp_path):
|
||||
assert len(second_approval.abilities) == 1
|
||||
assert ability_map.abilities[0].name == "Review Example Repository Usefulness"
|
||||
assert ability_map.abilities[0].capabilities[0].features[0].location == "app.py"
|
||||
assert ability_map.abilities[0].capabilities[0].features[0].source_refs
|
||||
assert ability_map.abilities[0].capabilities[0].features[0].source_refs[0].line == 3
|
||||
assert ability_map.abilities[0].capabilities[0].evidence[0].source_refs
|
||||
|
||||
candidate_graph = service.candidate_graph(repository.id, summary.analysis_run.id)
|
||||
assert candidate_graph.abilities[0].status == "approved"
|
||||
|
||||
@@ -315,6 +315,7 @@ def test_ui_register_analyze_and_approve_loop(tmp_path):
|
||||
assert "Review UI Repo Repository Usefulness" in approved_detail.text
|
||||
assert "Language: Python" in approved_detail.text
|
||||
assert "Framework: FastAPI" in approved_detail.text
|
||||
assert "interface:app.py:3" in approved_detail.text
|
||||
|
||||
search_response = client.get("/ui/search", params={"q": "repository"})
|
||||
assert search_response.status_code == 200
|
||||
|
||||
Reference in New Issue
Block a user