Provide access to all scope files

This commit is contained in:
2026-05-02 17:01:24 +02:00
parent 14fce4f4d6
commit 11f0230775
3 changed files with 99 additions and 15 deletions

View File

@@ -22,7 +22,7 @@ start: ## Start the API server in the background on port 8002
for i in $$(seq 1 50); do \
if [ -f $(PIDFILE) ]; then PID=$$(cat $(PIDFILE)); fi; \
if [ -n "$$PID" ] && ! kill -0 $$PID 2>/dev/null; then break; fi; \
if curl -fsS $(HEALTH_URL) >/dev/null 2>&1; then \
if [ -n "$$PID" ] && curl -fsS $(HEALTH_URL) >/dev/null 2>&1; then \
echo "Started (PID $$PID) — http://127.0.0.1:$(PORT)/ui"; \
exit 0; \
fi; \

View File

@@ -17,12 +17,23 @@ router = APIRouter(include_in_schema=False)
APP_NAME = "Repository Scoping"
def page(title: str, body: str, selected_repository: str | None = None) -> HTMLResponse:
repository_context = (
f'<span class="header-context">{escape(selected_repository)}</span>'
if selected_repository
else ""
)
def page(
title: str,
body: str,
selected_repository: str | None = None,
selected_repository_id: int | None = None,
) -> HTMLResponse:
if selected_repository and selected_repository_id is not None:
repository_context = (
f'<a class="header-context" href="/ui/repos/{selected_repository_id}">'
f"{escape(selected_repository)}</a>"
)
elif selected_repository:
repository_context = (
f'<span class="header-context">{escape(selected_repository)}</span>'
)
else:
repository_context = ""
return HTMLResponse(
f"""
<!doctype html>
@@ -197,7 +208,6 @@ def page(title: str, body: str, selected_repository: str | None = None) -> HTMLR
<nav class="actions">
<a href="/ui/search">Search</a>
<a href="/ui/discovery">Discovery</a>
<a href="/ui/scope">SCOPE</a>
<a href="/docs">API Docs</a>
</nav>
</header>
@@ -307,6 +317,43 @@ def scope_document() -> HTMLResponse:
return page("SCOPE.md", body)
@router.get("/ui/repos/{repository_id}/scope")
def repository_scope_document(
repository_id: int,
service: RegistryService = Depends(get_service),
) -> HTMLResponse:
repository = service.get_repository(repository_id)
checkout = service.ingestion.cached_checkout(repository.url)
if checkout is None:
rendered = (
'<p class="muted">No cached checkout exists for this repository yet. '
"Run analysis first, or use a local repository path.</p>"
)
else:
scope_path = checkout.source_path / "SCOPE.md"
if scope_path.exists():
content = scope_path.read_text(encoding="utf-8")
rendered = f'<pre class="scope-document">{escape(content)}</pre>'
else:
rendered = (
'<p class="muted">No SCOPE.md file was found in this '
"repository checkout.</p>"
)
body = f"""
<h1>SCOPE.md</h1>
<section class="panel stack">
<p class="muted">Canonical scope summary for the {escape(repository.name)} repository.</p>
{rendered}
</section>
"""
return page(
"SCOPE.md",
body,
selected_repository=repository.name,
selected_repository_id=repository.id,
)
@router.get("/ui/discovery")
def discovery_page(service: RegistryService = Depends(get_service)) -> HTMLResponse:
repositories = service.list_repositories()
@@ -701,6 +748,7 @@ def repository_detail(
<div class="actions">
<h1 style="margin-right:auto">{escape(repository.name)}</h1>
<a class="button secondary" href="/ui/repos/{repository_id}/export">Export</a>
<a class="button secondary" href="/ui/repos/{repository_id}/scope">SCOPE</a>
<a class="button secondary" href="/ui">Back</a>
</div>
<p class="muted">{escape(repository.description or '')}</p>
@@ -840,7 +888,12 @@ def repository_detail(
</form>
</section>
"""
return page(repository.name, body, selected_repository=repository.name)
return page(
repository.name,
body,
selected_repository=repository.name,
selected_repository_id=repository.id,
)
@router.get("/ui/repos/{repository_id}/export")
@@ -1296,6 +1349,7 @@ def analysis_run_detail(
f"{repository.name} Run {analysis_run_id}",
body,
selected_repository=repository.name,
selected_repository_id=repository.id,
)
@@ -1462,7 +1516,12 @@ def repository_element_listing(
</table>
</section>
"""
return page(title, body, selected_repository=repository.name)
return page(
title,
body,
selected_repository=repository.name,
selected_repository_id=repository.id,
)
@router.get("/ui/repos/{repository_id}/analysis-runs/{base_analysis_run_id}/diff/{target_analysis_run_id}")
@@ -1519,6 +1578,7 @@ def analysis_run_diff_detail(
f"{diff.repository.name} Change Review",
body,
selected_repository=diff.repository.name,
selected_repository_id=diff.repository.id,
)

View File

@@ -1209,6 +1209,10 @@ def test_ui_register_analyze_and_approve_loop(tmp_path):
"# UI Repo\nReports service status through API and CLI entry points.\n",
encoding="utf-8",
)
(source / "SCOPE.md").write_text(
"# SCOPE\n\n## One-liner\n\nUI Repo owns the status reporting scope.\n",
encoding="utf-8",
)
(source / "requirements.txt").write_text("fastapi\n", encoding="utf-8")
(source / "app.py").write_text(
"from fastapi import FastAPI\n"
@@ -1243,6 +1247,7 @@ def test_ui_register_analyze_and_approve_loop(tmp_path):
assert "Explore after registration" in index_response.text
assert "Use LLM assistance if configured" in index_response.text
assert "Trusted auto-populate after analysis" in index_response.text
assert '<a href="/ui/scope">SCOPE</a>' not in index_response.text
create_response = client.post(
"/ui/repos",
@@ -1268,6 +1273,19 @@ def test_ui_register_analyze_and_approve_loop(tmp_path):
assert "Use LLM assistance if configured" in detail_response.text
assert "Trusted auto-populate after analysis" in detail_response.text
assert "Repository Metadata" in detail_response.text
assert (
f'<a class="button secondary" href="/ui/repos/{repository_id}/scope">SCOPE</a>'
in detail_response.text
)
repo_scope_response = client.get(f"/ui/repos/{repository_id}/scope")
assert repo_scope_response.status_code == 200
assert (
f'<a class="header-context" href="/ui/repos/{repository_id}">UI Repo</a>'
in repo_scope_response.text
)
assert "Canonical scope summary for the UI Repo repository." in repo_scope_response.text
assert "UI Repo owns the status reporting scope." in repo_scope_response.text
edit_repository_response = client.post(
f"{repository_path}/edit",
@@ -1305,7 +1323,7 @@ def test_ui_register_analyze_and_approve_loop(tmp_path):
assert "1 abilities" in run_detail.text
assert "2 capabilities" in run_detail.text
assert "2 features" in run_detail.text
assert "7 facts" in run_detail.text
assert "8 facts" in run_detail.text
assert "Content Chunks" in run_detail.text
assert "README.md:1-2" in run_detail.text
assert "ID " in run_detail.text
@@ -1359,7 +1377,10 @@ def test_ui_register_analyze_and_approve_loop(tmp_path):
approved_detail = client.get(approve_response.headers["location"])
assert approved_detail.status_code == 200
assert '<span class="header-context">UI Repo Edited</span>' in approved_detail.text
assert (
f'<a class="header-context" href="/ui/repos/{repository_id}">UI Repo Edited</a>'
in approved_detail.text
)
assert "Approved Characteristics" in approved_detail.text
assert "Approved Characteristic Tree" in approved_detail.text
assert '<strong><a href="/ui/repos/1">UI Repo</a></strong>' in approved_detail.text
@@ -1374,7 +1395,7 @@ def test_ui_register_analyze_and_approve_loop(tmp_path):
assert "1 candidate abilities" in approved_detail.text
assert "2 candidate capabilities" in approved_detail.text
assert "2 candidate features" in approved_detail.text
assert "7 candidate facts" in approved_detail.text
assert "8 candidate facts" in approved_detail.text
assert "Use Approved Registry" in approved_detail.text
assert "Search Profile" in approved_detail.text
assert "Discovery" in approved_detail.text
@@ -1387,7 +1408,10 @@ def test_ui_register_analyze_and_approve_loop(tmp_path):
params={"scope": "all", "type": "scopes", "entry_filter": "approved"},
)
assert scope_listing.status_code == 200
assert '<span class="header-context">UI Repo Edited</span>' in scope_listing.text
assert (
f'<a class="header-context" href="/ui/repos/{repository_id}">UI Repo Edited</a>'
in scope_listing.text
)
assert f'<a href="/ui/repos/{repository_id}">UI Repo</a>' in scope_listing.text
assert (
f"/ui/repos/{repository_id}/elements?scope=all&amp;entry_filter=approved&amp;type=abilities"
@@ -1521,7 +1545,7 @@ def test_ui_register_analyze_and_approve_loop(tmp_path):
},
)
assert filtered_fact_listing.status_code == 200
assert "1 of 7 shown" in filtered_fact_listing.text
assert "1 of 8 shown" in filtered_fact_listing.text
assert "FastAPI" in filtered_fact_listing.text
assert "python route decorator" not in filtered_fact_listing.text