generated from coulomb/repo-seed
Provide access to all scope files
This commit is contained in:
2
Makefile
2
Makefile
@@ -22,7 +22,7 @@ start: ## Start the API server in the background on port 8002
|
|||||||
for i in $$(seq 1 50); do \
|
for i in $$(seq 1 50); do \
|
||||||
if [ -f $(PIDFILE) ]; then PID=$$(cat $(PIDFILE)); fi; \
|
if [ -f $(PIDFILE) ]; then PID=$$(cat $(PIDFILE)); fi; \
|
||||||
if [ -n "$$PID" ] && ! kill -0 $$PID 2>/dev/null; then break; 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"; \
|
echo "Started (PID $$PID) — http://127.0.0.1:$(PORT)/ui"; \
|
||||||
exit 0; \
|
exit 0; \
|
||||||
fi; \
|
fi; \
|
||||||
|
|||||||
@@ -17,12 +17,23 @@ router = APIRouter(include_in_schema=False)
|
|||||||
APP_NAME = "Repository Scoping"
|
APP_NAME = "Repository Scoping"
|
||||||
|
|
||||||
|
|
||||||
def page(title: str, body: str, selected_repository: str | None = None) -> HTMLResponse:
|
def page(
|
||||||
repository_context = (
|
title: str,
|
||||||
f'<span class="header-context">{escape(selected_repository)}</span>'
|
body: str,
|
||||||
if selected_repository
|
selected_repository: str | None = None,
|
||||||
else ""
|
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(
|
return HTMLResponse(
|
||||||
f"""
|
f"""
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
@@ -197,7 +208,6 @@ def page(title: str, body: str, selected_repository: str | None = None) -> HTMLR
|
|||||||
<nav class="actions">
|
<nav class="actions">
|
||||||
<a href="/ui/search">Search</a>
|
<a href="/ui/search">Search</a>
|
||||||
<a href="/ui/discovery">Discovery</a>
|
<a href="/ui/discovery">Discovery</a>
|
||||||
<a href="/ui/scope">SCOPE</a>
|
|
||||||
<a href="/docs">API Docs</a>
|
<a href="/docs">API Docs</a>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
@@ -307,6 +317,43 @@ def scope_document() -> HTMLResponse:
|
|||||||
return page("SCOPE.md", body)
|
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")
|
@router.get("/ui/discovery")
|
||||||
def discovery_page(service: RegistryService = Depends(get_service)) -> HTMLResponse:
|
def discovery_page(service: RegistryService = Depends(get_service)) -> HTMLResponse:
|
||||||
repositories = service.list_repositories()
|
repositories = service.list_repositories()
|
||||||
@@ -701,6 +748,7 @@ def repository_detail(
|
|||||||
<div class="actions">
|
<div class="actions">
|
||||||
<h1 style="margin-right:auto">{escape(repository.name)}</h1>
|
<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}/export">Export</a>
|
||||||
|
<a class="button secondary" href="/ui/repos/{repository_id}/scope">SCOPE</a>
|
||||||
<a class="button secondary" href="/ui">Back</a>
|
<a class="button secondary" href="/ui">Back</a>
|
||||||
</div>
|
</div>
|
||||||
<p class="muted">{escape(repository.description or '')}</p>
|
<p class="muted">{escape(repository.description or '')}</p>
|
||||||
@@ -840,7 +888,12 @@ def repository_detail(
|
|||||||
</form>
|
</form>
|
||||||
</section>
|
</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")
|
@router.get("/ui/repos/{repository_id}/export")
|
||||||
@@ -1296,6 +1349,7 @@ def analysis_run_detail(
|
|||||||
f"{repository.name} Run {analysis_run_id}",
|
f"{repository.name} Run {analysis_run_id}",
|
||||||
body,
|
body,
|
||||||
selected_repository=repository.name,
|
selected_repository=repository.name,
|
||||||
|
selected_repository_id=repository.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1462,7 +1516,12 @@ def repository_element_listing(
|
|||||||
</table>
|
</table>
|
||||||
</section>
|
</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}")
|
@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",
|
f"{diff.repository.name} Change Review",
|
||||||
body,
|
body,
|
||||||
selected_repository=diff.repository.name,
|
selected_repository=diff.repository.name,
|
||||||
|
selected_repository_id=diff.repository.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
"# UI Repo\nReports service status through API and CLI entry points.\n",
|
||||||
encoding="utf-8",
|
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 / "requirements.txt").write_text("fastapi\n", encoding="utf-8")
|
||||||
(source / "app.py").write_text(
|
(source / "app.py").write_text(
|
||||||
"from fastapi import FastAPI\n"
|
"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 "Explore after registration" in index_response.text
|
||||||
assert "Use LLM assistance if configured" 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 "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(
|
create_response = client.post(
|
||||||
"/ui/repos",
|
"/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 "Use LLM assistance if configured" in detail_response.text
|
||||||
assert "Trusted auto-populate after analysis" in detail_response.text
|
assert "Trusted auto-populate after analysis" in detail_response.text
|
||||||
assert "Repository Metadata" 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(
|
edit_repository_response = client.post(
|
||||||
f"{repository_path}/edit",
|
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 "1 abilities" in run_detail.text
|
||||||
assert "2 capabilities" in run_detail.text
|
assert "2 capabilities" in run_detail.text
|
||||||
assert "2 features" 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 "Content Chunks" in run_detail.text
|
||||||
assert "README.md:1-2" in run_detail.text
|
assert "README.md:1-2" in run_detail.text
|
||||||
assert "ID " 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"])
|
approved_detail = client.get(approve_response.headers["location"])
|
||||||
assert approved_detail.status_code == 200
|
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 Characteristics" in approved_detail.text
|
||||||
assert "Approved Characteristic Tree" 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
|
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 "1 candidate abilities" in approved_detail.text
|
||||||
assert "2 candidate capabilities" in approved_detail.text
|
assert "2 candidate capabilities" in approved_detail.text
|
||||||
assert "2 candidate features" 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 "Use Approved Registry" in approved_detail.text
|
||||||
assert "Search Profile" in approved_detail.text
|
assert "Search Profile" in approved_detail.text
|
||||||
assert "Discovery" 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"},
|
params={"scope": "all", "type": "scopes", "entry_filter": "approved"},
|
||||||
)
|
)
|
||||||
assert scope_listing.status_code == 200
|
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'<a href="/ui/repos/{repository_id}">UI Repo</a>' in scope_listing.text
|
||||||
assert (
|
assert (
|
||||||
f"/ui/repos/{repository_id}/elements?scope=all&entry_filter=approved&type=abilities"
|
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 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 "FastAPI" in filtered_fact_listing.text
|
||||||
assert "python route decorator" not in filtered_fact_listing.text
|
assert "python route decorator" not in filtered_fact_listing.text
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user