Skip to content

Initial implementation: Patchwave analysis CLI - #1

Merged
blimmer merged 7 commits into
mainfrom
initial-implementation
May 24, 2026
Merged

Initial implementation: Patchwave analysis CLI#1
blimmer merged 7 commits into
mainfrom
initial-implementation

Conversation

@blimmer

@blimmer blimmer commented May 24, 2026

Copy link
Copy Markdown
Contributor

Initial implementation of patchwave-analysis — a diagnostic CLI that measures Dependabot toil and CVE exposure across a GitHub org.

What it does

Crawls an org's repos and, per repo, collects language mix, Dependabot config, security (CVE) alerts, branch protection, active human contributors, and Dependabot PR history. It aggregates these into a cost model and renders a single self-contained HTML report plus a share-back zip of the raw data behind every metric.

Structure

  • Collectors (src/collectors/) — one per data source, each returning Result/ResultAsync; partial per-repo failures are logged as warnings and don't kill the run.
  • GitHub client (src/github/) — Octokit (REST + GraphQL) with retry/throttle, errors mapped to a flat discriminated union.
  • Report pipeline (src/report/) — aggregation, cost formulas, bundling, and HTML render that embeds the data into a React app.
  • Web report (src/report/web/) — React UI bundled to one HTML file.
  • Core infra (src/) — Context-based dependency injection with XxxImpl/FakeXxx for IO, clock, filesystem, analytics.

Conventions

  • neverthrow throughout (no try/catch in business logic; the CLI entrypoint is the only place that unwraps).
  • Fishery test factories; bun test for unit (*.test.ts), vitest browser tests for React components (*.browser.test.tsx).
  • Bun-native APIs, signed release builds via goreleaser.

Gates

lint, typecheck, and test (76 bun + 6 vitest browser) all pass.

Opening as a draft for review.

blimmer added 7 commits May 24, 2026 10:01
Establish the build and contribution scaffolding ahead of the
implementation:

- package.json/bun.lock: runtime and dev dependencies
- tsconfig.json: strict TS config with #src path alias
- eslint.config.mjs, .prettierignore: lint/format config
- bunfig.toml, vitest.config.ts, vitest.setup.ts: test runner config
- .github workflows + bootstrap action: PR/main/test CI
- .husky pre-commit/pre-push hooks
- .claude/rules: team conventions (neverthrow, Context/fakes,
  bun-native APIs, testing patterns)
- scripts/ensure-test-template.ts: test scaffolding helper
The injectable-dependency foundation everything else builds on,
following the Context + XxxImpl/FakeXxx convention.

- types.ts: shared domain types for collected data and the report
- context.ts: Context interface + createContext() wiring
- environment.ts, errors.ts, time.ts, concurrency.ts, logger.ts:
  cross-cutting primitives (env parsing, error helpers, Temporal
  wrappers, bounded concurrency, structured logging)
- Clock, FileSystem, BaseIo/IoImpl: I/O dependency interfaces +
  production implementations
- github/: GithubClient (REST/GraphQL), token resolution (auth.ts),
  and discriminated-union error mapping (errors.ts)
- Analytics + anonymousId: opt-out PostHog telemetry with a stable
  anonymous id
Deterministic test doubles implementing the Context dependency
interfaces, swapped in without module mocking:

- testHelpers/Fake{Analytics,Clock,FileSystem,GithubClient,Io}.ts:
  inspectable fakes for each injectable dependency
- testHelpers/MemoryStream.ts: in-memory writable for IO assertions
- testHelpers/createFakeContext.ts: assembles a fully-faked Context
- testFactories.ts: builders for domain fixtures used across tests
Per-domain GitHub collectors plus the classification heuristics they
feed. Each collector accepts its client/IO via Context and returns a
Result; per-repo failures are surfaced as warnings rather than
aborting the run.

Collectors:
- repos: org repo listing + language breakdown
- contributors: active committers
- branchProtection: required-checks / protection rules
- dependabotConfig: presence and shape of dependabot config
- dependabotPrs: Dependabot PR history
- cve: security/CVE alerts
- reverts: revert detection, indexed against Dependabot PRs

Heuristics:
- bumpType: semver bump classification + dev-dependency detection
Turns collected data into the shippable report artifact.

- aggregate.ts: folds collector output into the ReportBundle —
  per-repo and org-wide rollups, time series, risk/automation slices
- costFormulas.ts: labor/cost model used by the savings estimates
- bundle.ts: assembles report files and zips them (fflate)
- html.ts: renders the single-file HTML report, inlining the built
  web bundle (dist/report-web) as a text import
- testFactories.ts: report-layer fixture builders
The interactive single-page report embedded into the HTML artifact.

- web/App.tsx + acts/ (Verdict, CostStory, RiskStory, AutomatedStory,
  CallToAction, MethodologyAppendix): the narrative report sections
- web/primitives/ (charts, citation, brand mark, rows): presentational
  building blocks
- web/data/: reads the embedded ReportBundle + citations via context
- web/hooks/useAssumptions: user-tweakable cost assumptions
- web/format/: bytes/days/money formatters
- web/index*.{html,tsx}: production + dev (HMR) entrypoints, styles,
  and fixtures
- scripts/build-report-web.ts: bundles the web app into dist/report-web
Wires the collectors and report pipeline behind a CLI and documents
the project.

- src/index.ts: bin entrypoint — resolves token, builds Context,
  sets up telemetry, delegates to main()
- src/cli.ts: arg parsing, the collect -> aggregate -> render/bundle
  orchestration, and human-facing error formatting (the only place
  Results are unwrapped)
- removes the placeholder root index.ts
- README.md: usage and overview; CLAUDE.md: project conventions
@blimmer
blimmer merged commit 073dccb into main May 24, 2026
2 checks passed
@blimmer
blimmer deleted the initial-implementation branch May 24, 2026 16:06
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