From 0b8f98935c7370710e5ca252e7be046e53add021 Mon Sep 17 00:00:00 2001 From: Ben Limmer Date: Mon, 25 May 2026 10:53:45 -0600 Subject: [PATCH 1/2] chore: add just task runner with verify/build/release recipes Port the planbridge task-runner convention to this repo. `just verify` becomes the canonical full-check entrypoint (format:check, typecheck, lint, test) and is what agents run before marking work complete. Other recipes (install, bootstrap, run, build, dev, storybook) wrap existing package.json scripts, and a release.just mod adds a goreleaser dry-run. Pin just in .tool-versions alongside bun. --- .tool-versions | 1 + just/release.just | 9 +++++++++ justfile | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 just/release.just create mode 100644 justfile diff --git a/.tool-versions b/.tool-versions index 0d1c574..d4ec180 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1,2 @@ bun 1.3.14 +just 1.49.0 diff --git a/just/release.just b/just/release.just new file mode 100644 index 0000000..0913245 --- /dev/null +++ b/just/release.just @@ -0,0 +1,9 @@ +# Release recipes +# All recipes run from the project root so scripts/ and .goreleaser.yaml resolve correctly. + +set working-directory := '..' + +# Build all release artifacts locally without publishing or notarizing. +# Requires `goreleaser` on PATH (brew install goreleaser/tap/goreleaser). +dry-run: + goreleaser release --snapshot --clean --skip=publish,notarize diff --git a/justfile b/justfile new file mode 100644 index 0000000..d82059d --- /dev/null +++ b/justfile @@ -0,0 +1,40 @@ +# patchwave-analysis justfile + +mod release 'just/release.just' + +# Default recipe - list available commands +default: + @just --list + +# Install dependencies +install: + bun install {{ if env("CI", "") != "" { "--frozen-lockfile" } else { "" } }} + +# Full verification: format + typecheck + lint + test +verify: install + bun run format:check + bun run typecheck + bun run lint + bun run test + +# Bootstrap development environment (asdf toolchain, deps, git hooks) +bootstrap: + asdf install + just install + +# Run the CLI from source (builds the embedded report first). Pass an org/user as an argument. +run *args: + bun run start {{ args }} + +# Compile a host-platform binary (for cross-platform artifacts use `just release dry-run`) +build: + bun run build:report-web + bun build --compile ./src/index.ts --outfile dist/patchwave-analysis + +# Run the embedded report UI dev server with HMR +dev: + bun run dev:report-web + +# Run Storybook for the report UI (http://localhost:6006) +storybook: + bun run storybook From fc0f03cd3fc85bc1a716f75cf340de65e7c8332c Mon Sep 17 00:00:00 2001 From: Ben Limmer Date: Mon, 25 May 2026 10:53:52 -0600 Subject: [PATCH 2/2] docs: adopt AGENTS.md instructions and add CONTRIBUTING Make AGENTS.md the canonical project doc (overview, layout, verification, PR rules, conventions linking every .claude/rules file) and reduce CLAUDE.md to an `@AGENTS.md` stub, matching planbridge. Add a tailored CONTRIBUTING.md for the dev/test/release workflow and refocus README on CLI consumers, pointing contributors to CONTRIBUTING.md. --- AGENTS.md | 85 ++++++++++++++++++++++++++++++++++ CLAUDE.md | 118 +----------------------------------------------- CONTRIBUTING.md | 77 +++++++++++++++++++++++++++++++ README.md | 15 +++--- 4 files changed, 169 insertions(+), 126 deletions(-) create mode 100644 AGENTS.md create mode 100644 CONTRIBUTING.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..774261e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,85 @@ +# AGENTS.md + +## 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 plus a raw-data zip to a temporary directory. No data leaves the user's network unless they choose to share the generated artifacts. + +The single entrypoint is `patchwave-analysis []` — an interactive session that prompts for the target if omitted, then for what to share when the scan finishes. There are no other flags; the time window (90 days) is fixed. + +## Stack + +- **Runtime:** Bun (the binary is `bun build --compile`d; see `.goreleaser.yaml`). +- **Language:** TypeScript (strict). +- **Layout:** single package, flat `src/` tree. No workspaces, no monorepo. +- **Report UI:** React + Tailwind, built into `dist/report-web/index.html` and embedded into the binary via a `with { type: 'text' }` import. Browser tests use **vitest** (browser mode via Playwright/Chromium); CLI tests use **bun:test**. + +## Repository layout + +``` +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 +│ ├── upload/ artifact sharing +│ ├── testHelpers/ shared test utilities +│ ├── context.ts CliContext DI root (see Conventions) +│ ├── cli.ts arg parsing + main() +│ └── index.ts bootstrap: telemetry/logger/analytics wiring, then main() +├── scripts/ analyze.sh installer + build-report-web.ts +├── .goreleaser.yaml cross-platform compile + macOS notarize +└── justfile root-level recipes +``` + +## Verification + +Before marking a task complete, run `just verify` and fix anything that fails. It runs four steps in order: + +- `bun run format:check` — Prettier +- `bun run typecheck` — strict `tsc --noEmit` (builds the web report first) +- `bun run lint` — ESLint (`--max-warnings 0`) +- `bun run test` — dispatches `test:unit` (bun:test over `src/**/*.test.ts`) then `test:browser` (vitest) + +Do **not** run a bare `bun test` at the repo root — Bun's runner would walk the report UI's `.test.tsx` files, which depend on a real DOM and only run under vitest. Use `bun run test` (the dispatch script) or a targeted `bun test ./src//foo.test.ts` during iteration. + +## Pull requests + +Before opening a PR, read `.github/pull_request_template.md` and follow it exactly — title rules (Conventional Commits, chosen by changelog visibility per `release-please-config.json`), the Summary/Review-focus/Commits sections, and the commit-hygiene guidance. + +## Conventions + +The following files under `.claude/rules/` carry team conventions enforced for `src/`. Read the relevant one before editing matching files: + +- [Error Handling Neverthrow](.claude/rules/error-handling-neverthrow.md) — no try/catch in business logic; wrap fallible I/O in `Result` / `ResultAsync`. +- [Context Interfaces and Fakes](.claude/rules/context-interfaces-and-fakes.md) — injected dependencies get a narrow public interface plus a separate real implementation. +- [Testing Patterns](.claude/rules/testing-patterns.md) — test data comes from Fishery factories (`testFactories.ts`), never hand-rolled `createXxx()` helpers. +- [Bun-native APIs](.claude/rules/bun-native-apis.md) — reach for `Bun.*` globals before the Node equivalent. +- [Bun testing](.claude/rules/bun-testing.md) — non-obvious `bun:test` conventions. + +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. +- **`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. + +### File naming + +- **camelCase** for `.ts` / `.tsx` files (`cli.ts`, `context.ts`, `tokenWalkthrough.ts`). +- **PascalCase when a file's primary export is a module-level class**, matching the class name (`Telemetry.ts` exports `class …`, `IoImpl.ts`, `BrowserOpener.ts`). Test files follow the same casing. +- Tooling-mandated filenames (`tsconfig.json`, `package.json`, `eslint.config.mjs`, workflow files, etc.) follow upstream conventions. +- Directory names are lowercase. Tests are co-located next to the implementation (`cli.ts` → `cli.test.ts`); no `__tests__/` or top-level `tests/` tree. + +### Imports + +Use relative imports with explicit `.ts` / `.tsx` extensions (`import { main } from './cli.ts';`). This is a single package — there are no subpath (`#src/*`) or cross-package (`@scope/*`) imports. + +## 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. Org names, repo names, tokens, report contents, and the machine hostname are never sent — see the "Telemetry & privacy" section of `README.md` for the full guarantee. diff --git a/CLAUDE.md b/CLAUDE.md index bd06cdd..43c994c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,117 +1 @@ ---- -description: Use Bun instead of Node.js, npm, pnpm, or vite. -globs: '*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json' -alwaysApply: false ---- - -Default to using Bun instead of Node.js. - -- Use `bun ` instead of `node ` or `ts-node ` -- Use `bun test` instead of `jest` or `vitest` -- Use `bun build ` instead of `webpack` or `esbuild` -- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` -- Use `bun run - - -``` - -With the following `frontend.tsx`: - -```tsx#frontend.tsx -import React from "react"; -import { createRoot } from "react-dom/client"; - -// import .css files directly and it works -import './index.css'; - -const root = createRoot(document.body); - -export default function Frontend() { - return

