@shiyuphan/follow-me 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,93 +1,165 @@
1
+ <p align="center"><img src="assets/logo.png" width="200" alt="A sweaty cyan blob with mismatched googly eyes and its tongue out chases a smug red blob in a party hat labelled HEAD along a git branch"></p>
2
+
1
3
  # follow-me
2
4
 
3
- Read a branch the way it was thought, not the way it was committed.
5
+ follow-me splits a branch's net change into small commits in an order a reader can follow, proves the result equals the original, and lets you review it line by line.
6
+
7
+ [![npm](https://img.shields.io/npm/v/@shiyuphan/follow-me)](https://www.npmjs.com/package/@shiyuphan/follow-me)
8
+ [![ci](https://github.com/phanshiyu/follow-me/actions/workflows/ci.yml/badge.svg)](https://github.com/phanshiyu/follow-me/actions/workflows/ci.yml)
9
+
10
+ <p align="center"><img src="assets/playback.gif" width="960" alt="Playback of an eight-commit branch: each commit opens with its narration, then its changed lines appear chunk by chunk beside a sequence diagram"></p>
4
11
 
5
- follow-me takes a branch's net change and splits it into fine commits ordered so a reader
6
- can follow the author's thought process. Individual commits need not compile. The last
7
- commit's tree is byte-identical to the original tip, and follow-me proves it with git's own
8
- tree hash. Then it serves a local page where you walk the commits one by one, comment on any
9
- hunk or line, and hand the comments to a coding agent for the next version.
12
+ *Playback at 1x, sped up 1.5 times for the GIF. The voice is not in the GIF; the browser reads the narration aloud.*
10
13
 
11
- Two people use it:
14
+ ## Why
12
15
 
13
- - **The author** runs extract, check, apply and review to turn a messy branch into a readable
14
- stack before asking for review. Comments on the split go back to the agent, which writes
15
- the next plan.
16
- - **The reviewer** runs `follow-me walk` on someone else's branch to watch it being written,
17
- with narration spoken as each line appears, and leaves comments on the code. The result is
18
- a code review for the author.
16
+ People commit in the order things happened: wip, fix, actually fix, revert. A reviewer then reads that history or gives up and reads one big diff. Neither shows how the change was thought through.
19
17
 
20
- Inspired by [spark-joy](https://github.com/Dansyuqri/spark-joy)'s "split your own changes".
18
+ The blob with its tongue out is you, on commit 31 of someone's 47-commit branch.
19
+
20
+ follow-me is for two people:
21
+
22
+ - **The author** commits however they like, then wants a branch that reads like a story before asking for review.
23
+ - **The reviewer** wants to watch someone else's branch being written, one line at a time, with the author's reasoning read aloud.
21
24
 
22
25
  ## Install
23
26
 
24
- Requires Node 24 or newer and git. There is no build step; Node runs the TypeScript sources.
27
+ You need Node 24 or newer and git.
28
+
29
+ ```sh
30
+ npm i -g @shiyuphan/follow-me
31
+ ```
32
+
33
+ follow-me ships an Agent Skill that teaches a coding agent to drive the CLI. Install it once:
34
+
35
+ ```sh
36
+ follow-me install
37
+ ```
38
+
39
+ `install` looks in your home directory for the config directories of Claude Code, OpenCode, Codex CLI, Cursor, Gemini CLI, and GitHub Copilot CLI. It copies the skill into the user-level skill directory of each one it finds, for example `~/.claude/skills/follow-me/SKILL.md`, so every repo sees it. Add `--project` to install into the current repo instead. Other flags are `--harness claude,codex`, `--all`, and `--dry-run`.
40
+
41
+ ```sh
42
+ follow-me install --list # each harness, where its skill goes, and its docs
43
+ follow-me uninstall # removes exactly the files install writes
44
+ ```
45
+
46
+ [Using follow-me from your coding agent](docs/harnesses.md) has the per-harness details.
47
+
48
+ ## Split your own branch
49
+
50
+ Check out the branch you want to split and run this from the repo root:
51
+
52
+ ```sh
53
+ follow-me split --base main # extract, draft, apply and open the review in one go
54
+ ```
55
+
56
+ With an Anthropic key in the environment, `split` asks the API for the draft. Without one, it writes `.follow-me/<branch>/prompt.md` and stops: ask your coding agent to follow it, then run `follow-me check` and `follow-me split` again. Every command ends with a `next:` line naming the step after it. The steps `split` runs are also commands of their own:
25
57
 
26
58
  ```sh
27
- git clone https://github.com/<you>/follow-me
28
- cd follow-me && npm install && npm link # puts `follow-me` on your PATH
59
+ follow-me extract --base main # snapshot the diff from main to the tip, list the units
60
+ follow-me prompt # instructions and every unit, for your coding agent
61
+ # the agent writes .follow-me/<branch>/draft.json
62
+ follow-me check # print every problem with the draft at once
63
+ follow-me apply # write the commits to <branch>-split
64
+ follow-me review # open the review page in your browser
65
+ follow-me feedback # print the review as markdown for the agent
29
66
  ```
30
67
 
31
- ## Author flow
68
+ A unit is a text hunk (`h_…`), a whole file that cannot be split by line (`f_…`), or a mode change (`m_…`). The draft assigns every change line to exactly one commit. `h_x:0-3,7` takes change lines 0 to 3 and 7 of hunk `h_x`, so one hunk can spread across several commits.
32
69
 
33
- Run everything from the repo whose branch you want to split, with that branch checked out.
70
+ If you skip the agent, `follow-me plan` asks the Anthropic API for the draft. It needs `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN` in the environment, and takes `--model <id>`.
71
+
72
+ `check` and `apply` also parse every file each commit writes, so a split can leave a commit that does not build but never one that ends inside a function. `.js`, `.ts`, `.json`, `.sh` and `.py` files are checked and other types are listed as skipped; `--syntax "<command with {file}>"` swaps in your own checker and `apply --allow-syntax-errors` goes ahead anyway. With no API key and no agent, `follow-me plan --naive` writes one `wip: <path>` commit per touched file, which is enough to run `check`, `apply` and the tree proof on any branch.
73
+
74
+ `apply` never writes your original branch, your index, or your working tree. It builds the commits in a temporary index and points `<branch>-split` at them, or the branch you name with `--name`. It refuses the branch being split and any branch checked out in a worktree, even with `--force`. `--force` only lets it move a branch follow-me did not create. Then it compares the last commit's tree hash with the original tip's and prints both:
75
+
76
+ ```text
77
+ created feature-split at c90eb5350284 from plan v1 (3 commits)
78
+ signing: off
79
+ proof: feature-split^{tree} ae948488c55a3606fa2419eb1c018fff8014093d equals feature^{tree} ae948488c55a3606fa2419eb1c018fff8014093d
80
+ ```
81
+
82
+ If the hashes differ, `apply` exits with code 1. The commits are signed when your git config signs commits (`commit.gpgsign`), and `--sign` or `--no-sign` overrides that. Running `apply` again with the same plan prints `unchanged` and leaves the branch alone, signed or not.
83
+
84
+ The red blob in the party hat is your branch tip. Nothing follow-me does moves it.
85
+
86
+ ![The review page: the commit list on the left, commit 3 of 8 with file line numbers, a comment on lines 14-15 and its diagram on the right](assets/screenshots/review.png)
87
+ *The review page. Pick a commit on the left. Comment on its message, its position ("This belongs elsewhere"), a hunk, or single lines.*
88
+
89
+ ![One hunk split across commits: commit 4 adds one line, earlier commits' lines read as context and the four lines commit 5 adds fold into one row](assets/screenshots/split-hunk.png)
90
+ *One hunk split across commits. Commit 3 takes 2 of 7 change lines. The others are dimmed and marked "→ commit 4" or "→ commit 5".*
91
+
92
+ On the review page, `n` or `j` goes to the next commit, `p` or `k` to the previous one. **End review** saves `.follow-me/<branch>/reviews/vN.json`. An unfinished review autosaves and comes back when you open the session again.
93
+
94
+ ## Watch someone else's branch
95
+
96
+ Check out their branch and run:
34
97
 
35
98
  ```sh
36
- follow-me extract --base main # anchor a session, list the units (hunks, files, modes)
37
- follow-me prompt # the planner prompt, for a coding agent to read
38
- # the agent writes .follow-me/<branch>/draft.json
39
- follow-me check # every problem with the draft, at once
40
- follow-me apply # write <branch>-split, prove the tree matches
41
- follow-me review # open the review page; end it to save reviews/v1.json
42
- follow-me feedback # the comments as markdown, for the agent
99
+ follow-me walk --base main
43
100
  ```
44
101
 
45
- With Anthropic credentials in the environment, `follow-me plan` writes the draft itself.
46
- Otherwise the Claude Code skill in `skills/follow-me/` does it; link it into
47
- `.claude/skills/follow-me/` and ask Claude to split the branch.
102
+ `walk` extracts the branch and plans it as a narrated story. With an Anthropic key in the environment it asks the API. Without one, it prints `follow-me prompt --walk` for your coding agent, and you run `follow-me walk` again once the agent's draft passes `follow-me check`. `walk` applies nothing and writes no branch.
103
+
104
+ ![Playback: chunk 2 of 2 of commit 3, its narration card above the two lines it introduces, the progress strip in the player bar and the unit's sequence diagram beside the code](assets/screenshots/playback.png)
105
+ *Playback. Each line types in while the narration for its commit or hunk is read aloud. The caption shows the narration being read.*
48
106
 
49
- After a review, the agent reads the comments, writes a new draft that answers each one in
50
- `responses`, and runs `check` and `apply` again. Plans are versioned under
51
- `.follow-me/<branch>/plans/`, reviews under `reviews/`, and a comment counts as answered when
52
- a later plan lists it.
107
+ Playback goes commit by commit. Space pauses, **Previous** and **Next** move between commits, and you can change the speed, pick a voice, or mute. Pause on any line to comment on it. Narration uses the browser's built-in speech synthesis, so it needs no key and no network.
53
108
 
54
- ## Reviewer flow
109
+ When you end the review, `follow-me feedback --walk` prints it as a code review for the author, grouped by file. Code reviews are saved apart from the author's plan reviews, in `.follow-me/<branch>/reviews/walk/vN.json`, so you can walk a plan the author already reviewed. Each comment quotes its hunk as it reads in that commit, with the line number on the branch. The author's agent fixes the branch itself. A walk review never feeds the split loop below.
110
+
111
+ ## One server for every review
112
+
113
+ The first `review` or `walk` starts a review server for your user and serves the page. Every later `review` or `walk`, from any repo or worktree, adds its session to that server, prints the URL, and exits. The server's index page lists every open session.
55
114
 
56
115
  ```sh
57
- git checkout their-branch
58
- follow-me walk --base main # extract, plan, and open playback
116
+ follow-me review --detach # start the server in the background and return
117
+ follow-me review --no-open # print the URL without opening a browser
118
+ follow-me review --wait # return only once the review ends, so an agent can go on to feedback
119
+ follow-me server status # the server, its port, and the sessions it hosts
120
+ follow-me server stop # stop it, autosaved reviews are kept
59
121
  ```
60
122
 
61
- Playback walks the commits in order, speaks the narration the planner wrote, and types each
62
- added line out. Space pauses, `n` and `p` move between commits, and the speed and voice are
63
- adjustable. Pause on a line to comment on it. Finishing writes `reviews/vN.json`, and
64
- `follow-me feedback` renders it as a code review grouped by file.
123
+ The server listens on `127.0.0.1` only. If the `--port` you ask for is taken by another program, it picks a free port and prints `port N is in use by another program, using M`. It exits on its own 30 seconds after its last session ends. It is the only one at the party who knows when to leave. Its record lives in `$FOLLOW_ME_HOME`, else `$XDG_STATE_HOME/follow-me`, else `~/.local/state/follow-me`.
65
124
 
66
- Narration uses the browser's built-in speech synthesis. No key, no network.
125
+ Each worktree has its own `.follow-me/` directory, which git ignores, and each branch has its own session in it. A lock stops two agents from applying or reviewing one session at once. `follow-me clean` removes what no live session needs: sessions of deleted branches, review drafts that a finished review replaced, and locks and temp files of dead processes. It keeps plan versions, because reviews refer to them. `clean --dry-run` lists what it would remove, and `clean --all` removes `.follow-me/` entirely unless a live process holds a session.
67
126
 
68
127
  ## How it works
69
128
 
70
- Every unit comes from one `git diff base..tip`, so hunks of a file never overlap in base
71
- coordinates. A commit's version of a file is the base lines with that commit's change lines
72
- swapped in. Nothing is patched, so hunks can be assigned in any order and split by line.
73
- See `docs/DESIGN.md`.
129
+ Every unit comes from one `git diff base..tip`, so the hunks of a file never overlap in base coordinates. A commit's version of a file is therefore the base lines with that commit's change lines swapped in. Nothing is patched. Hunks can go in any order and split at any line, and nothing can fail to apply.
130
+
131
+ Two checks replace patching. While parsing, every context line and removed line must equal the base file at its position. After the last commit, every path's bytes and mode must equal the tip, and then git's own tree hash is compared once more.
74
132
 
75
- The user's working tree, index and branches are never touched. Commits are built in a
76
- temporary index with dates derived from the tip, so the same plan always yields the same
77
- SHAs and re-running `apply` is a no-op.
133
+ There is one limit. A commit can only contain lines that exist in the base or in the tip, so follow-me cannot invent an intermediate version of a line that the author typed and later deleted.
78
134
 
79
- ## Safety
135
+ [docs/DESIGN.md](docs/DESIGN.md) has the full design.
80
136
 
81
- - `apply` refuses to move a branch it did not create unless `--force`.
82
- - The review server binds `127.0.0.1` only and rejects cross-origin requests.
83
- - Everything under `.follow-me/` is ignored by git.
137
+ ## The agent loop
138
+
139
+ Each `apply` freezes the draft as the next plan version, `.follow-me/<branch>/plans/vN.json`. Each review belongs to one version. `follow-me feedback` gives every comment an id like `c_abc234`. After a review, `follow-me prompt` includes the previous plan and the open comments, and the agent answers each comment in the next draft:
140
+
141
+ ```json
142
+ "responses": [
143
+ { "comment": "c_abc234", "status": "addressed", "note": "Moved the guard before its first use." }
144
+ ]
145
+ ```
146
+
147
+ `status` is `addressed`, `declined`, or `deferred`. A comment counts as answered once a later plan lists it. `check` warns about unanswered comments but does not block `apply`. `follow-me status` shows the plans, the reviews, how many comments are still open, and the branch `apply` writes.
84
148
 
85
149
  ## Development
86
150
 
87
151
  ```sh
88
- npm run check # biome, strict tsc, node --test
89
- node bin/follow-me.ts <command>
152
+ npm install
153
+ npm run check # biome, strict tsc, and node --test, the same as CI
154
+ node bin/follow-me.ts <command> # run the CLI from source
90
155
  ```
91
156
 
92
- Tests run against real git repositories built by `test/fixture-repo.ts`.
93
- Decisions are logged in `docs/decisions.tsv`.
157
+ There is no build step for development. Node 24 runs the TypeScript sources directly, and `npm run build` exists only for publishing. Tests run real git against repositories built by `test/fixture-repo.ts`. Non-obvious decisions go in `docs/decisions.tsv` through `scripts/log-decision.sh`.
158
+
159
+ To release, run `npm version patch`, `minor`, or `major`, then `git push --follow-tags`. The tag triggers the publish workflow. [docs/releasing.md](docs/releasing.md) has the details.
160
+
161
+ ## Credits
162
+
163
+ Inspired by the "split your own changes" idea in [spark-joy](https://github.com/Dansyuqri/spark-joy). Built with [pstack](https://github.com/alexanderop/pstack).
164
+
165
+ MIT licensed.
@@ -9,16 +9,35 @@ export class PlanRequestError extends Error {
9
9
  this.failure = failure;
10
10
  }
11
11
  }
12
- const narratedUnit = {
12
+ const optionalText = { anyOf: [{ type: "string" }, { type: "null" }] };
13
+ const concern = {
13
14
  type: "object",
14
- properties: { unit: { type: "string" }, narration: { type: "string" } },
15
- required: ["unit", "narration"],
15
+ properties: {
16
+ severity: { type: "string", enum: ["blocker", "major", "minor"] },
17
+ kind: { type: "string", enum: ["bug", "missing-test", "naming", "risk"] },
18
+ text: { type: "string" },
19
+ lines: { anyOf: [{ type: "array", items: { type: "integer" } }, { type: "null" }] },
20
+ },
21
+ required: ["severity", "kind", "text", "lines"],
22
+ additionalProperties: false,
23
+ };
24
+ const concerns = { anyOf: [{ type: "array", items: concern }, { type: "null" }] };
25
+ const annotatedUnit = {
26
+ type: "object",
27
+ properties: {
28
+ unit: { type: "string" },
29
+ narration: optionalText,
30
+ diagram: optionalText,
31
+ concerns,
32
+ },
33
+ required: ["unit", "narration", "diagram", "concerns"],
16
34
  additionalProperties: false,
17
35
  };
18
- const unitList = { type: "array", items: { anyOf: [{ type: "string" }, narratedUnit] } };
36
+ const unitList = { type: "array", items: { anyOf: [{ type: "string" }, annotatedUnit] } };
19
37
  /**
20
38
  * Mirrors PlanFile on the wire. Structured outputs need every property required and closed
21
- * objects, so commit narration is nullable instead of optional; the plan parser reads null as none.
39
+ * objects, so narration, diagram, concerns and a response's `at` are nullable instead of
40
+ * optional; the plan parser reads null as none.
22
41
  */
23
42
  export const PLAN_SCHEMA = {
24
43
  type: "object",
@@ -32,10 +51,12 @@ export const PLAN_SCHEMA = {
32
51
  type: "object",
33
52
  properties: {
34
53
  message: { type: "string" },
35
- narration: { anyOf: [{ type: "string" }, { type: "null" }] },
54
+ narration: optionalText,
55
+ diagram: optionalText,
56
+ concerns,
36
57
  units: unitList,
37
58
  },
38
- required: ["message", "narration", "units"],
59
+ required: ["message", "narration", "diagram", "concerns", "units"],
39
60
  additionalProperties: false,
40
61
  },
41
62
  },
@@ -45,10 +66,21 @@ export const PLAN_SCHEMA = {
45
66
  type: "object",
46
67
  properties: {
47
68
  comment: { type: "string" },
48
- status: { type: "string", enum: ["addressed", "declined", "deferred"] },
69
+ status: { type: "string", enum: ["addressed", "declined", "deferred", "answered"] },
49
70
  note: { type: "string" },
71
+ at: {
72
+ anyOf: [
73
+ {
74
+ type: "object",
75
+ properties: { commit: { type: "integer" }, unit: optionalText },
76
+ required: ["commit", "unit"],
77
+ additionalProperties: false,
78
+ },
79
+ { type: "null" },
80
+ ],
81
+ },
50
82
  },
51
- required: ["comment", "status", "note"],
83
+ required: ["comment", "status", "note", "at"],
52
84
  additionalProperties: false,
53
85
  },
54
86
  },