generated from coulomb/repo-seed
Coevolution extension
This commit is contained in:
@@ -17,6 +17,7 @@ from repo_registry.core.models import (
|
||||
CapabilitySummary,
|
||||
ContentChunk,
|
||||
Evidence,
|
||||
ExpectationGap,
|
||||
Feature,
|
||||
ObservedFact,
|
||||
Repository,
|
||||
@@ -47,6 +48,7 @@ class RegistryStore:
|
||||
connection.executescript(migration_path.read_text(encoding="utf-8"))
|
||||
self._ensure_content_chunks_table(connection)
|
||||
self._ensure_approved_source_ref_columns(connection)
|
||||
self._ensure_expectation_gaps_table(connection)
|
||||
|
||||
def connect(self) -> sqlite3.Connection:
|
||||
connection = sqlite3.connect(self.database_path)
|
||||
@@ -92,6 +94,29 @@ class RegistryStore:
|
||||
"CREATE INDEX IF NOT EXISTS idx_content_chunks_run ON content_chunks(analysis_run_id)"
|
||||
)
|
||||
|
||||
def _ensure_expectation_gaps_table(self, connection: sqlite3.Connection) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS expectation_gaps (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
repository_id INTEGER NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,
|
||||
analysis_run_id INTEGER REFERENCES analysis_runs(id) ON DELETE SET NULL,
|
||||
expected_type TEXT NOT NULL,
|
||||
expected_name TEXT NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
notes TEXT NOT NULL DEFAULT '',
|
||||
status TEXT NOT NULL DEFAULT 'open',
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
"""
|
||||
)
|
||||
connection.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_expectation_gaps_repository ON expectation_gaps(repository_id)"
|
||||
)
|
||||
connection.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_expectation_gaps_run ON expectation_gaps(analysis_run_id)"
|
||||
)
|
||||
|
||||
def create_repository(
|
||||
self,
|
||||
*,
|
||||
@@ -1050,6 +1075,93 @@ class RegistryStore:
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def create_expectation_gap(
|
||||
self,
|
||||
repository_id: int,
|
||||
analysis_run_id: int | None,
|
||||
*,
|
||||
expected_type: str,
|
||||
expected_name: str,
|
||||
source: str,
|
||||
notes: str = "",
|
||||
) -> ExpectationGap:
|
||||
self.get_repository(repository_id)
|
||||
if analysis_run_id is not None:
|
||||
self.get_analysis_run(repository_id, analysis_run_id)
|
||||
with self.connect() as connection:
|
||||
cursor = connection.execute(
|
||||
"""
|
||||
INSERT INTO expectation_gaps
|
||||
(repository_id, analysis_run_id, expected_type, expected_name,
|
||||
source, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
repository_id,
|
||||
analysis_run_id,
|
||||
expected_type,
|
||||
expected_name,
|
||||
source,
|
||||
notes,
|
||||
),
|
||||
)
|
||||
gap_id = int(cursor.lastrowid)
|
||||
log_operation(
|
||||
"expectation_gap_recorded",
|
||||
repository_id=repository_id,
|
||||
analysis_run_id=analysis_run_id,
|
||||
expectation_gap_id=gap_id,
|
||||
expected_type=expected_type,
|
||||
)
|
||||
return self.get_expectation_gap(repository_id, gap_id)
|
||||
|
||||
def get_expectation_gap(
|
||||
self,
|
||||
repository_id: int,
|
||||
expectation_gap_id: int,
|
||||
) -> ExpectationGap:
|
||||
with self.connect() as connection:
|
||||
row = connection.execute(
|
||||
"""
|
||||
SELECT id, repository_id, analysis_run_id, expected_type,
|
||||
expected_name, source, notes, status, created_at
|
||||
FROM expectation_gaps
|
||||
WHERE repository_id = ? AND id = ?
|
||||
""",
|
||||
(repository_id, expectation_gap_id),
|
||||
).fetchone()
|
||||
if row is None:
|
||||
raise NotFoundError(
|
||||
f"expectation gap {expectation_gap_id} was not found for repository "
|
||||
f"{repository_id}"
|
||||
)
|
||||
return self._expectation_gap_from_row(row)
|
||||
|
||||
def list_expectation_gaps(
|
||||
self,
|
||||
repository_id: int,
|
||||
analysis_run_id: int | None = None,
|
||||
) -> list[ExpectationGap]:
|
||||
self.get_repository(repository_id)
|
||||
params: tuple[int, ...]
|
||||
where = "WHERE repository_id = ?"
|
||||
params = (repository_id,)
|
||||
if analysis_run_id is not None:
|
||||
where += " AND analysis_run_id = ?"
|
||||
params = (repository_id, analysis_run_id)
|
||||
with self.connect() as connection:
|
||||
rows = connection.execute(
|
||||
f"""
|
||||
SELECT id, repository_id, analysis_run_id, expected_type,
|
||||
expected_name, source, notes, status, created_at
|
||||
FROM expectation_gaps
|
||||
{where}
|
||||
ORDER BY created_at DESC, id DESC
|
||||
""",
|
||||
params,
|
||||
).fetchall()
|
||||
return [self._expectation_gap_from_row(row) for row in rows]
|
||||
|
||||
def fail_analysis_run(
|
||||
self,
|
||||
repository_id: int,
|
||||
@@ -2215,3 +2327,17 @@ class RegistryStore:
|
||||
end_line=row["end_line"],
|
||||
text=row["text"],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _expectation_gap_from_row(row: sqlite3.Row) -> ExpectationGap:
|
||||
return ExpectationGap(
|
||||
id=row["id"],
|
||||
repository_id=row["repository_id"],
|
||||
analysis_run_id=row["analysis_run_id"],
|
||||
expected_type=row["expected_type"],
|
||||
expected_name=row["expected_name"],
|
||||
source=row["source"],
|
||||
notes=row["notes"],
|
||||
status=row["status"],
|
||||
created_at=row["created_at"],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user