generated from coulomb/repo-seed
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:
@@ -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":
|
||||
|
||||
@@ -70,11 +70,12 @@ declaring `ansible` as a dependency. Fix: create the manifest.
|
||||
**Type B — Manifest without lockfile**: a `pyproject.toml` or `package.json`
|
||||
exists but no lockfile has been generated. Fix: run `uv lock` / `npm install`.
|
||||
|
||||
**Type C — Lockfile not ingested**: lockfile exists but `make ingest-sbom` has
|
||||
not been run, so the State Hub has no record. Fix: run `make ingest-sbom`.
|
||||
**Type C — Lockfile not ingested**: lockfile exists but `custodian ingest-sbom`
|
||||
has not been run, so the State Hub has no record. Fix: run `custodian ingest-sbom`
|
||||
from the repo root.
|
||||
|
||||
**Type D — Stale ingest**: lockfile exists and was ingested, but has since been
|
||||
updated (new deps added) without a fresh ingest. Fix: re-run `make ingest-sbom`.
|
||||
updated (new deps added) without a fresh ingest. Fix: re-run `custodian ingest-sbom`.
|
||||
|
||||
**Type E — Ecosystem not supported**: the repo uses an ecosystem the ingest
|
||||
script doesn't yet parse (Go, Java, Ruby, Ansible Galaxy collections). The
|
||||
@@ -92,7 +93,7 @@ uv add ansible # adds dep + resolves transitive tree
|
||||
uv lock # generates or updates uv.lock
|
||||
git add pyproject.toml uv.lock && git commit
|
||||
```
|
||||
Then ingest: `make ingest-sbom REPO=<slug> SCAN=1 REPO_PATH=<path>`
|
||||
Then ingest: `custodian ingest-sbom` (from the repo root)
|
||||
|
||||
### Node / npm
|
||||
`package-lock.json` is generated automatically by `npm install`. Commit it.
|
||||
@@ -168,6 +169,22 @@ See the full standard: [`/docs/inter-repo-communication`](/docs/inter-repo-commu
|
||||
|
||||
## Ingest commands
|
||||
|
||||
### From the repo root (recommended)
|
||||
|
||||
```bash
|
||||
# Scan all lockfiles in the current repo and ingest
|
||||
custodian ingest-sbom
|
||||
|
||||
# Dry run — parse and report without submitting
|
||||
custodian ingest-sbom --dry-run
|
||||
```
|
||||
|
||||
`custodian ingest-sbom` looks up the repo slug from the State Hub registration
|
||||
(`local_path` match), then scans the whole tree for all supported lockfile
|
||||
formats. The repo must be registered first — see `custodian register-project`.
|
||||
|
||||
### From the state-hub directory (low-level)
|
||||
|
||||
```bash
|
||||
# Auto-detect lockfile at repo root
|
||||
make ingest-sbom REPO=<slug> REPO_PATH=/path/to/repo
|
||||
|
||||
@@ -143,7 +143,7 @@ display(html`<div class="kpi-row">
|
||||
// Returns a new "⚠ not ingested" span with a ? help button each time it's called.
|
||||
function _sbomGap() {
|
||||
const el = html`<span class="sbom-warn sbom-gap-hint">⚠ not ingested</span>`;
|
||||
withDocHelp(el, "/docs/repos");
|
||||
withDocHelp(el, "/docs/sbom");
|
||||
return el;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user