Verified with the coordinated workspace changes by devkit full run 2026-09-08T225814-186389-0000-3e3ed7cd (all seven phases passed). This shared UI pass does not mark the individual module reviews complete.
91 lines
3.2 KiB
Python
Executable File
91 lines
3.2 KiB
Python
Executable File
"""Small read-only context bundles; no automatic source/credential dumping."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
from pathlib import Path
|
|
|
|
from .common import META_ROOT, now, read_json
|
|
from .workspace import inspect_repository, load_project, selected_repositories
|
|
|
|
|
|
def build_context(
|
|
workspace_root: Path, project_file: Path | None, names: list[str], changed: bool
|
|
) -> dict:
|
|
project = load_project(workspace_root, project_file)
|
|
repos = selected_repositories(project, names)
|
|
with ThreadPoolExecutor(max_workers=8) as pool:
|
|
states = list(pool.map(inspect_repository, repos))
|
|
if changed:
|
|
states = [
|
|
state
|
|
for state in states
|
|
if state["errors"]
|
|
or state["dirty_entries"]
|
|
or state["ahead"]
|
|
or (state["head"] and not state["upstream"])
|
|
]
|
|
inventory = {}
|
|
if not project_file:
|
|
path = META_ROOT / "docs/project/ui-review-issue-inventory.json"
|
|
if path.is_file():
|
|
payload = read_json(path)
|
|
for item in payload.get("issues", []) if isinstance(payload, dict) else []:
|
|
if isinstance(item, dict):
|
|
inventory[item.get("repository")] = item
|
|
for state in states:
|
|
root = Path(state["path"])
|
|
state["instructions"] = [
|
|
str(path) for path in (root / "AGENTS.md",) if path.is_file()
|
|
]
|
|
state["documentation"] = [
|
|
str(path)
|
|
for path in (
|
|
root / "README.md",
|
|
root / "docs/README.md",
|
|
root / "docs/MODULE_ARCHITECTURE.md",
|
|
)
|
|
if path.is_file()
|
|
]
|
|
state["change_entry_count"] = len(state["dirty_entries"])
|
|
state["review_issue"] = inventory.get(state["name"], {}).get("url")
|
|
state["suggested_check"] = (
|
|
f"./devkit check --repo {state['name']} --profile quick --dry-run"
|
|
)
|
|
summary = [
|
|
f"{project.name}: {len(states)} repositories selected (offline; upstream counts may be stale)."
|
|
]
|
|
for state in states:
|
|
errors = f" ERROR: {'; '.join(state['errors'])}" if state["errors"] else ""
|
|
summary.append(
|
|
f"{state['name']}: {state['branch'] or '(detached/unborn)'}; {state['change_entry_count']} change entries; ahead={state['ahead']} behind={state['behind']}{errors}"
|
|
)
|
|
return {
|
|
"schema_version": 1,
|
|
"generated_at": now(),
|
|
"workspace_root": str(workspace_root),
|
|
"project": project.name,
|
|
"remote_checked": False,
|
|
"repositories": states,
|
|
"summary": summary,
|
|
"_exit_code": 1 if any(state["errors"] for state in states) else 0,
|
|
}
|
|
|
|
|
|
def register(subparsers):
|
|
parser = subparsers.add_parser(
|
|
"context",
|
|
help="Offline repository changes, ownership and relevant instruction paths",
|
|
)
|
|
parser.add_argument("--repo", action="append", default=[])
|
|
parser.add_argument(
|
|
"--changed",
|
|
action="store_true",
|
|
help="Show dirty or locally ahead repositories, retaining inspection errors",
|
|
)
|
|
parser.set_defaults(
|
|
handler=lambda args: build_context(
|
|
args.workspace_root, args.project, args.repo, args.changed
|
|
)
|
|
)
|