generated from coulomb/repo-seed
Add activity-core scope context API
This commit is contained in:
@@ -62,6 +62,7 @@ from repo_registry.web_api.schemas import (
|
||||
RepositoryComparisonResponse,
|
||||
RepositoryCreate,
|
||||
RepositoryResponse,
|
||||
RepositoryScopeContextResponse,
|
||||
RepositoryUpdate,
|
||||
ReviewDecisionResponse,
|
||||
ScanSummaryResponse,
|
||||
@@ -1346,6 +1347,45 @@ def export_repository_registry_entry(
|
||||
return PlainTextResponse(content, media_type="application/x-yaml")
|
||||
|
||||
|
||||
@app.get(
|
||||
"/repos/{repo_slug}/scope/context",
|
||||
tags=["scope"],
|
||||
response_model=RepositoryScopeContextResponse,
|
||||
)
|
||||
def get_repository_scope_context(
|
||||
repo_slug: str,
|
||||
service: RegistryService = Depends(get_service),
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> dict[str, object]:
|
||||
try:
|
||||
repository = repository_by_slug(service, repo_slug)
|
||||
ability_map = service.ability_map(repository.id)
|
||||
except NotFoundError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
scope_path = inspectable_scope_file_path(service, repository, repo_slug, settings)
|
||||
scope_md_exists = bool(scope_path and scope_path.is_file())
|
||||
scope_summary = (
|
||||
scope_summary_from_file(scope_path)
|
||||
if scope_md_exists and scope_path is not None
|
||||
else None
|
||||
)
|
||||
if not scope_summary:
|
||||
scope_summary = (
|
||||
ability_map.scope.description.strip()
|
||||
or (repository.description.strip() if repository.description else None)
|
||||
or None
|
||||
)
|
||||
|
||||
return {
|
||||
"repo_slug": slugify(repo_slug),
|
||||
"capabilities": scope_context_capabilities(ability_map),
|
||||
"tags": scope_context_tags(ability_map),
|
||||
"scope_md_exists": scope_md_exists,
|
||||
"scope_summary": scope_summary,
|
||||
}
|
||||
|
||||
|
||||
@app.get(
|
||||
"/repos/{repo_slug}/scope",
|
||||
tags=["scope"],
|
||||
@@ -1413,6 +1453,97 @@ def write_repository_scope(
|
||||
return {"written": True, "path": str(scope_path)}
|
||||
|
||||
|
||||
def scope_context_capabilities(ability_map) -> list[str]:
|
||||
return unique_preserving_order(
|
||||
[
|
||||
capability.name
|
||||
for ability in ability_map.abilities
|
||||
for capability in ability.capabilities
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def scope_context_tags(ability_map) -> list[str]:
|
||||
tags = []
|
||||
for ability in ability_map.abilities:
|
||||
tags.append(ability.primary_class)
|
||||
tags.extend(ability.attributes)
|
||||
for capability in ability.capabilities:
|
||||
tags.append(capability.primary_class)
|
||||
tags.extend(capability.attributes)
|
||||
return sorted(unique_preserving_order(tags), key=str.lower)
|
||||
|
||||
|
||||
def unique_preserving_order(values: list[str]) -> list[str]:
|
||||
result: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for value in values:
|
||||
item = str(value).strip()
|
||||
key = item.lower()
|
||||
if not item or key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
|
||||
def inspectable_scope_file_path(
|
||||
service: RegistryService,
|
||||
repository,
|
||||
repo_slug: str,
|
||||
settings: Settings,
|
||||
) -> Path | None:
|
||||
try:
|
||||
state_hub_path = state_hub_scope_file_path(repo_slug, settings)
|
||||
except ValueError:
|
||||
state_hub_path = None
|
||||
if state_hub_path is not None:
|
||||
return state_hub_path
|
||||
|
||||
source_path = Path(repository.url)
|
||||
if source_path.exists() and source_path.is_dir():
|
||||
return source_path / "SCOPE.md"
|
||||
|
||||
checkout = service.ingestion.cached_checkout(repository.url)
|
||||
if checkout is not None and checkout.source_path.exists():
|
||||
return checkout.source_path / "SCOPE.md"
|
||||
return None
|
||||
|
||||
|
||||
def scope_summary_from_file(scope_path: Path) -> str | None:
|
||||
try:
|
||||
content = scope_path.read_text(encoding="utf-8")
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
paragraph: list[str] = []
|
||||
in_frontmatter = False
|
||||
frontmatter_checked = False
|
||||
for line in content.splitlines():
|
||||
stripped = line.strip()
|
||||
if not frontmatter_checked:
|
||||
frontmatter_checked = True
|
||||
if stripped == "---":
|
||||
in_frontmatter = True
|
||||
continue
|
||||
if in_frontmatter:
|
||||
if stripped == "---":
|
||||
in_frontmatter = False
|
||||
continue
|
||||
if not stripped:
|
||||
if paragraph:
|
||||
return " ".join(paragraph)
|
||||
continue
|
||||
if stripped == "---" or stripped.startswith("#") or stripped.startswith(">"):
|
||||
if paragraph:
|
||||
return " ".join(paragraph)
|
||||
continue
|
||||
paragraph.append(stripped)
|
||||
if paragraph:
|
||||
return " ".join(paragraph)
|
||||
return None
|
||||
|
||||
|
||||
def ensure_scope_generation_ready(
|
||||
service: RegistryService,
|
||||
repo_slug: str,
|
||||
|
||||
@@ -862,6 +862,51 @@ class RepositoryAbilityMapResponse(BaseModel):
|
||||
}
|
||||
|
||||
|
||||
class RepositoryScopeContextResponse(BaseModel):
|
||||
repo_slug: str = Field(
|
||||
description="Slug requested by the caller after normal slugification."
|
||||
)
|
||||
capabilities: list[str] = Field(
|
||||
description="Approved capability names from the repository ability map."
|
||||
)
|
||||
tags: list[str] = Field(
|
||||
description=(
|
||||
"Sorted union of approved ability and capability primary classes "
|
||||
"plus their attributes."
|
||||
)
|
||||
)
|
||||
scope_md_exists: bool = Field(
|
||||
description=(
|
||||
"True when repo-scoping can inspect a local or cached checkout and "
|
||||
"root SCOPE.md exists."
|
||||
)
|
||||
)
|
||||
scope_summary: str | None = Field(
|
||||
description=(
|
||||
"First non-empty paragraph of root SCOPE.md when available, "
|
||||
"otherwise registry metadata."
|
||||
)
|
||||
)
|
||||
|
||||
model_config = {
|
||||
"extra": "forbid",
|
||||
"json_schema_extra": {
|
||||
"examples": [
|
||||
{
|
||||
"repo_slug": "repo-scoping",
|
||||
"capabilities": ["Generate SCOPE.md"],
|
||||
"tags": ["api", "generation", "scope"],
|
||||
"scope_md_exists": True,
|
||||
"scope_summary": (
|
||||
"Repository Scoping maps repositories into reviewable "
|
||||
"scope graphs."
|
||||
),
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class IdResponse(BaseModel):
|
||||
id: int
|
||||
|
||||
|
||||
Reference in New Issue
Block a user