diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..046ea13 --- /dev/null +++ b/CONTRIBUTING.md @@ -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 `); +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. diff --git a/README.md b/README.md index e5dbd04..55e0fb8 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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 - - - - - - - - - - - - - - - - - - - - - - -
Diff selectionDescribe which diff to review in plain English — working tree, last commit, branch comparisons, arbitrary refs.
Session selectionBy default asks flow into the invoking session. Add "in our session about X" or "session <uuid>" to attach to a different past session — the one that originally wrote the code, for example.
Inline commentsClick the + gutter button to comment on any line. Drag to comment on a range.
Streaming answersTokens stream in as Claude generates them — same model context that wrote the code.
Threaded discussionsMultiple asks per line, each its own thread, all anchored to the diff.
+## Usage ### Diff selection @@ -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 `). -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: `/.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 # → /.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 /.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 /.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 ` 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..log` (where -`` 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 -`/.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 diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000..0718d72 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,49 @@ +# Support + +Before filing an issue, please skim the common problems below — most +askdiff bugs have a known cause. + +## 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 ` 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..log` (where +`` 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 +`/.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`. + +## Filing an issue + +If the troubleshooting above doesn't cover your case, open an issue at +[github.com/narghev/askdiff/issues](https://github.com/narghev/askdiff/issues) +with: + +- askdiff version (`npx askdiff --version`) +- Claude Code version (`claude --version`) +- Node version (`node --version`) and OS +- The relevant `/tmp/askdiff..log` excerpt (the suffix is your + CC session UUID — find it via `ls -t /tmp/askdiff.*.log | head -1`) +- What you ran and what you expected + +## Contributing + +For development setup, architecture, and the `/askdiff-dev` skill, see +[CONTRIBUTING.md](./CONTRIBUTING.md). diff --git a/assets/nl-descriptors-demo.gif b/assets/nl-descriptors-demo.gif new file mode 100644 index 0000000..f3fdc3a Binary files /dev/null and b/assets/nl-descriptors-demo.gif differ diff --git a/assets/working-tree-demo.gif b/assets/working-tree-demo.gif new file mode 100644 index 0000000..437286f Binary files /dev/null and b/assets/working-tree-demo.gif differ