generated from coulomb/repo-seed
fixed restart target
This commit is contained in:
20
Makefile
20
Makefile
@@ -2,6 +2,7 @@ PORT := 8002
|
||||
PIDFILE := var/api.pid
|
||||
LOGFILE := var/api-8002.log
|
||||
UVICORN := .venv/bin/uvicorn
|
||||
HEALTH_URL := http://127.0.0.1:$(PORT)/health
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
.PHONY: help start stop restart status showlogs
|
||||
@@ -15,10 +16,23 @@ start: ## Start the API server in the background on port 8002
|
||||
@if [ -f $(PIDFILE) ] && kill -0 $$(cat $(PIDFILE)) 2>/dev/null; then \
|
||||
echo "Already running (PID $$(cat $(PIDFILE)))"; \
|
||||
else \
|
||||
nohup $(UVICORN) repo_registry.web_api.app:app --port $(PORT) \
|
||||
setsid sh -c 'echo $$$$ > $(PIDFILE); exec $(UVICORN) repo_registry.web_api.app:app --host 127.0.0.1 --port $(PORT)' \
|
||||
>> $(LOGFILE) 2>&1 & \
|
||||
echo $$! > $(PIDFILE); \
|
||||
echo "Started (PID $$!) — http://127.0.0.1:$(PORT)/ui"; \
|
||||
PID=""; \
|
||||
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 \
|
||||
echo "Started (PID $$PID) — http://127.0.0.1:$(PORT)/ui"; \
|
||||
exit 0; \
|
||||
fi; \
|
||||
sleep 0.1; \
|
||||
done; \
|
||||
rm -f $(PIDFILE); \
|
||||
echo "Failed to start API server on http://127.0.0.1:$(PORT)/ui"; \
|
||||
echo "Recent log output:"; \
|
||||
tail -40 $(LOGFILE) 2>/dev/null || true; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
stop: ## Stop the API server
|
||||
|
||||
@@ -17,7 +17,12 @@ router = APIRouter(include_in_schema=False)
|
||||
APP_NAME = "Repository Scoping"
|
||||
|
||||
|
||||
def page(title: str, body: str) -> HTMLResponse:
|
||||
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 ""
|
||||
)
|
||||
return HTMLResponse(
|
||||
f"""
|
||||
<!doctype html>
|
||||
@@ -166,6 +171,9 @@ def page(title: str, body: str) -> HTMLResponse:
|
||||
font: 13px/1.55 ui-monospace, SFMono-Regular, Consolas, monospace;
|
||||
}}
|
||||
.actions {{ display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }}
|
||||
.header-brand {{ display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; }}
|
||||
.header-context {{ color: #b6c8d6; font-weight: 650; }}
|
||||
.header-context::before {{ content: "/"; color: #6b7d8f; margin-right: 10px; }}
|
||||
[data-pending] {{ display: none; color: var(--muted); }}
|
||||
form.is-submitting [data-pending] {{ display: inline; }}
|
||||
form.is-submitting button[type="submit"] {{ opacity: .7; cursor: wait; }}
|
||||
@@ -182,7 +190,10 @@ def page(title: str, body: str) -> HTMLResponse:
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<a href="/ui"><strong>{APP_NAME}</strong></a>
|
||||
<div class="header-brand">
|
||||
<a href="/ui"><strong>{APP_NAME}</strong></a>
|
||||
{repository_context}
|
||||
</div>
|
||||
<nav class="actions">
|
||||
<a href="/ui/search">Search</a>
|
||||
<a href="/ui/discovery">Discovery</a>
|
||||
@@ -279,6 +290,7 @@ def repository_index(service: RegistryService = Depends(get_service)) -> HTMLRes
|
||||
|
||||
@router.get("/ui/scope")
|
||||
def scope_document() -> HTMLResponse:
|
||||
repository_name = Path.cwd().name
|
||||
scope_path = Path("SCOPE.md")
|
||||
if scope_path.exists():
|
||||
content = scope_path.read_text(encoding="utf-8")
|
||||
@@ -288,7 +300,7 @@ def scope_document() -> HTMLResponse:
|
||||
body = f"""
|
||||
<h1>SCOPE.md</h1>
|
||||
<section class="panel stack">
|
||||
<p class="muted">Canonical scope summary for this repository.</p>
|
||||
<p class="muted">Canonical scope summary for the {escape(repository_name)} repository.</p>
|
||||
{rendered}
|
||||
</section>
|
||||
"""
|
||||
@@ -828,7 +840,7 @@ def repository_detail(
|
||||
</form>
|
||||
</section>
|
||||
"""
|
||||
return page(repository.name, body)
|
||||
return page(repository.name, body, selected_repository=repository.name)
|
||||
|
||||
|
||||
@router.get("/ui/repos/{repository_id}/export")
|
||||
@@ -1280,7 +1292,11 @@ def analysis_run_detail(
|
||||
{render_expectation_gaps(expectation_gaps)}
|
||||
</section>
|
||||
"""
|
||||
return page(f"{repository.name} Run {analysis_run_id}", body)
|
||||
return page(
|
||||
f"{repository.name} Run {analysis_run_id}",
|
||||
body,
|
||||
selected_repository=repository.name,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/ui/repos/{repository_id}/analysis-runs/{analysis_run_id}/expectation-gaps")
|
||||
@@ -1446,7 +1462,7 @@ def repository_element_listing(
|
||||
</table>
|
||||
</section>
|
||||
"""
|
||||
return page(title, body)
|
||||
return page(title, body, selected_repository=repository.name)
|
||||
|
||||
|
||||
@router.get("/ui/repos/{repository_id}/analysis-runs/{base_analysis_run_id}/diff/{target_analysis_run_id}")
|
||||
@@ -1499,7 +1515,11 @@ def analysis_run_diff_detail(
|
||||
</section>
|
||||
</div>
|
||||
"""
|
||||
return page(f"{diff.repository.name} Change Review", body)
|
||||
return page(
|
||||
f"{diff.repository.name} Change Review",
|
||||
body,
|
||||
selected_repository=diff.repository.name,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/ui/repos/{repository_id}/analysis-runs/{analysis_run_id}/candidate-graph/approve")
|
||||
@@ -2654,11 +2674,17 @@ def render_element_row(
|
||||
repository_id: int,
|
||||
analysis_run_id: int | None,
|
||||
) -> str:
|
||||
name = str(row["name"])
|
||||
rendered_name = (
|
||||
f'<a href="/ui/repos/{repository_id}">{escape(name)}</a>'
|
||||
if row.get("item_kind") == "scope"
|
||||
else escape(name)
|
||||
)
|
||||
return f"""
|
||||
<tr>
|
||||
<td>{render_entry_badge(row)}</td>
|
||||
<td><span class="pill">{escape(str(row["primary_class"]))}</span>{render_attribute_pills(row)}</td>
|
||||
<td>{escape(str(row["name"]))}{render_drilldown_links(row, repository_id, analysis_run_id)}</td>
|
||||
<td>{rendered_name}{render_drilldown_links(row, repository_id, analysis_run_id)}</td>
|
||||
<td>{escape(str(row["parent"]))}</td>
|
||||
<td>{render_element_source_detail(row)}</td>
|
||||
<td>{render_element_actions(row, repository_id, analysis_run_id)}</td>
|
||||
@@ -3587,7 +3613,7 @@ def render_ability_map(ability_map: dict, repository_id: int) -> str:
|
||||
<div class="tree">
|
||||
<ul>
|
||||
<li id="scope-{scope['id']}">
|
||||
<strong>{escape(scope['name'])}</strong>
|
||||
<strong><a href="/ui/repos/{repository_id}">{escape(scope['name'])}</a></strong>
|
||||
<span class="pill">scope</span>
|
||||
<span class="pill">{scope['confidence']:.2f} {escape(scope['confidence_label'])}</span>
|
||||
<p class="muted">{escape(scope_description)}</p>
|
||||
|
||||
@@ -330,6 +330,7 @@ def test_ui_scope_page_presents_scope_md():
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "SCOPE.md" in response.text
|
||||
assert "Canonical scope summary for the repo-scoping repository." in response.text
|
||||
assert "scope.generate" in response.text
|
||||
assert "repo-scoping" in response.text
|
||||
|
||||
@@ -1358,8 +1359,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 "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
|
||||
assert "scope" in approved_detail.text
|
||||
assert "Evidence supporting this capability" in approved_detail.text
|
||||
assert "1 scope" in approved_detail.text
|
||||
@@ -1378,6 +1381,14 @@ def test_ui_register_analyze_and_approve_loop(tmp_path):
|
||||
assert "Export" in approved_detail.text
|
||||
assert "Elements" in approved_detail.text
|
||||
assert "q=Report+Service+Status" in approved_detail.text
|
||||
|
||||
scope_listing = client.get(
|
||||
f"/ui/repos/{repository_id}/elements",
|
||||
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 href="/ui/repos/{repository_id}">UI Repo</a>' in scope_listing.text
|
||||
assert (
|
||||
f"/ui/repos/{repository_id}/elements?scope=all&entry_filter=approved&type=abilities"
|
||||
in approved_detail.text
|
||||
|
||||
Reference in New Issue
Block a user