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
115 changes: 115 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Contributing to askdiff

Thanks for poking at askdiff. This doc covers the dev loop, repo layout,
and the in-repo `/askdiff-dev` skill you'll use to test changes.

## Setup

```bash
git clone https://github.com/narghev/askdiff
cd askdiff
pnpm install
pnpm test
pnpm lint
pnpm run build
```

Node 24+ (current active LTS) and pnpm are the only requirements. There is
no Anthropic API key to set — askdiff shells out to the `claude` CLI.

## Architecture

The npm package (`packages/cli`) is a single esbuild-bundled Node binary
that hosts an HTTP server (serving the prebuilt UI bundle in `dist/ui/`)
and a WebSocket on the same port at `/ws`. The CLI imports `startServer`
from `@askdiff/server`, which spawns `claude --resume` per ask and
forwards `text_delta` events to the client. The browser UI
(`packages/ui-browser`) is React 19 + Vite + Tailwind v4 + zustand, with
`react-diff-view` for rendering and refractor for syntax highlighting.

`SPEC.md` has the full wire protocol, repository layout, and design
rationale. Read it before making changes that touch the protocol or the
launch flow.

## Dev loop

From a Claude Code session in this repo:

```
/askdiff-dev # first launch: Vite + WS server with HMR
/askdiff-dev # again: kills the WS server, restarts on same port with a fresh diff
/askdiff-dev last commit # description-driven: HEAD~1..HEAD
```

`/askdiff-dev` runs the in-repo TypeScript via `tsx` and pairs the WS
server with a local Vite dev server, so UI changes hot-reload. Use it
(not `/askdiff`) to test changes to the server, the CLI, or the
natural-language flow — `/askdiff` always pulls `npx -y askdiff@latest`,
so unpublished work won't run there.

The WS server idle-shuts after 5 min with no connected clients; Vite is
intentionally persistent (HMR is the whole point). Kill Vite via
Activity Monitor or `pkill -f 'ui-browser.*vite'` on the rare occasion
you want it gone.

To exercise the production-shaped binary locally:

```bash
pnpm run build
node packages/cli/dist/index.js --port 7838
```

## Configuration

The skill resolves everything for the normal `/askdiff` flow, so users
shouldn't ever need to set these. They exist for power use, debugging,
and running the CLI directly outside a Claude Code session.

