Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 2 additions & 32 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
import './preview.css';
import type { Decorator, Preview } from '@storybook/react-vite';

type Theme = 'system' | 'light' | 'dark';

// Force a color scheme by toggling the .theme-* classes styles.css reacts to.
// A story can pin one via `parameters.theme`; otherwise the toolbar global wins.
const withTheme: Decorator = (Story, ctx) => {
const theme = (ctx.parameters.theme as Theme | undefined) ?? (ctx.globals.theme as Theme) ?? 'system';
const root = document.documentElement;
root.classList.remove('theme-light', 'theme-dark');
if (theme === 'light') root.classList.add('theme-light');
if (theme === 'dark') root.classList.add('theme-dark');
return <Story />;
};
import type { Preview } from '@storybook/react-vite';

const preview: Preview = {
parameters: {
Expand All @@ -25,27 +12,10 @@ const preview: Preview = {
backgrounds: { disable: true },
options: {
storySort: {
order: ['Report', ['App', ['Light', 'Dark']]],
},
},
},
globalTypes: {
theme: {
description: 'Color scheme',
defaultValue: 'system',
toolbar: {
title: 'Theme',
icon: 'mirror',
items: [
{ value: 'system', title: 'System' },
{ value: 'light', title: 'Light' },
{ value: 'dark', title: 'Dark' },
],
dynamicTitle: true,
order: ['Report', ['App', 'Sample']],
},
},
},
decorators: [withTheme],
};

export default preview;
20 changes: 10 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Project: patchwave-analysis

A diagnostic CLI that measures Dependabot toil and CVE exposure across a GitHub org. It runs in the user's environment, crawls `api.github.com`, and writes a self-contained HTML report to a temporary directory. No data leaves the user's network unless they choose to share the generated report.
A diagnostic CLI that measures Dependabot toil and CVE exposure across a GitHub org. It runs in the user's environment, crawls `api.github.com`, and writes a self-contained HTML report to a temporary directory. The report never leaves the user's machine.

The single entrypoint is `patchwave-analysis [<org-or-user>]` — an interactive session that prompts for the target if omitted, then opens the generated report when the scan finishes. There are no other flags; the time window (90 days) is fixed.

Expand All @@ -20,12 +20,12 @@ patchwave-analysis/
├── src/
│ ├── collectors/ per-slice GitHub data collectors (repos, PRs, CVEs, …)
│ ├── github/ Octokit client wiring (retry + throttling plugins)
│ ├── heuristics/ derived metrics (toil cost, automation upside, …)
│ ├── interactive/ Clack prompts: token walkthrough, share/open, banner, TTY gate
│ ├── prompt/ Prompter abstraction over @clack/prompts
│ ├── report/ report aggregation + `report/web/` React UI
│ ├── testHelpers/ shared test utilities
│ ├── context.ts CliContext DI root (see Conventions)
│ ├── context/ Context DI root + injected dependencies (Prompter, Io, FileSystem, …)
│ ├── heuristics/ PR classification (bump type)
│ ├── interactive/ Clack prompts: token walkthrough, scope gate, banner, report open, TTY gate
│ ├── report/ report aggregation, cost formulas + `report/web/` React UI
│ ├── telemetry/ PostHog analytics + Sentry crash reporting
│ ├── testHelpers/ shared test utilities and fakes
│ ├── cli.ts arg parsing + main()
│ └── index.ts bootstrap: telemetry/logger/analytics wiring, then main()
├── scripts/ analyze.sh installer + build-report-web.ts
Expand Down Expand Up @@ -61,14 +61,14 @@ The following files under `.claude/rules/` carry team conventions enforced for `

Beyond the rules:

- **Dependency injection is non-negotiable.** All business logic flows through the typed `CliContext` (`src/context.ts`). Prefer explicit context-object wiring over module-level singletons or global mocking.
- **Dependency injection is non-negotiable.** All business logic flows through the typed `Context` (`src/context/index.ts`). Prefer explicit context-object wiring over module-level singletons or global mocking.
- **`ctx` always comes first, and is destructured at the point of use.** Helpers that take a context list it as the first parameter (`fn(ctx, other)`); pull the fields you need at the top of the body (`const { io, logger } = ctx;`) rather than reaching through `ctx.io.stdout` at each call site. This narrows each function to the surface it depends on and keeps test stubs honest.
- **Business output (stdout) and diagnostics (logger → stderr) stay on separate channels** so piped consumers see clean stdout while humans get readable logs.
- **Use Temporal for all time handling.** Use `@js-temporal/polyfill` via `src/time.ts`, keep in-process values as Temporal objects, serialize only ISO strings at JSON boundaries, and do not use `Date`.
- **In Zod string schemas, prefer `.nonempty()` over `.min(1)`** (`.trim().nonempty()` when surrounding whitespace should not count).
- **Destructured defaults over `??` fallbacks.** Apply defaults in a single destructuring assignment — `const { version = '0.0.1' } = input;`, not per-field `??`.
- **Helpers at the bottom of files.** Primary exports come first; module-local helpers and factories sit below them. In test files they live after all `describe()` blocks.
- **The product is always "PatchWave" in prose and UI copy** — capital P and W, one word. Never "patchwave", "Patchwave", or "patch wave". The only lowercase forms are literal identifiers that must match their real-world spelling: the `patchwave-analysis` CLI/binary name, the `patchwave-report.html` artifact, and the `patchwave.ai` domain.
- **The product is always "PatchWave" in prose and UI copy** — capital P and W, one word. Never "patchwave", "Patchwave", or "patch wave". The only lowercase forms are literal identifiers that must match their real-world spelling: the `patchwave-analysis` CLI/binary name, the `patchwave-report.html` artifact, and the `patchwave.ai` domain. This CLI is "PatchWave Dependency Checkup" (or "the checkup") in prose; "PatchWave" alone means the GitHub app it promotes.

### File naming

Expand All @@ -83,4 +83,4 @@ Use relative imports with explicit `.ts` / `.tsx` extensions (`import { main } f

## Telemetry

Instrumentation (PostHog analytics + Sentry crash reporting, gated by build-time keys) is wired in `src/index.ts`. `Sentry.init` must run before `createLogger` so its `pinoIntegration` subscribes to pino's diagnostics channel first. Crash reports omit automatic request data and the machine hostname, but include error messages and stack traces; those may contain org names, repo names, tokens, or other sensitive details. See the "Telemetry & privacy" section of `README.md` for the user-facing disclosure.
Instrumentation (PostHog analytics + Sentry crash reporting, gated by build-time keys) is wired in `src/index.ts`. `Sentry.init` must run before `createLogger` so its `pinoIntegration` subscribes to pino's diagnostics channel first. Crash reports omit automatic request data and the machine hostname, but include error messages and stack traces; those may contain org names, repo names, tokens, or other sensitive details. The generated report sends no analytics; report clicks are measured through the campaign tags in `src/report/web/reportCtaUrls.ts`. See the "Telemetry & privacy" section of `README.md` for the user-facing disclosure.
15 changes: 8 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Contributing to patchwave-analysis
# Contributing to PatchWave Dependency Checkup

## Setup

Expand All @@ -20,12 +20,13 @@ This is a single package with a flat `src/` tree:
src/
├── collectors/ per-slice GitHub data collectors (repos, PRs, CVEs, …)
├── github/ Octokit client wiring (retry + throttling)
├── heuristics/ derived metrics (toil cost, automation upside, …)
├── interactive/ Clack prompts (token walkthrough, share/open, banner, TTY gate)
├── prompt/ Prompter abstraction over @clack/prompts
├── report/ report aggregation + report/web/ React UI
├── upload/ artifact sharing
└── *.ts CLI bootstrap, context (DI root), Octokit/IO/telemetry plumbing
├── context/ Context DI root + injected dependencies (Prompter, Io, FileSystem, …)
├── heuristics/ PR classification (bump type)
├── interactive/ Clack prompts (token walkthrough, scope gate, banner, report open, TTY gate)
├── report/ report aggregation, cost formulas + report/web/ React UI
├── telemetry/ PostHog analytics + Sentry crash reporting
├── testHelpers/ shared test utilities and fakes
└── *.ts CLI bootstrap (index.ts), arg parsing + main() (cli.ts), shared types
```

## Development
Expand Down
38 changes: 28 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# [PatchWave](https://patchwave.ai) Analysis
# PatchWave Dependency Checkup

PatchWave Analysis is a diagnostic CLI that measures Dependabot toil and CVE exposure across a GitHub org. It reads from the GitHub API and writes a self-contained HTML report you can use on its own, no PatchWave account needed.
PatchWave Dependency Checkup is a diagnostic CLI that measures Dependabot toil and CVE exposure across a GitHub org. It reads from the GitHub API and writes a self-contained HTML report you can use on its own, no PatchWave account needed.

![The top of a sample report for an org called acme. The headline reads "~131 hrs/year" spent triaging, reviewing, and merging Dependabot PRs. Below it, the report notes that 102 Dependabot updates are still open, 74 days on average, and that 7 known vulnerabilities are unpatched, including 1 critical and 3 high.](docs/report-screenshot.png)

## Run it

Expand All @@ -23,7 +25,7 @@ tar -xzf patchwave-analysis_darwin_arm64.tar.gz

Easiest path: run `gh auth login --scopes "repo,read:org"` (via [GitHub CLI](https://cli.github.com)) and you're done. The CLI also reads `GITHUB_TOKEN` and `GH_TOKEN`, so you can pass a token directly instead.

PatchWave needs a **classic** personal access token with two scopes:
The checkup needs a **classic** personal access token with two scopes:

- **`repo`** — your private repos and their pull-request data (this also covers `security_events` for the CVE numbers)
- **`read:org`** — listing the org's repos
Expand All @@ -39,12 +41,19 @@ Fine-grained tokens aren't supported, due to GitHub API restrictions. The CLI ch

The CLI only reads from the API. It never writes.

Any org member can run the checkup, but only org owners and security managers get the full CVE section. Other accounts see security alerts only for repos where GitHub already shows them Dependabot alerts.

## Troubleshooting

**The report shows $0, or no Dependabot PRs.** The token can't see your private
repos, usually because it's missing the **`repo`** scope (or it's a fine-grained
token, which isn't supported). PatchWave checks the token's scopes before scanning,
so an unexpected $0 almost always means a missing scope.
**The report shows $0, or no Dependabot PRs.** Check the selected repos and any
collection warnings in the report. The estimate counts human reviews and merges in
the last 90 days, so zero can be a valid result. The checkup rejects missing token
scopes before scanning, but a token with the right scopes can still lack access to
some repos or require your org's SSO authorization.

**The CVE section says your account can't read the org's alerts.** Only org
owners and security managers can read every repo's Dependabot alerts. Ask one of
them to run the checkup.

**Your org isn't in the list to pick from.** Choose **"Other (type a name)"** and
enter the org's login directly.
Expand All @@ -59,13 +68,20 @@ The report covers:
- **Toil cost:** annualized engineer-time, with assumptions you can adjust right in the browser
- **Automation upside:** projected savings with [PatchWave](https://patchwave.ai)

## What is PatchWave?

PatchWave is a GitHub app that automatically analyzes, fixes and merges your Dependabot PRs. It's free for 30 days, up to 100 PRs, no credit card.

- [Join the public beta](https://app.patchwave.ai/join?utm_source=patchwave-analysis&utm_medium=readme)
- [Learn more at patchwave.ai](https://patchwave.ai/?utm_source=patchwave-analysis&utm_medium=readme)

## What it reads from GitHub

Everything comes from `api.github.com` over a fixed 90-day window. After choosing a target, PatchWave asks whether to analyze all active repositories or choose a subset. For the selected repos (archived repos and forks are always skipped), it reads:
Everything comes from `api.github.com` over a fixed 90-day window. After choosing a target, the checkup asks whether to analyze all active repositories or choose a subset. For the selected repos (archived repos and forks are always skipped), it reads:

- The repo list, visibility, and primary-language metadata (plus whether Dependabot security updates are enabled)
- The open Dependabot PR backlog and PRs resolved in the window — state, timing, reviews, and who merged
- Open Dependabot security alerts, via the org-level endpoint
- Open Dependabot security alerts, from the org-level endpoint when your account can use it and from each repo otherwise

All calls are read only. It writes nothing back to GitHub and reads no repository file contents.

Expand All @@ -77,7 +93,9 @@ The report is one self-contained file with every metric baked in. It carries no

## Telemetry & privacy

We send anonymous usage events and crash reports so we can improve the tool. Crash reports omit the machine hostname, request data, cookies, and HTTP bodies. Error messages and stack traces are included and may themselves contain org or repo names, tokens, or other sensitive details; report contents are not collected, but details embedded in an error may be sent. The generated report stays on your machine.
The CLI sends anonymous usage events and crash reports so we can improve the tool. Usage events cover when a scan runs, how it ends, and counts such as repos and Dependabot PRs scanned; they never include org or repo names. Crash reports omit the machine hostname, request data, cookies, and HTTP bodies. Error messages and stack traces are included and may themselves contain org or repo names, tokens, or other sensitive details. See our [privacy policy](https://contextbridge.ai/privacy-policy).

The generated report stays on your machine and sends nothing. Its data, fonts, and images are embedded in the HTML file. Links to PatchWave include campaign tags that say which link you clicked, but no org or repo details.

To disable telemetry, set any of these in your environment:

Expand Down
Loading
Loading