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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ coverage/
.DS_Store
*.tsbuildinfo
*.docx

.ripple/
.bench/
*.cpuprofile
60 changes: 60 additions & 0 deletions BENCHMARKS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Benchmarks

Ripple ships an incremental parse cache (`.ripple/cache/`). These numbers
quantify what that buys in practice: repeated runs on an unchanged codebase
skip re-parsing, and output stays byte-identical between cold and warm runs.

## How to reproduce

```sh
pnpm build
node scripts/bench.mjs # sizes 200..2000, 3 repeats
node scripts/bench.mjs --repeats 5 # more repeats for stabler medians
node scripts/bench.mjs --files 2000 # single size
```

## Methodology

- **Synthetic layered projects** (`core → shared → domain → features → app`)
generated by `scripts/bench.mjs` with a seeded PRNG (seed 42), so every
run regenerates the same code. Real repos were deliberately not used so
results stay reproducible for anyone, anywhere.
- **Cold run**: `RIPPLE_NO_CACHE=1` — full re-parse, cache untouched.
- **Warm run**: normal invocation against the cache written by the cold run.
- Timing is wall-clock of the spawned process (median of N runs), measured
against the built bundle (`dist/bin.js`), never the dev loader.
- **Determinism**: every cold/warm pair must produce byte-identical JSON
(excluding `durationMs`) for the run to count; any drift shows as `NO`.

## Results

Machine: Windows 11 desktop (reference point only — expect noise on any
machine; the ratios and the determinism guarantee are the stable parts).

| files | command | cold (ms) | warm (ms) | speedup |
| ----: | :------ | --------: | --------: | ------: |
| 200 | graph | 1458 | 1214 | 1.20x |
| 200 | analyze | 1425 | 1207 | 1.18x |
| 500 | graph | 1660 | 1292 | 1.28x |
| 500 | analyze | 1778 | 1291 | 1.38x |
| 1000 | graph | 2045 | 1372 | 1.49x |
| 1000 | analyze | 1885 | 1224 | 1.54x |
| 2000 | graph | 2666 | 1634 | 1.63x |
| 2000 | analyze | 2509 | 1449 | 1.73x |

`analyze` beats `graph` because the impact traversal is near-free once the
graph is built; the win grows with repo size, which is exactly where
parse-time matters.

## Notes and caveats

- Small projects (≤500 files) show little absolute gain: process startup
(Node + bundle load) dominates both runs. The cache is worth less on a
repo Ripple can parse in ~1.5 s anyway.
- Absolute numbers are machine-local. CI sandboxes, antivirus scans and
cold page caches add noise — run `--repeats 5` before comparing.
- The cache is invalidated when discovery config (`include`/`ignore`)
changes; aliases/tsconfig changes re-run resolution but reuse parsing.
- `RIPPLE_TRACE=1` prints per-stage timings (`ts-project`, `discover`,
`cache.load-cache`, `cache.freshness+parse`, `cache.save`, `graph.*`)
for profiling a single run.
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,41 @@ All notable changes to Ripple are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Incremental parse cache (`.ripple/cache/`). Repeated `analyze`, `graph`,
`diff` and `doctor` runs only re-parse the files whose content changed —
everything else is served from the cached surface, so the stable JSON
contract stays byte-identical while large-codebase runs get faster.
Disable with `RIPPLE_NO_CACHE=1`. The cache is never scanned by discovery.
- Parsing is now fully error-tolerant: a broken file that trips an extractor
records a `parseError` instead of aborting the run, matching the documented
"lower confidence instead of failing" behavior.
- `ripple diff --format sarif` and `ripple analyze --sarif` — SARIF 2.1.0
output for GitHub Code Scanning. Every changed file becomes a finding
(`error` for CRITICAL/HIGH, `warning` for MEDIUM, `note` for LOW) with a
stable `primaryLocationLineHash` fingerprint for cross-run deduplication;
allowlisted files are emitted as `note` with an in-source suppression. The
gate verdict still rides on the exit code.
- `ripple mcp` — a Model Context Protocol server over stdio that exposes
Ripple's analysis as tools for AI coding agents: `impact` (blast radius of
a file), `dependents` (who imports a file, up to a depth), `risk` (score
with factor breakdown) and `gate_status` (does the current change set pass
the merge gate). Point any MCP client at `ripple mcp` to risk-check
refactors before they happen.

### Changed

