feat(sbom): add custodian ingest-sbom + fix help button target

custodian_cli.py:
- new ingest-sbom subcommand: auto-detects repo slug from local_path
  registration, runs ingest_sbom.py --scan from the repo root
- --dry-run flag passes through to the underlying script
- --slug override for repos where path lookup fails

repos.md:
- ? button on "⚠ not ingested" now opens /docs/sbom (not /docs/repos)

docs/sbom.md:
- Ingest commands section now leads with `custodian ingest-sbom` (repo-root)
- make ingest-sbom kept as low-level alternative
- Per-ecosystem and gap-type references updated to new command

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 13:31:08 +01:00
parent 944104307a
commit 62fbe884e3
3 changed files with 65 additions and 5 deletions

View File

@@ -284,6 +284,41 @@ def cmd_register(args: argparse.Namespace) -> None:
print(" The repo agent will pick up 4 onboarding tasks and integrate autonomously.")
def cmd_ingest_sbom(args: argparse.Namespace) -> None:
"""Ingest SBOM for the current (or specified) repo. Auto-detects slug from registration."""
project_path = Path(args.path).resolve()
_api_get("/state/health")
# Resolve repo slug: explicit override, or look up by local_path
repo_slug = args.slug
if not repo_slug:
repos = _api_get("/repos/")
repo = next((r for r in repos if r.get("local_path") == str(project_path)), None)
if not repo:
print(f"ERROR: No registered repo found for path '{project_path}'.")
print(" Register first: custodian register-project --domain <slug>")
print(" Or pass --slug explicitly.")
sys.exit(1)
repo_slug = repo["slug"]
print(f"==> Ingesting SBOM for '{repo_slug}' from {project_path} ...")
python = STATE_HUB_DIR / ".venv" / "bin" / "python"
ingest_script = STATE_HUB_DIR / "scripts" / "ingest_sbom.py"
if not python.exists():
print(f"ERROR: .venv not found at {STATE_HUB_DIR}. Run 'make install' in the state-hub directory.")
sys.exit(1)
cmd = [str(python), str(ingest_script), "--repo", repo_slug, "--scan", "--repo-path", str(project_path)]
if args.dry_run:
cmd.append("--dry-run")
result = subprocess.run(cmd)
sys.exit(result.returncode)
def cmd_create_workstream(args: argparse.Namespace) -> None:
"""Create a workstream under a domain's topic."""
_api_get("/state/health")
@@ -406,6 +441,12 @@ def main() -> None:
help="Project directory (defaults to current directory)",
)
# ingest-sbom
ing = sub.add_parser("ingest-sbom", help="Ingest SBOM for the repo at the current directory")
ing.add_argument("--path", default=os.getcwd(), help="Repo directory (defaults to cwd)")
ing.add_argument("--slug", default=None, help="Repo slug (auto-detected from path if omitted)")
ing.add_argument("--dry-run", action="store_true", help="Parse lockfiles but do not submit to API")
# create-workstream
cws = sub.add_parser("create-workstream", help="Create a workstream under a domain topic")
cws.add_argument("--domain", required=True, help="Domain slug to create the workstream under")
@@ -429,6 +470,8 @@ def main() -> None:
if args.command == "register-project":
cmd_register(args)
elif args.command == "ingest-sbom":
cmd_ingest_sbom(args)
elif args.command == "create-workstream":
cmd_create_workstream(args)
elif args.command == "create-task":