Skip to content

Commit 7d075c9

Browse files
authored
Update the skill to handle natural language diffs, change the logic of the server to read the diff from a temp file and not staged/unstaged git invocations (#2)
1 parent d81f120 commit 7d075c9

16 files changed

Lines changed: 446 additions & 134 deletions

File tree

.claude/skills/askdiff-dev/SKILL.md

Lines changed: 142 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,148 @@ user-invocable: true
55
allowed-tools: Bash
66
---
77

8-
Local-development variant of `/askdiff`. Starts the WS server **and**
9-
the browser UI's Vite dev server (with HMR). Vite is configured to
8+
Local-development variant of `/askdiff`. Starts the WS server **and** the
9+
browser UI's Vite dev server (with HMR), and exercises the in-repo
10+
TypeScript instead of the published npm package. Vite is configured to
1011
proxy `/ws` to the WS server, so the UI uses the same same-origin
1112
`new WebSocket('ws://host/ws')` URL in dev as in prod. The
1213
`ASKDIFF_DEV_WS_TARGET` env var tells Vite which port to forward to.
1314

14-
Use this when editing `packages/ui-browser` and you want changes to
15-
reload instantly instead of rebuilding the npm package.
15+
Use this when editing `packages/server` or `packages/ui-browser` and you
16+
want changes to reload instantly instead of rebuilding/republishing.
17+
18+
> **Keep Step 1–3 in sync with `.claude/skills/askdiff/SKILL.md`.** The
19+
> diff-resolution flow (interpret → git → temp file → label) must behave
20+
> identically in both skills; only Step 4 (launch) differs. If you change
21+
> the table or the bash blocks below, change them in the user-facing
22+
> `askdiff` skill too.
23+
24+
## Step 1 — figure out which diff the user wants
25+
26+
Look at the message that invoked this skill. Anything after `/askdiff-dev`
27+
is the user's diff description (may be empty).
28+
29+
| User said | git command | Suggested label |
30+
|---|---|---|
31+
| `/askdiff-dev` (no args) | working tree — see Step 2 | `Working tree` |
32+
| `/askdiff-dev last commit` | `git diff HEAD~1 HEAD` | `HEAD~1..HEAD` |
33+
| `/askdiff-dev last 3 commits` | `git diff HEAD~3 HEAD` | `HEAD~3..HEAD` |
34+
| `/askdiff-dev the 5th latest commit` | `git diff HEAD~5 HEAD~4` | `HEAD~5..HEAD~4` |
35+
| `/askdiff-dev current branch against feature/test` | `git diff feature/test...HEAD` (three-dot, PR semantics) | `feature/test…HEAD` |
36+
| `/askdiff-dev main vs my branch` | `git diff main...HEAD` | `main…HEAD` |
37+
| `/askdiff-dev abc123 vs def456` | `git diff abc123 def456` | `abc123..def456` |
38+
| `/askdiff-dev staged` | `git diff --cached` | `staged` |
39+
40+
Defaults when the user is ambiguous:
41+
- "branch X against branch Y" / "X vs Y" between two named refs ⇒ three-dot
42+
(`git diff X...Y`) — matches how GitHub renders PRs.
43+
- Two arbitrary commits ⇒ two-dot (`git diff A B`).
44+
- "Nth latest commit" ⇒ that single commit's changes
45+
(`git diff HEAD~N HEAD~(N-1)`).
46+
47+
### When the description is vague
48+
49+
If the description doesn't fit the table — e.g. "the commit where I added
50+
the favicon", "the last commit by my coworker David", "where we ripped out
51+
the old auth code", "the commit that broke CI last week" — pin down a
52+
single commit with the ladder below, then diff `<sha>^..<sha>` (same shape
53+
as the "Nth latest commit" pattern). Try in order until exactly one commit
54+
matches; if several match, pick the most recent and **tell the user which
55+
one you chose**; if none match, stop and ask — do not guess.
56+
57+
1. **Author.** "by <name>", "<name>'s last", "by my coworker":
58+
```bash
59+
git log --author=<pattern> -i -1 --format='%H %an %s'
60+
```
61+
62+
2. **Commit message.** "the migration commit", "where I bumped deps":
63+
```bash
64+
git log --grep=<keyword> -i -1 --format='%H %s'
65+
```
66+
67+
3. **Diff content.** "where I added/removed/touched <thing>". `-S` matches
68+
when a string's count changed in any file; `-G` is a regex over the
69+
diff text:
70+
```bash
71+
git log -S"<distinctive-string>" -1 --format='%H %s'
72+
git log -G"<regex>" -1 --format='%H %s'
73+
```
74+
75+
4. **File history.** When you can identify the file but not the commit
76+
(e.g. "where the homepage was added" — search the working tree for a
77+
plausible path first, then ask git):
78+
```bash
79+
git ls-files | grep -i <hint> # find candidate path
80+
git log --follow -1 --format='%H %s' -- <path> # most recent touch
81+
git log --follow --diff-filter=A -1 --format='%H %s' -- <path> # commit that introduced it
82+
```
83+
84+
Once a SHA is in hand, build the label as `<short-sha>: <one-line gloss>`
85+
(e.g. `d0b332b: add favicon`) and use `git diff <sha>^ <sha>` as the
86+
diff command. If the user's count and description disagree (e.g. "my 3rd
87+
previous commit, where I added a favicon" but the favicon is at HEAD~2),
88+
trust the description over the count and **flag the off-by-one to the
89+
user** so they know what you picked.
90+
91+
**Validate every ref first.** Run `git rev-parse --verify <ref>^{commit}` for
92+
each ref the user named directly. If any fails, stop and tell the user
93+
which ref didn't resolve — do not launch the server. (Refs returned by the
94+
search ladder are already validated by virtue of `git log` finding them.)
95+
96+
## Step 2 — write the diff to a temp file
97+
98+
```bash
99+
diff_file=$(mktemp /tmp/askdiff-diff.XXXXXX)
100+
```
101+
102+
(macOS `mktemp` only substitutes trailing X's, so the template can't have
103+
a `.diff` suffix. The server doesn't care about the extension.)
104+
105+
**Working tree (no description).** Untracked files don't appear in
106+
`git diff HEAD`, so we union them in via `--no-index`:
107+
108+
```bash
109+
{
110+
git -C "$project_cwd" diff HEAD --no-color
111+
git -C "$project_cwd" ls-files --others --exclude-standard -z \
112+
| while IFS= read -r -d '' f; do
113+
git -C "$project_cwd" diff --no-index --no-color -- /dev/null "$f" || true
114+
done
115+
} > "$diff_file"
116+
```
117+
118+
(In an empty repo with no HEAD, replace `HEAD` with the empty-tree SHA
119+
`4b825dc642cb6eb9a060e54bf8d69288fbee4904`.)
120+
121+
**Description path.** Just run the resolved command:
16122

17-
Run this as a single Bash command so discovered values survive into the
18-
launch:
123+
```bash
124+
git -C "$project_cwd" diff <args> --no-color > "$diff_file"
125+
```
126+
127+
For the description path, if the resulting file is empty, **stop** — tell the
128+
user the requested diff is empty and don't launch. The working-tree path
129+
*can* legitimately be empty (clean tree); launch anyway and the UI will
130+
show "No changes."
131+
132+
## Step 3 — pick a short label
133+
134+
Use the "Suggested label" column above. For the working-tree case, use
135+
`Working tree`. Keep it under ~40 chars. This becomes `ASKDIFF_DIFF_LABEL`.
136+
137+
## Step 4 — launch (in-repo)
138+
139+
Run as a single Bash command so the discovered values survive into the
140+
launch. Substitute `EXTRA_DIFF_FILE` and `EXTRA_DIFF_LABEL` literally with
141+
the values from Step 2/3.
19142

20143
```
21144
set +e
22145
146+
# Filled in by Step 2/3.
147+
EXTRA_DIFF_FILE=""
148+
EXTRA_DIFF_LABEL=""
149+
23150
# 1. Free port for the WS server (default 7837, bump until free).
24151
port=7837
25152
while lsof -iTCP:$port -sTCP:LISTEN -t >/dev/null 2>&1; do
@@ -36,8 +163,14 @@ if [ -f "$session_file" ]; then
36163
[ -n "$manifest_cwd" ] && project_cwd="$manifest_cwd"
37164
fi
38165
39-
# 3. Start the WS server.
40-
cd "$project_cwd" && PORT=$port ASKDIFF_SESSION_ID="$session_id" ASKDIFF_PROJECT_CWD="$project_cwd" nohup pnpm --filter @askdiff/server exec tsx src/main.ts > /tmp/askdiff.log 2>&1 &
166+
# 3. Start the WS server (in-repo via tsx).
167+
cd "$project_cwd" \
168+
&& PORT=$port \
169+
ASKDIFF_SESSION_ID="$session_id" \
170+
ASKDIFF_PROJECT_CWD="$project_cwd" \
171+
ASKDIFF_DIFF_FILE="$EXTRA_DIFF_FILE" \
172+
ASKDIFF_DIFF_LABEL="$EXTRA_DIFF_LABEL" \
173+
nohup pnpm --filter @askdiff/server exec tsx src/main.ts > /tmp/askdiff.log 2>&1 &
41174
disown
42175
sleep 1.5
43176
head -5 /tmp/askdiff.log
@@ -81,6 +214,7 @@ echo "UI: $ui_url"
81214
Then tell the user:
82215
- the WS server port (visible in the `listening on ws://...` line)
83216
- the resolved Claude session ID (from the `claude session:` line)
217+
- the diff label (always set)
84218
- the WS log file: `/tmp/askdiff.log`
85219
- the Vite log file: `/tmp/askdiff-ui.log`
86220
- the UI URL (last echoed line) — already opened in their default browser

.claude/skills/askdiff/SKILL.md

Lines changed: 137 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,137 @@ user-invocable: true
55
allowed-tools: Bash
66
---
77

8-
Start the published `askdiff` CLI in the background. Before launching,
9-
check whether a newer version is available on npm. If so, halt with
10-
the line `UPDATE_AVAILABLE: pinned=X latest=Y` so we can ask the user
11-
whether to upgrade or proceed on the pinned version.
8+
Compute the unified diff the user wants to review, write it to a temp file,
9+
then launch the published `askdiff` CLI in the background pointing at that
10+
file. The server is intentionally git-illiterate — it only reads the file
11+
you produce and serves it to the browser. Skipping the file is a startup
12+
error.
1213

13-
Run this as a single Bash command:
14+
> **Keep Step 1–3 in sync with `.claude/skills/askdiff-dev/SKILL.md`.** The
15+
> diff-resolution flow (interpret → git → temp file → label) must behave
16+
> identically in both skills; only Step 4 (launch) differs. If you change
17+
> the table or the bash blocks below, change them in the dev skill too.
18+
19+
## Step 1 — figure out which diff the user wants
20+
21+
Look at the message that invoked this skill. Anything after `/askdiff` is the
22+
user's diff description (may be empty).
23+
24+
| User said | git command | Suggested label |
25+
|---|---|---|
26+
| `/askdiff` (no args) | working tree — see Step 2 | `Working tree` |
27+
| `/askdiff last commit` | `git diff HEAD~1 HEAD` | `HEAD~1..HEAD` |
28+
| `/askdiff last 3 commits` | `git diff HEAD~3 HEAD` | `HEAD~3..HEAD` |
29+
| `/askdiff the 5th latest commit` | `git diff HEAD~5 HEAD~4` | `HEAD~5..HEAD~4` |
30+
| `/askdiff current branch against feature/test` | `git diff feature/test...HEAD` (three-dot, PR semantics) | `feature/test…HEAD` |
31+
| `/askdiff main vs my branch` | `git diff main...HEAD` | `main…HEAD` |
32+
| `/askdiff abc123 vs def456` | `git diff abc123 def456` | `abc123..def456` |
33+
| `/askdiff staged` | `git diff --cached` | `staged` |
34+
35+
Defaults when the user is ambiguous:
36+
- "branch X against branch Y" / "X vs Y" between two named refs ⇒ three-dot
37+
(`git diff X...Y`) — matches how GitHub renders PRs.
38+
- Two arbitrary commits ⇒ two-dot (`git diff A B`).
39+
- "Nth latest commit" ⇒ that single commit's changes
40+
(`git diff HEAD~N HEAD~(N-1)`).
41+
42+
### When the description is vague
43+
44+
If the description doesn't fit the table — e.g. "the commit where I added
45+
the favicon", "the last commit by my coworker David", "where we ripped out
46+
the old auth code", "the commit that broke CI last week" — pin down a
47+
single commit with the ladder below, then diff `<sha>^..<sha>` (same shape
48+
as the "Nth latest commit" pattern). Try in order until exactly one commit
49+
matches; if several match, pick the most recent and **tell the user which
50+
one you chose**; if none match, stop and ask — do not guess.
51+
52+
1. **Author.** "by <name>", "<name>'s last", "by my coworker":
53+
```bash
54+
git log --author=<pattern> -i -1 --format='%H %an %s'
55+
```
56+
57+
2. **Commit message.** "the migration commit", "where I bumped deps":
58+
```bash
59+
git log --grep=<keyword> -i -1 --format='%H %s'
60+
```
61+
62+
3. **Diff content.** "where I added/removed/touched <thing>". `-S` matches
63+
when a string's count changed in any file; `-G` is a regex over the
64+
diff text:
65+
```bash
66+
git log -S"<distinctive-string>" -1 --format='%H %s'
67+
git log -G"<regex>" -1 --format='%H %s'
68+
```
69+
70+
4. **File history.** When you can identify the file but not the commit
71+
(e.g. "where the homepage was added" — search the working tree for a
72+
plausible path first, then ask git):
73+
```bash
74+
git ls-files | grep -i <hint> # find candidate path
75+
git log --follow -1 --format='%H %s' -- <path> # most recent touch
76+
git log --follow --diff-filter=A -1 --format='%H %s' -- <path> # commit that introduced it
77+
```
78+
79+
Once a SHA is in hand, build the label as `<short-sha>: <one-line gloss>`
80+
(e.g. `d0b332b: add favicon`) and use `git diff <sha>^ <sha>` as the
81+
diff command. If the user's count and description disagree (e.g. "my 3rd
82+
previous commit, where I added a favicon" but the favicon is at HEAD~2),
83+
trust the description over the count and **flag the off-by-one to the
84+
user** so they know what you picked.
85+
86+
**Validate every ref first.** Run `git rev-parse --verify <ref>^{commit}` for
87+
each ref the user named directly. If any fails, stop and tell the user
88+
which ref didn't resolve — do not launch the server. (Refs returned by the
89+
search ladder are already validated by virtue of `git log` finding them.)
90+
91+
## Step 2 — write the diff to a temp file
92+
93+
```bash
94+
diff_file=$(mktemp /tmp/askdiff-diff.XXXXXX)
95+
```
96+
97+
(macOS `mktemp` only substitutes trailing X's, so the template can't have
98+
a `.diff` suffix. The server doesn't care about the extension.)
99+
100+
**Working tree (no description).** Untracked files don't appear in
101+
`git diff HEAD`, so we union them in via `--no-index`:
102+
103+
```bash
104+
{
105+
git -C "$project_cwd" diff HEAD --no-color
106+
git -C "$project_cwd" ls-files --others --exclude-standard -z \
107+
| while IFS= read -r -d '' f; do
108+
git -C "$project_cwd" diff --no-index --no-color -- /dev/null "$f" || true
109+
done
110+
} > "$diff_file"
111+
```
112+
113+
(In an empty repo with no HEAD, replace `HEAD` with the empty-tree SHA
114+
`4b825dc642cb6eb9a060e54bf8d69288fbee4904`.)
115+
116+
**Description path.** Just run the resolved command:
117+
118+
```bash
119+
git -C "$project_cwd" diff <args> --no-color > "$diff_file"
120+
```
121+
122+
`<args>` is whatever you resolved in Step 1 (e.g. `HEAD~1 HEAD` or
123+
`feature/test...HEAD` or `--cached`).
124+
125+
For the description path, if the resulting file is empty, **stop** — tell the
126+
user the requested diff is empty and don't launch. (`/askdiff HEAD vs HEAD`
127+
is the canonical empty case.) The working-tree path *can* legitimately be
128+
empty (clean tree); launch anyway and the UI will show "No changes."
129+
130+
## Step 3 — pick a short label
131+
132+
Use the "Suggested label" column above. For the working-tree case, use
133+
`Working tree`. Keep it under ~40 chars. This becomes `ASKDIFF_DIFF_LABEL`.
134+
135+
## Step 4 — launch
136+
137+
Run as a single Bash command. Substitute `EXTRA_DIFF_FILE` and
138+
`EXTRA_DIFF_LABEL` literally with the values from Step 2/3.
14139