- Cache freshness is checked with a cheap mtime+size match (file stats now
run in parallel) instead of re-hashing every file, and the cache is only
rewritten when something changed — repeated runs on an untouched tree get
~1.2x–1.7x faster with growing repo size instead of rewriting state.
Benchmark methodology and numbers: `BENCHMARKS.md`.
- `RIPPLE_TRACE=1` prints per-stage timings (pipeline, cache, graph) for
profiling single runs.

## [0.6.0] - 2026-08-13

### Added
Expand Down
57 changes: 56 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ ripple <command> [options]
| Command | Flag | Description |
| --------- | --------------------- | ---------------------------------------------------------------------------------------------- |
| `analyze` | `-j, --json` | Emit the JSON report instead of the terminal report |
| `analyze` | `--sarif` | Emit a SARIF 2.1.0 report for code scanning |
| `analyze` | `-v, --verbose` | Include the risk-factor point breakdown |
| `analyze` | `-d, --depth <n>` | Cap the reverse traversal at `n` levels |
| `analyze` | `-c, --config <path>` | Use a specific config file |
Expand All @@ -235,11 +236,16 @@ ripple <command> [options]
| `diff` | `-j, --json` | Emit the JSON report instead of the terminal report |
| `diff` | `-b, --base <ref>` | Git ref to diff against (default: `origin/main`, `main`, `HEAD~1`, or `diff.base` from config) |
| `diff` | `-g, --gate <level>` | Blocking level: `medium`, `high`, or `critical` (default: `high`, or `diff.gate` from config) |
| `diff` | `-f, --format <fmt>` | Output: `terminal`, `json`, or `github` (workflow annotations) |
| `diff` | `-f, --format <fmt>` | Output: `terminal`, `json`, `github` (workflow annotations), or `sarif` |
| `diff` | `-d, --depth <n>` | Cap reverse traversal per file |
| `diff` | `-c, --config <path>` | Use a specific config file |
| `diff` | `--no-color` | Disable ANSI colors |

### `ripple mcp`

Serve Ripple's analysis tools over the Model Context Protocol (see
[the MCP section](#ripple-mcp-1) below).

### `ripple doctor`

Independent health checks — config validity, tsconfig presence, source
Expand Down Expand Up @@ -333,6 +339,55 @@ verdict, so the job fails when the gate blocks:
(`--json` is shorthand for `--format json`; the format flag also accepts
`terminal`, the default.)

`--format sarif` emits SARIF 2.1.0, the format GitHub Code Scanning speaks:

```bash
ripple diff --format sarif > ripple.sarif
```

Each analyzed change becomes a finding on the file — `error` when it is
CRITICAL/HIGH, `warning` for MEDIUM, `note` for LOW — with a stable
`primaryLocationLineHash` fingerprint so results deduplicate across runs.
Allowlisted files are emitted as `note` with an in-source suppression, so
they show as exempted rather than failing. Upload the report straight into
Code Scanning alerts:

```yaml
- uses: actions/upload-sarif@v3
with:
sarif_file: ripple.sarif
```

The exit code still carries the gate verdict regardless of format.

### `ripple mcp`

`ripple mcp` exposes Ripple's analysis to AI coding agents as a Model
Context Protocol server over stdio. Point any MCP client at the command and
the agent can check a file's blast radius before touching it:

```json
{
"mcpServers": {
"ripple": { "command": "npx", "args": ["@alimaandev/ripple", "mcp"] }
}
}
```

Four tools are served:

| Tool | Inputs | Returns |
| ------------- | ------------------- | ----------------------------------------------------------------------- |
| `impact` | `file`, `maxDepth?` | Blast radius: affected files, routes, tests, components, risk level |
| `dependents` | `file`, `depth?` | Who imports the file, up to a depth (default 1, direct) |
| `risk` | `file` | Risk score with the factor breakdown behind it |
| `gate_status` | `base?`, `gate?` | Current change set vs the merge gate: files, levels, pass/block verdict |

A typical agent loop: `impact src/auth/session.ts` before refactoring,
`dependents` to see who breaks, then `gate_status` after editing to confirm
the gate still passes. Tool results are JSON text; failures return
`isError` results instead of crashing the session.

## Configuration

Ripple discovers `ripple.config.ts`, `.js`, `.cjs`, `.mjs`, or `.json` in the
Expand Down
10 changes: 10 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
prettier,
{
files: ["scripts/**/*.mjs"],
languageOptions: {
globals: {
process: "readonly",
console: "readonly",
performance: "readonly",
},
},
},
{
files: ["**/*.{ts,tsx}"],
languageOptions: {
Expand Down
Loading
Loading