| Variable | Default | Notes |
|---|---|---|
| `PORT` | `7837` | Auto-bumps if taken. |
| `ASKDIFF_SESSION_ID` | (resolved from `$PPID`) | Force a specific Claude Code session UUID. |
| `ASKDIFF_PROJECT_CWD` | (parent CC manifest, then `process.cwd()`) | Project directory to diff. |
| `ASKDIFF_MODEL` | (inherits resumed session's model) | Override the Claude model for asks. |
| `CLAUDE_CONFIG_DIR` | `~/.claude` | Where Claude Code stores `sessions/`, `projects/`. |
| `ASKDIFF_SKIP_UPDATE_CHECK` | unset | Set to `1` to suppress the post-launch npm version check. |

CLI flags also work (`askdiff --port 7838 --no-open --session <uuid>`);
run `askdiff --help` for the full list.

## Tests and lint

```bash
pnpm test # jest, all packages
pnpm test:watch
pnpm lint # eslint, all packages
pnpm --filter @askdiff/protocol exec tsc --noEmit
pnpm --filter @askdiff/server exec tsc --noEmit
pnpm --filter @askdiff/ui-browser exec tsc --noEmit
pnpm --filter @askdiff/ui-browser build # production build sanity check
```

Tests are co-located as `*.test.ts`. `@askdiff/ui-browser` deliberately
has no tests yet — the surface is too new and visual to lock in. Add
tests once the UX is stable; React Testing Library is the natural fit.

## Coding conventions

- Strict TypeScript — no `any`, no `ts-ignore`, no non-null assertions in hot paths.
- Named exports only (no default exports except entry points).
- Module-level functions in `util/` are arrow functions for consistency.
- Errors at the WS boundary are surfaced as `error` messages, never thrown across the socket.
- `zod.safeParse` on every incoming message; never trust raw input.
- Comments explain *why* — only when the why isn't obvious from the code or the SPEC.

See `CLAUDE.md` for the full set of conventions and the "do not" list
(e.g. don't reintroduce the Anthropic SDK, don't inject the diff into
the prompt, don't run git from the server).

## Pull requests

- Match existing style; run `pnpm lint` and `pnpm test` before pushing.
- Keep `.claude/skills/askdiff/SKILL.md` and `.claude/skills/askdiff-dev/SKILL.md`
in sync on Steps 1–4 prose, table, and routing — only the launch
command differs.
- If you change the wire protocol, update `SPEC.md` in the same PR.
227 changes: 40 additions & 187 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@ answer streams back inline — and because each ask resumes the same
Claude Code session that wrote the code, the model already remembers
the file, the conversation, and why it made the change.

![askdiff demo](https://raw.githubusercontent.com/narghev/askdiff/main/assets/output.gif)
![askdiff demo for natural language descriptors](https://raw.githubusercontent.com/narghev/askdiff/update-readme/assets/nl-descriptors-demo.gif)

> Asking about a past commit's diff in the session that wrote it.

## Why askdiff?

Developers often prompt AIs to write the code, then, if the diff becomes large enough, open draft GitHub PRs in order to review it better.
Then, if there are any questions, take the diff back to the terminal, ask questions about it, and repeat.

**Askdiff** simplifies this process by combining the diff viewer and the Q&A interface into one seamless experience, directly integrated with the very same Claude Code session that wrote the code. It makes you treat the model truly as a coworker who already knows the entire context and the reasoning behind every single line of the diff.

## Quickstart

Expand All @@ -30,20 +39,23 @@ npx -y askdiff install-skill --global

```
/askdiff # working-tree changes
/askdiff last commit # HEAD~1..HEAD
/askdiff main vs feature/x # main…HEAD (PR-style)
/askdiff David's latest commit where he removed the xmpp integration # author + content search
/askdiff last commit attached to the session where we discussed auth # send asks to a different past session
```

That's it. No API key, no config. The browser opens to a syntax-highlighted diff; comments stream back as the model thinks.

## Why askdiff?
## Use cases

Developers often prompt AIs to write the code, then, if the diff becomes large enough, open draft GitHub PRs in order to review it better.
Then, if there are any questions, take the diff back to the terminal, ask questions about it, and repeat.
### Review a past commit in the session that wrote it

**Askdiff** simplifies this process by combining the diff viewer and the Q&A interface into one seamless experience, directly integrated with the very same Claude Code session that wrote the code. It makes you treat the model truly as a coworker who already knows the entire context and the reasoning behind every single line of the diff.
The hero demo above shows this case: `/askdiff` with a natural-language description of the diff and session finds the right git invocation, captures the output, and points the server at it. Asks flow into that past session, so the model already wrote (or investigated) the code you're asking about — even if you're currently in a different session.

### Review code as you're writing it

`/askdiff` with no description shows the working tree diff — all uncommitted changes. Best used in the same session that made the changes, so it holds the full context of the edits.

![askdiff demo for the working directory path](https://raw.githubusercontent.com/narghev/askdiff/update-readme/assets/working-tree-demo.gif)

## How it works

Expand All @@ -54,34 +66,11 @@ So your question becomes a real turn in the running session's transcript:
full context that wrote the code; the prompt is just your question.
- **No Anthropic API key needed.** askdiff doesn't talk to the API —
it shells out to the `claude` CLI you've already auth'd via
subscription or whatever.
- **No process cleanup.** The server self-exits after 5 minutes of
subscription or API key.
- **Auto-cleanup.** The server self-exits after 5 minutes of
inactivity — close the browser tab and forget about it.

## Features

<table>
<tr>
<td><a href="#diff-selection">Diff selection</a></td>
<td>Describe which diff to review in plain English — working tree, last commit, branch comparisons, arbitrary refs.</td>
</tr>
<tr>
<td><a href="#session-selection">Session selection</a></td>
<td>By default asks flow into the invoking session. Add "in our session about X" or "session &lt;uuid&gt;" to attach to a different past session — the one that originally wrote the code, for example.</td>
</tr>
<tr>
<td><a href="#inline-comments">Inline comments</a></td>
<td>Click the <code>+</code> gutter button to comment on any line. Drag to comment on a range.</td>
</tr>
<tr>
<td><a href="#streaming-answers">Streaming answers</a></td>
<td>Tokens stream in as Claude generates them — same model context that wrote the code.</td>
</tr>
<tr>
<td><a href="#threaded-discussions">Threaded discussions</a></td>
<td>Multiple asks per line, each its own thread, all anchored to the diff.</td>
</tr>
</table>
## Usage

### Diff selection

Expand Down Expand Up @@ -159,175 +148,39 @@ Each line can have multiple ask/answer pairs. They render as a
threaded conversation inline with the diff, so you can ask a
follow-up without losing context.

## Configuration

All optional. Set as env vars before running `npx -y askdiff` — or
let the skill resolve them automatically.

| Variable | Default | Notes |
|---|---|---|
| `PORT` | `7837` | Auto-bumps if taken. |
| `ASKDIFF_SESSION_ID` | (resolved from `$PPID`) | Force a specific Claude Code session UUID. |
| `ASKDIFF_PROJECT_CWD` | (parent CC manifest, then `process.cwd()`) | Project directory to diff. |
| `ASKDIFF_MODEL` | (inherits resumed session's model) | Override the Claude model for asks. |
| `CLAUDE_CONFIG_DIR` | `~/.claude` | Where Claude Code stores `sessions/`, `projects/`. |

CLI flags also work (`askdiff --port 7838 --no-open --session <uuid>`).
Run `askdiff --help` for the full list.
## Updates

## Updating

The skill asynchronously hits the npm registry after launching askdiff. If a newer version is available, you'll see a passive notice
*after* the launch with the upgrade command pre-formatted:

```
── A new version of askdiff is available ──
installed: 0.3.0
latest: 0.3.1
to update: npx -y askdiff@latest install-skill --force
(add --global if you installed user-level)
```

You're free to ignore it — the version you have is still working in
front of you. When you do want to upgrade, run the printed command at
the same scope you installed:
After launching, the skill asynchronously checks npm for a newer
version and prints a passive upgrade notice if one exists. Run the
printed command at the same scope you originally installed:

```bash
# project-local install (the default)
npx -y askdiff@latest install-skill --force

# user-level install
npx -y askdiff@latest install-skill --global --force
npx -y askdiff@latest install-skill --force # project-local
npx -y askdiff@latest install-skill --global --force # user-level
```

The first subsequent `/askdiff` runs the upgraded CLI. Set
`ASKDIFF_SKIP_UPDATE_CHECK=1` to suppress the network call entirely.
Set `ASKDIFF_SKIP_UPDATE_CHECK=1` to suppress the network call.

## Skills shipped

`install-skill` writes one file: `<git-root>/.claude/skills/askdiff/SKILL.md`
by default — scoped to the current project. That's the entire surface
area in your CC config for that repo. The command walks up from `cwd`
looking for a `.git` directory and refuses (rather than guessing a path)
if none is found.

```bash
cd /path/to/your/project # default install scope
npx -y askdiff install-skill # → <git-root>/.claude/skills/askdiff/SKILL.md
```

To install user-level instead — making `/askdiff` available from any
Claude Code session you start — pass `--global`:

```bash
npx -y askdiff install-skill --global
# → ~/.claude/skills/askdiff/SKILL.md
```

Project skills override same-named user skills, so it's safe to have
both: a global install for general use, plus a project install pinning
this repo to a specific askdiff version.

> **Upgrading from `0.2.x`?** The old version installed user-level by
> default. To preserve that behavior on upgrade, run
> `npx -y askdiff@latest install-skill --global --force`. Otherwise the
> upgrade will install project-locally and leave your old user-level
> skill stale.

### Uninstalling
## Uninstalling

Uninstall is a single `rm` — there's intentionally no `uninstall-skill`
command. Delete whichever scope you installed:

```bash
# project-local install (the default)
rm -rf <git-root>/.claude/skills/askdiff

# user-level install (--global)
rm -rf ~/.claude/skills/askdiff
```

It's safe to `rm -rf` the whole `skills/askdiff/` directory — askdiff
keeps no other state under `~/.claude` or your project. Anything left
in `/tmp/askdiff*` is session-scoped scratch and clears itself out
within the WS server's idle-shutdown window.

In this repo (for contributors) there is one more:

- `/askdiff-dev` — local Vite dev server with HMR + tsx-run WS server.
Use when editing `packages/server` or `packages/ui-browser`. Re-invoking
`/askdiff-dev` (or `/askdiff`) from the same session kills the previous
server, reuses its port, and points at a freshly-written diff — that's
the refresh path. The WS server idle-shuts after 5 min with no clients.

## Architecture

The npm package (`packages/cli`) is a single esbuild-bundled Node
binary that hosts an HTTP server (serving the prebuilt UI bundle in
`dist/ui/`) and a WebSocket on the same port at `/ws`. The CLI
imports `startServer` from `@askdiff/server`, which spawns
`claude --resume` per ask and forwards `text_delta` events to the
client. The browser UI (`packages/ui-browser`) is React 19 + Vite +
Tailwind v4 + zustand, with `react-diff-view` for rendering and
refractor for syntax highlighting.

## Development

```bash
git clone https://github.com/narghev/askdiff
cd askdiff
pnpm install
pnpm test
pnpm lint
pnpm run build
rm -rf <git-root>/.claude/skills/askdiff # project-local (the default)
rm -rf ~/.claude/skills/askdiff # user-level (--global)
```

From a Claude Code session in this repo:
askdiff keeps no other state under `~/.claude` or your project.
Anything left in `/tmp/askdiff*` is session-scoped scratch and clears
itself out within the WS server's idle-shutdown window.

```
/askdiff-dev # first launch: Vite + WS server with HMR
/askdiff-dev # again: kills the WS server, restarts on same port with a fresh diff
/askdiff-dev last commit # description-driven: HEAD~1..HEAD
```

The WS server idle-shuts after 5 min with no connected clients; Vite is
intentionally persistent (HMR is the whole point). Kill Vite via
Activity Monitor or `pkill -f 'ui-browser.*vite'` on the rare occasion
you want it gone.

To exercise the production-shaped binary locally:

```bash
pnpm run build
node packages/cli/dist/index.js --port 7838
```
## Help & contributing

## Troubleshooting

**"Claude session: (none — set ASKDIFF_SESSION_ID or use --session)"**
The skill couldn't read the parent CC manifest. You're either running
askdiff from outside a Claude Code session (no `$PPID.json` in
`~/.claude/sessions/`), or `CLAUDE_CONFIG_DIR` points somewhere
else. Pass `--session <uuid>` explicitly to override.

**"Port 7837 is already in use"**
Another askdiff (from a different session) is running, or something
else grabbed the port. Same-session re-invocations don't hit this —
they reuse their session's saved port. Pass `--port 7838` to force a
specific port, or wait 5 min for the idle WS server to self-terminate.

**Browser opens, UI loads, but never connects**
The WS upgrade is failing. Check `/tmp/askdiff.<suffix>.log` (where
`<suffix>` is your CC session UUID) — usually it's an old UI cached
against a new server (reload the browser tab) or a hung
`claude --resume` subprocess (check `ps aux | grep claude`).

**`/askdiff` doesn't appear in Claude Code's skill picker**
Run `npx -y askdiff install-skill` from inside the project (writes
`<git-root>/.claude/skills/askdiff/SKILL.md`), or
`npx -y askdiff install-skill --global` to install user-level
(`~/.claude/skills/askdiff/SKILL.md`). If the file is there but still
missing from the picker, restart Claude Code or run `/reload-plugins`.
- Hitting a bug? See [SUPPORT.md](./SUPPORT.md) for common issues and
how to file a useful report.
- Hacking on askdiff? See [CONTRIBUTING.md](./CONTRIBUTING.md) for the
dev loop, architecture, and the in-repo `/askdiff-dev` skill.

## Star History

Expand Down
Loading
Loading