Skip to content

Add per-ecosystem record counts to scan_summary (#52)#63

Open
Gi-v wants to merge 1 commit into
perplexityai:mainfrom
Gi-v:feature/my-issue-fix
Open

Add per-ecosystem record counts to scan_summary (#52)#63
Gi-v wants to merge 1 commit into
perplexityai:mainfrom
Gi-v:feature/my-issue-fix

Conversation

@Gi-v

@Gi-v Gi-v commented Jul 5, 2026

Copy link
Copy Markdown

PR Description

## Summary

Populates `scan_summary.counts` with one key per ecosystem that emitted
at least one package record, in addition to the existing `package` and
`finding` keys.

Fixes #52

## Changes

- Added `scanner.Result.RecordsByEcosystem map[string]int`, populated
  from the `emit` closure after `ObservePackage` returns
  `written=true` — mirroring the existing `RecordsEmitted` semantics
  exactly, so dedup-suppressed records and `--findings-only` records
  are never double-counted.
- In `cmd/bumblebee/runScan`, merged `RecordsByEcosystem` into the
  existing `counts` map before `EmitSummary` is called.
- Added `model.Counts`, a `map[string]int` alias with a custom
  `MarshalJSON` that emits keys in a fixed order — `package`,
  `finding`, then each ecosystem in `model.SupportedEcosystemOrder` —
  instead of Go's default alphabetical map-key ordering, so
  `scan_summary.counts` key order is deterministic across runs and
  hosts regardless of Go's randomized map iteration.
- Updated `ScanSummary.Counts` to use the new `model.Counts` type.
- No schema change: `docs/schema/v0.1.0/scan-summary.schema.json`
  already declares `"counts": { "type": "object",
  "additionalProperties": { "type": "integer" } }`, so new keys are
  schema-legal today.
- Documented the per-ecosystem keys in `README.md` next to the
  existing `package` / `finding` description.
- Added `TestRecordsByEcosystem` in `internal/scanner/scanner_test.go`:
  a multi-ecosystem (npm/go/rubygems) fixture scan asserting
  `Result.RecordsByEcosystem` has the expected per-ecosystem counts,
  that dedup on a re-run against the same emitter zeroes it out, and
  that `--findings-only` suppresses it entirely.
- Added `TestRunScanSummaryCountsDeterministicOrder` in
  `cmd/bumblebee/main_test.go`: an end-to-end test that runs a real
  multi-ecosystem scan through `runScan`, reads the emitted
  `scan_summary` line from disk, and asserts `package`, `finding`, and
  the ecosystem keys appear in the on-the-wire JSON in
  `model.SupportedEcosystemOrder` — not Go's default alphabetical
  order — closing the gap the scanner-level test alone couldn't cover.

## Acceptance criteria

- [x] `scanner.Result.RecordsByEcosystem` is populated for every
  successful scan.
- [x] `scan_summary.counts` contains one key per ecosystem that
  emitted at least one package record, in addition to the existing
  `package` and `finding` keys.
- [x] `--findings-only` and dedup-suppressed records do not inflate
  per-ecosystem counts (same semantics as `package_records_emitted`).
- [x] The order of keys in the emitted `counts` map is deterministic
  across runs (stable sort by `model.SupportedEcosystemOrder`).
- [x] Default `bumblebee scan` output for an existing single-ecosystem
  scan is byte-identical except for the new keys in `scan_summary.counts`.
- [x] `README.md` documents the new keys.
- [x] New test in `internal/scanner/scanner_test.go` asserts
  per-ecosystem counts on a multi-ecosystem fixture scan.
- [x] `go test ./...` passes; `gofmt -l .` prints nothing.

## Testing

```sh
gofmt -l .
go build ./...
go test ./...
go test -race ./internal/scanner/... ./cmd/...
go test ./internal/scanner/... -run TestRecordsByEcosystem -v
go test ./cmd/bumblebee/... -run TestRunScanSummaryCountsDeterministicOrder -v

All pass, including -race.

Example output

"counts": {
  "package": 5421,
  "finding": 3,
  "npm": 4210,
  "pypi": 873,
  "go": 234,
  "rubygems": 87,
  "packagist": 12,
  "mcp": 5
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant