diff --git a/Makefile b/Makefile index 2982dee..4acb0d9 100644 --- a/Makefile +++ b/Makefile @@ -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; \ diff --git a/src/repo_registry/web_ui/views.py b/src/repo_registry/web_ui/views.py index 1862136..21275ec 100644 --- a/src/repo_registry/web_ui/views.py +++ b/src/repo_registry/web_ui/views.py @@ -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'{escape(selected_repository)}' - 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'' + f"{escape(selected_repository)}" + ) + elif selected_repository: + repository_context = ( + f'{escape(selected_repository)}' + ) + else: + repository_context = "" return HTMLResponse( f""" @@ -197,7 +208,6 @@ def page(title: str, body: str, selected_repository: str | None = None) -> HTMLR @@ -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 = ( + '
No cached checkout exists for this repository yet. ' + "Run analysis first, or use a local repository path.
" + ) + else: + scope_path = checkout.source_path / "SCOPE.md" + if scope_path.exists(): + content = scope_path.read_text(encoding="utf-8") + rendered = f'{escape(content)}'
+ else:
+ rendered = (
+ 'No SCOPE.md file was found in this ' + "repository checkout.
" + ) + body = f""" +Canonical scope summary for the {escape(repository.name)} repository.
+ {rendered} +{escape(repository.description or '')}
@@ -840,7 +888,12 @@ def repository_detail( """ - 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( """ - 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, ) diff --git a/tests/test_web_api.py b/tests/test_web_api.py index b65fcce..2bf502d 100644 --- a/tests/test_web_api.py +++ b/tests/test_web_api.py @@ -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 'SCOPE' 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'SCOPE' + in detail_response.text + ) + + repo_scope_response = client.get(f"/ui/repos/{repository_id}/scope") + assert repo_scope_response.status_code == 200 + assert ( + f'UI Repo' + 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 'UI Repo Edited' in approved_detail.text + assert ( + f'UI Repo Edited' + in approved_detail.text + ) assert "Approved Characteristics" in approved_detail.text assert "Approved Characteristic Tree" in approved_detail.text assert 'UI Repo' 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 'UI Repo Edited' in scope_listing.text + assert ( + f'UI Repo Edited' + in scope_listing.text + ) assert f'UI Repo' in scope_listing.text assert ( f"/ui/repos/{repository_id}/elements?scope=all&entry_filter=approved&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