Hello, world!

; -} - -root.render(); -``` - -Then, run index.ts - -```sh -bun --hot ./index.ts -``` - -For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`. - -## Conventions - -Team rules under `.claude/rules/` apply to files under `src/`. Read them before editing matching files: - -- [Error Handling Neverthrow](.claude/rules/error-handling-neverthrow.md) — no try/catch in business logic; use `Result` / `ResultAsync` +@AGENTS.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c44f6c3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,77 @@ +# Contributing to patchwave-analysis + +## Setup + +You need [`asdf`](https://asdf-vm.com/) installed and on your `PATH` (it manages the `bun` and `just` versions pinned in `.tool-versions`). Then: + +```sh +git clone https://github.com/contextbridge/patchwave-analysis +cd patchwave-analysis +asdf install +just bootstrap # asdf toolchain + bun install (husky git hooks install on bun install) +just run -- --help +``` + +## Repo layout + +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 +``` + +## Development + +Run the CLI locally (this builds the embedded report first): + +```sh +just run # prompts for an org/user +just run contextbridge # scan a specific org/user +``` + +Iterate on the embedded report UI with HMR, or browse components in Storybook: + +```sh +just dev # report UI dev server (bun run dev:report-web) +just storybook # http://localhost:6006 +``` + +## Testing + +Run all checks with `just verify` (format, typecheck, lint, tests). For individual steps: + +1. `bun run format` (Prettier) +2. `bun run typecheck` +3. `bun run lint` (ESLint) +4. `bun run test` — `test:unit` (bun:test over `src/**/*.test.ts`) then `test:browser` (vitest for the report UI) + +Don't run a bare `bun test` at the repo root: Bun's runner walks the report UI's `.test.tsx` files, which only run under vitest. Use `bun run test`, or target a single file with `bun test ./src//foo.test.ts`. + +## Coding conventions + +See [`AGENTS.md`](./AGENTS.md) and the rule files under [`.claude/rules/`](./.claude/rules/). + +## Pull requests + +1. Use [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `chore:`, etc.) for PR titles. The `Lint PR title` check enforces this, and the prefix decides changelog visibility (see `release-please-config.json`). +2. Follow the PR template [`.github/pull_request_template.md`](./.github/pull_request_template.md). +3. Ensure `just verify` runs without errors before opening the PR. + +## Releases + +Releases are cut by the ContextBridge team. Stable releases are automated by [release-please](https://github.com/googleapis/release-please) feeding into [goreleaser](https://goreleaser.com/): + +- On every push to `main`, release-please opens (or updates) a release PR that bumps `CHANGELOG.md` from conventional-commit titles since the last release. Merging it creates the tag; goreleaser then compiles, signs/notarizes, and attaches the binaries. Don't edit `CHANGELOG.md` by hand. +- To validate the release build locally without publishing or notarizing, run `just release dry-run` (requires `goreleaser` on `PATH`). + +## Code of conduct + +Be kind. Assume good faith. If something feels off, email [`support@contextbridge.ai`](mailto:support@contextbridge.ai). diff --git a/README.md b/README.md index 2e6dcd2..e5310c8 100644 --- a/README.md +++ b/README.md @@ -45,15 +45,8 @@ tar -xzf patchwave-analysis_darwin_arm64.tar.gz ```sh git clone https://github.com/contextbridge/patchwave-analysis cd patchwave-analysis -bun install -bun run build:report-web -bun run src/index.ts -``` - -For local report UI development: - -```sh -bun run dev:report-web +just install +just run ``` ## Usage @@ -119,6 +112,10 @@ Both are disabled together by setting any of: When disabled, no anonymous-id file is created, no analytics events are sent, and Sentry is never initialized. +## Contributing + +Development setup, testing, and release workflow live in [`CONTRIBUTING.md`](./CONTRIBUTING.md). + ## License MIT.