15140
```
16141
set +e
@@ -20,6 +145,10 @@ set +e
20145
# this repo always pulls the newest published version).
21146
ASKDIFF_VERSION="latest"
22147
148+
# Filled in by Step 2/3.
149+
EXTRA_DIFF_FILE=""
150+
EXTRA_DIFF_LABEL=""
151+
23152
# 1. Resolve parent Claude Code session + cwd.
24153
session_file="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/sessions/$PPID.json"
25154
session_id=""
@@ -46,6 +175,8 @@ fi
46175
cd "$project_cwd" \
47176
&& ASKDIFF_SESSION_ID="$session_id" \
48177
ASKDIFF_PROJECT_CWD="$project_cwd" \
178+
ASKDIFF_DIFF_FILE="$EXTRA_DIFF_FILE" \
179+
ASKDIFF_DIFF_LABEL="$EXTRA_DIFF_LABEL" \
49180
nohup npx -y askdiff@"$ASKDIFF_VERSION" --no-open > /tmp/askdiff.log 2>&1 &
50181
disown
51182
@@ -83,6 +214,7 @@ echo "UI: $url"
83214
launch already happened. Tell the user:
84215
- the WS server URL (the `listening on http://...` line)
85216
- the resolved Claude session ID (the `claude session:` line)
217+
- the diff label (always set)
86218
- the log file: `/tmp/askdiff.log`
87219
- the UI URL (last echoed line) — already opened in their default browser
88220

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ coverage/
1919
# Local skill management — not part of askdiff
2020
.agents/
2121
skills-lock.json
22+
23+
*.mp4

0 commit comments

Comments
 (0)