planrails 0.4.1 → 0.5.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,83 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.1 — 2026-09-13
4
+
5
+ A fresh-context review of 0.5.0, run the same day, found the block — the one copy
6
+ of the rule that survives into a real plan — weaker than §4, and wrong in one
7
+ case: it matched a holder by name, and a name changes (accepting a plan in Claude
8
+ Code's plan mode retitles the session; `claude -n` and a resume can rename it), so
9
+ a live holder could read as stale and be taken over. This repo's own plan hit it
10
+ within the hour.
11
+
12
+ - **The block matches by session id.** Its paragraph now says what the line holds
13
+ (name · first 8 characters of the session id · since), that the id is what to
14
+ match, which listing to run and what it shows, the three outcomes — stop; stale
15
+ only when the id is unlisted *and* no other session in the checkout is live;
16
+ write your own line on `doing` and on a takeover — and what to do without a
17
+ listing: git is the record. Step 1 of the loop puts the session on the line.
18
+ - **§4** matches the id over the whole listing before any directory filter (a
19
+ session can hold a plan from another directory), drops the last-activity
20
+ advice, and adds the no-listing rule and "a plan you wrote in this session
21
+ already names you"; §1 speaks of plans in the plural and who holds each; the
22
+ "Why" bullet names no private repository.
23
+ - The skill puts the listing first and no longer says "or plausibly does"; the
24
+ worked example's `CLAUDE.md` carries the held-by sentence; the template pin now
25
+ fails when the NOW line is lost or the paragraph leaves the block. 74 tests.
26
+ - **Existing 0.5.0 plans:** replace the block's last paragraph and its step 1 with
27
+ the template's.
28
+ - Field-tested on these files, with PLAN.md hashes recorded and each case's start
29
+ state committed: a holder renamed after the claim was written was recognised
30
+ by its id and left alone; a plan with no line, a dirty PLAN.md and a busy peer
31
+ stopped the newcomer; told to continue the second of two plans, a session
32
+ claimed only that one and left the held plan byte-identical; a stale claim
33
+ with nothing else live was taken over with a correctly written line, and the
34
+ plan finished by the loop. 0.5.0's field test ran the block pasted into a 0.4.1
35
+ planner: the block was tested, §4 was not; this one runs the shipped bytes.
36
+
37
+ ## 0.5.0 — 2026-09-13
38
+
39
+ One method change, paid for by a real collision. The reload line loads an active
40
+ plan into every session opened in a checkout, and on 2026-09-13 a second session
41
+ in one repo read the reloaded plan, was told "continue", committed, and was
42
+ starting the next task while the first session was mid-edit on the same files and
43
+ the same PLAN.md. The checker is unchanged apart from its stamp; `init` still
44
+ writes only its two files.
45
+
46
+ - **A plan names the session working it.** NOW gains a fourth line,
47
+ `session: <name> · <first 8 characters of the session id> · since <date>`, or
48
+ `session: none` — written when the plan is written, when a task goes `doing`,
49
+ and on a takeover; cleared at close. It is a lead, not a lock: whether that
50
+ session is alive is decided by the live listing (`claude agents --json`, which
51
+ is documented and lists headless runs too; `ListAgents` inside Claude Code), so
52
+ a stale line from a closed terminal blocks nobody.
53
+ - **The plan's block runs the check.** "How to work this plan" now tells a
54
+ session, before any task goes `doing` and even when the person asked it to
55
+ continue: re-read NOW from disk, list the live sessions, run
56
+ `git status --short`, and — if the line names a live session that is not you,
57
+ or says `none` while a peer in the checkout is busy or plan files are dirty —
58
+ stop, report and ask before writing to the plan, its files, or a commit.
59
+ Several reloaded plans: work only the one assigned in this session, and say
60
+ which.
61
+ - **One plan per session, one session per plan** replaces "one active plan per
62
+ repo". Several plans may be active, each with its own reload line and one
63
+ holder; keep them few, since every session reloads all of them. The "Active
64
+ plans" heading in `CLAUDE.md` carries one line saying so.
65
+ - **PLANNER.md** §3 has the rule and "name the session"; §4 the check, the listing
66
+ and the decision rules (busy, idle, waiting, stale, a plan with no line, a
67
+ worktree, a sub-agent), and how to stop and ask; §5 clears the line at close;
68
+ "Why it is shaped this way" records the incident.
69
+ - **Existing plans: update by hand.** `init` never touches a plan. Add the
70
+ `session:` line to NOW, change "NOW is three lines" to four, and append the
71
+ block's last paragraph from the template. A plan without them still passes the
72
+ checker, which ignores the line; a test pins that.
73
+ - Field-tested with headless sessions in a scratch project: told only "Continue
74
+ the active plan.", a second session stopped, reported and asked while the
75
+ holder was live (idle with a clean tree; busy with a dirty PLAN.md and no
76
+ line), and wrote nothing; took over a stale claim and finished the plan by the
77
+ loop; and, told to continue the second of two plans, wrote its claim on that
78
+ one and left the held plan byte-identical. One wording gap found and fixed: the
79
+ id is the session id, not the ref `ListAgents` prints. 73 tests.
80
+
3
81
  ## 0.4.1 — 2026-09-13
4
82
 
5
83
  - The Claude Code skill is `/planrails`, not `/plan`, because Claude Code has a
package/PLANNER.md CHANGED
@@ -1,4 +1,4 @@
1
- <!-- planrails 0.4.1 -->
1
+ <!-- planrails 0.5.1 -->
2
2
  # The Planner
3
3
 
4
4
  You are about to plan a piece of work with a person, then help execute it so the
@@ -58,8 +58,9 @@ tell you what the repo already says.
58
58
  2. **List `docs/`** and read the ones this feature touches. Use sub-agents for
59
59
  long files so your own context stays clear. Record each relevant doc's path
60
60
  and one line of what it holds.
61
- 3. **Read `.project-management/`.** Read the active plan in full; list the
62
- finished ones by id only.
61
+ 3. **Read `.project-management/`.** Read in full the plan this session is
62
+ assigned the person names it; if one plan is active, that one — and list
63
+ the others by id, with who holds each (NOW's `session:` line).
63
64
  4. **Read the last ~20 commits** (`git log --oneline -20`) for how the code moves.
64
65
 
65
66
  **A sub-agent's finding is a lead, not a fact.** If it names a file and line, you
@@ -73,7 +74,7 @@ READY — <project name>
73
74
  Stack: <language, framework, package manager>
74
75
  Always-on: <CLAUDE.md | AGENTS.md | README.md>
75
76
  Check: <the check command> Test: <the test command>
76
- Plans: <existing plan ids; which is active, or "none">
77
+ Plans: <existing plan ids; which are active and who holds each, or "none">
77
78
  Docs: <relevant doc paths, or "none">
78
79
  Touches: <the folders this feature will change>
79
80
  Questions: <up to 4 things the repo did not answer, or "none">
@@ -125,10 +126,26 @@ Pick a short kebab-case `<id>` (`weekly-digest`). Then:
125
126
  Put it on its own line, outside any code block — an `@` import wrapped in
126
127
  backticks does not load. Claude Code re-reads that file, and everything it
127
128
  imports, at every session start and after every compaction, so the plan comes
128
- back on its own. One active plan per repo at a time; to pause another, set its
129
- header to `status: paused` and wrap its line in backticks. (For a tool that
129
+ back on its own. Under the same heading, one line says how the plans are held:
130
+ *Each plan below is held by one session — see its NOW `session:` line. Work one
131
+ only when asked in this session, after the check in its block.* (For a tool that
130
132
  does not do `@`-imports, put the plan's path in `AGENTS.md` and open it by hand
131
133
  at the start of each session.)
134
+ - **One plan per session, one session per plan.** A repo may hold several active
135
+ plans, each with its own reload line and each held by one session, named on its
136
+ NOW `session:` line. Every session reloads all of them, so keep them few; a plan
137
+ nobody is working is paused (`status: paused`, its line in backticks). A session
138
+ works only the plan the person in that session assigned; the other reloaded
139
+ plans are context, not work orders. Naming the session after the plan
140
+ (`claude -n <id>`) shows the holder in every listing and in the terminal title;
141
+ Claude Code gives a second live session with that name a variant suffix.
142
+ - **Name the session.** NOW's `session:` line names the session that will execute
143
+ the plan: yours, if that is you. In Claude Code the name is what `ListAgents`
144
+ prints after "This session is", and the id is the first eight characters of
145
+ `$CLAUDE_CODE_SESSION_ID` — the `sessionId` that `claude agents --json` prints,
146
+ not the bracketed ref `ListAgents` shows. The id survives a resume when the name
147
+ may not. Elsewhere, write anything a reader can tell apart (`codex@mbp tty003`);
148
+ `none` when handing the plan to someone else.
132
149
  - **Wire the checker, if the project runs Node and has a check command.**
133
150
  `npx planrails init` already put it at `.project-management/planrails/check-plans.mjs`;
134
151
  if you did not run init, copy `tools/check-plans.mjs` from
@@ -154,9 +171,53 @@ for — read them and you skip the struggle instead of repeating it. After a
154
171
  compaction, also run `git status --short`: it is the journal of in-flight work
155
172
  that NOW may not mention yet.
156
173
 
174
+ **Find out who holds the plan before you touch it.** The reload line puts every
175
+ active plan into every session opened in this checkout; a plan in your context is
176
+ not a work order. At session start, read NOW's `session:` line and say in one line
177
+ which plan you are on, or none. Before any task goes `doing` — even when the
178
+ person just asked you to continue; they can forget which window holds the plan —
179
+ re-read that line from disk (your reloaded copy may be older than the file), list
180
+ the live sessions, and run `git status --short`. `claude agents --json` lists every
181
+ live session on the machine — an interactive one with `name`, `sessionId`, `cwd`,
182
+ `pid`, `status` (`busy`, `idle`, or `waiting` for the person) and `startedAt`; a
183
+ background one with `state` — and a headless `claude -p` run is listed too. Match
184
+ the line's id against `sessionId` over the whole listing; `cwd` then tells which
185
+ peers are in this checkout. Never filter by directory before matching the id: a
186
+ session can hold a plan from another directory. Names are for people: accepting
187
+ a plan in Claude Code's plan mode retitles the session, `claude -n` and a resume
188
+ can rename it. In Claude Code, `ListAgents` names you and the peers, but its
189
+ bracketed ref is not the session id; the id is `$CLAUDE_CODE_SESSION_ID`. Then
190
+ decide:
191
+
192
+ - The line's id is yours, whatever your name is now: go on. A plan you wrote in
193
+ this session already names you.
194
+ - Its id is live and not yours: stop. Busy is mid-task; idle is between turns
195
+ and still the holder; waiting means it needs the person — tell them.
196
+ - Its id is not listed: the claim is stale only when no other session in this
197
+ checkout is live — then say so, take over, write your claim. When one is live,
198
+ stop and ask; it may be the holder under a new id, after a branch or a fork.
199
+ - `none`, or no line (an older plan): a busy session in this checkout, or plan
200
+ files dirty in `git status`: stop. Otherwise say what you saw in one line, write
201
+ your claim, go on.
202
+ - No `claude` command (another agent), or a plan shared across machines: there is
203
+ no listing to consult. Git is the record: pull first, read a `doing` row with a
204
+ fresh `updated:` stamp as someone's work in flight, and ask.
205
+ - A worktree is another checkout with its own copy of the plan: a merge concern
206
+ later, not a clobber now. A sub-agent you briefed never runs this check; it
207
+ never writes the plan.
208
+
209
+ Stopping means: report the session's name, status and start, what NOW says and
210
+ what `git status` shows, then ask — `AskUserQuestion` in Claude Code; a `claude -p`
211
+ run ends its turn with the question — whether to leave the plan to that session,
212
+ take it over here once they have stopped it, or work something else. Write nothing
213
+ to the plan, its files, or a commit until they answer. Stage commits by path;
214
+ another session's in-flight edits are not yours to sweep up.
215
+
157
216
  The loop for each task:
158
217
 
159
- 1. **Set it doing.** Change the status cell to `doing`. Update **NOW**.
218
+ 1. **Check who holds the plan** (above), then **set it doing.** Change the status
219
+ cell to `doing`. Update **NOW**, and put your session on its `session:` line
220
+ (`since` from `date` on a takeover; unchanged when it already names you).
160
221
  2. **Do the work.** Fix the cause, not the symptom. The simplest change that
161
222
  works, end to end.
162
223
  3. **Run the proof.** Right now, not from memory. Copy the exit code and the last
@@ -225,10 +286,10 @@ the task `blocked` with the reason, and stop. Do not hand-fix state to look done
225
286
  defects the self-tests had passed.
226
287
  3. **Update the docs** the work changed — in the same step, per the project's
227
288
  documentation guide if it has one.
228
- 4. **Retire the plan.** Set its header to `status: done`, then wrap its reload
229
- line in backticks, or delete it. Do not move a bare `@` line under a
230
- "Finished" heading: it still imports, and every retired plan would reload
231
- forever. The status comes first: the checker holds an `active` plan to its
289
+ 4. **Retire the plan.** Set its header to `status: done` and NOW's `session:` to
290
+ `none`, then wrap its reload line in backticks, or delete it. Do not move a
291
+ bare `@` line under a "Finished" heading: it still imports, and every retired
292
+ plan would reload forever. The status comes first: the checker holds an `active` plan to its
232
293
  reload line. The plan files stay on disk; they are the record.
233
294
  5. **Graduate any lasting learning.** A learning that is true beyond this feature
234
295
  moves to the always-loaded file (`CLAUDE.md` / `AGENTS.md`), so it outlives the
@@ -261,12 +322,21 @@ Each line here was paid for by a real failure in earlier planning systems:
261
322
  repeated. The same struggle coming back in a new chat is the exact failure this
262
323
  fixes.
263
324
  - **History stays out of the plan.** One plan grew a 16,000-word progress section,
264
- stamped two hours behind its own log. NOW is three lines; LOG.md is the history.
325
+ stamped two hours behind its own log. NOW is four lines; LOG.md is the history.
265
326
  - **Sub-agent findings are leads** because four spot-checked findings were each
266
327
  right in direction and wrong in number, and a wrong number becomes a wrong plan.
267
328
  Delegation is method, not machinery: a brief of one unit and nothing else worked.
268
329
  - **Repo-relative paths** because absolute paths break on the next machine, and a
269
330
  path a reader cannot open is worse than none.
331
+ - **The `session:` line and the check before `doing`** exist because the reload
332
+ line loads a plan into every session opened in a checkout, not only the one
333
+ working it. On 2026-09-13, a second session in one repo, opened for a tool
334
+ upgrade, read the reloaded plan, was told to continue, committed, and was
335
+ starting the next task while the first session was mid-edit on the same files
336
+ and the same PLAN.md; the owner interrupted. The person had asked, so the check
337
+ runs even then. The claim is a lead and the live listing decides, so a stale
338
+ line from a closed terminal blocks nobody — and it is matched by session id,
339
+ because the same day a session's name changed within the hour.
270
340
  - **Nothing the method needs is installed.** `npx planrails init` only copies two
271
341
  files, and you can copy them by hand instead. The old version was a heavy
272
342
  package, and that is what broke on the projects that were not npm — Python,
@@ -295,16 +365,19 @@ status: active · opened <YYYY-MM-DD> · id: <kebab-id>
295
365
  RESUME: <T2 — the one thing to do next, with the file; written for a stranger>
296
366
  NEXT: <T3 · T4 · …>
297
367
  updated: <YYYY-MM-DD HH:MM, from `date`>
368
+ session: <none, or the session working this plan: name · the first 8 characters of its session id · since YYYY-MM-DD HH:MM>
298
369
 
299
370
  ## How to work this plan
300
371
  Read NOW, then Rules and Learnings; do not re-read Context. One task at a time:
301
- 1. Set it `doing`; point NOW at it.
372
+ 1. Set it `doing`; point NOW at it; put your session on its `session:` line.
302
373
  2. Do the work: fix causes, not symptoms; the simplest change that works end to end.
303
374
  3. Run the proof now. Paste `YYYY-MM-DD HH:MM · exit N · "last line"` into evidence. Every stamp comes from `date` run at that moment, never typed from memory.
304
375
  4. `done` only if N is 0. Point NOW at the next task; append an entry to LOG.md: did, files, proof, next, learned.
305
376
  5. If it fought back, add a Learning: the trap, then the rule. A verified fact goes in Context, a choice in Decisions.
306
377
 
307
- NOW is three lines for a stranger; on a long task note the sub-step, and update it before any turn ends. Blocked: say so in NOW, reason in the evidence cell. A task you will not do is `dropped`; its row stays. After a compaction, `git status --short` shows the in-flight work. Edit this file with the editor or a quoted heredoc; an unquoted shell string eats backticks. A self-contained task may go to a sub-agent briefed with its row, Rules, Decisions, Learnings and Context; it writes to a named file and reports a few lines, which are leads; you run the proof before pasting evidence. If the project runs Node, `node .project-management/planrails/check-plans.mjs` must pass. Full method: `.project-management/planrails/PLANNER.md` §4.
378
+ NOW is four lines for a stranger; on a long task note the sub-step, and update it before any turn ends. Blocked: say so in NOW, reason in the evidence cell. A task you will not do is `dropped`; its row stays. After a compaction, `git status --short` shows the in-flight work. Edit this file with the editor or a quoted heredoc; an unquoted shell string eats backticks. A self-contained task may go to a sub-agent briefed with its row, Rules, Decisions, Learnings and Context; it writes to a named file and reports a few lines, which are leads; you run the proof before pasting evidence. If the project runs Node, `node .project-management/planrails/check-plans.mjs` must pass. Full method: `.project-management/planrails/PLANNER.md` §4.
379
+
380
+ The `session:` line names the one session working this plan: name · first 8 characters of its session id · since. Match by id; names change. `claude agents --json` lists every live session on this machine with `sessionId`, `cwd` and `status`; in Claude Code your id is `$CLAUDE_CODE_SESSION_ID`. Before any task goes `doing`, even when asked to continue: re-read NOW from disk, run that listing and `git status --short`. Stop, report what you found, and ask before writing to the plan, its files, or a commit if the line's id is live and not yours, or if the line is `none` or missing while another session in this checkout is busy or plan files are dirty. If the id is not listed and no other session in this checkout is live, the claim is stale: say so, take over. On `doing` and on a takeover, write your own name · id · since, from `date`. Without a `claude` command, or on another machine, git is the record: pull first, treat a fresh `doing` row as someone's work in flight, and ask. Other reloaded plans are context; work only the one assigned in this session, and say which.
308
381
 
309
382
  ## Goal
310
383
  <4–5 sentences: what we are building and why. What is true when it ships. A mermaid diagram only if the architecture is non-trivial.>
package/README.md CHANGED
@@ -9,8 +9,10 @@ work that is not. planrails fixes that with three plain rules and almost no code
9
9
 
10
10
  1. **The plan reloads itself.** One line in your project's root `CLAUDE.md`
11
11
  re-opens the plan after every compaction, and the plan carries its own
12
- operating loop, so a session that never saw the planner prompt still works it
13
- correctly. (Claude Code reloads it for you; another agent opens it by hand.)
12
+ operating loop and the name of the session working it, so a session that
13
+ never saw the planner prompt still works it correctly and a second session
14
+ in the same checkout checks for the first before it touches the plan. (Claude
15
+ Code reloads it for you; another agent opens it by hand.)
14
16
  2. **Every task names its proof before the work starts** — the command that will
15
17
  show it is done. No command, no way to fake it later.
16
18
  3. **A task is done only when its proof was run and its exit code pasted in.** An
@@ -94,7 +96,7 @@ planner method does not depend on any language.
94
96
 
95
97
  ### As a `/planrails` command in Claude Code
96
98
 
97
- Copy [`skill/`](skill) to `~/.claude/skills/planrails/`. Then `/planrails` starts
99
+ Copy [`skill/`](https://github.com/vivmagarwal/planrails/tree/main/skill) to `~/.claude/skills/planrails/`. Then `/planrails` starts
98
100
  the same flow in any project that has run `npx planrails init` — the skill reads
99
101
  the project's own copy of `PLANNER.md`, so every project follows the version it
100
102
  has. (It is not called `/plan`, because Claude Code has a `/plan` of its own.)
@@ -120,9 +122,10 @@ status: active · opened 2026-09-12 · id: weekly-digest
120
122
  RESUME: T2 — render the digest through the email seam (lib/digest/render.ts)
121
123
  NEXT: T3 schedule the Monday send · T4 docs
122
124
  updated: 2026-09-12 14:20
125
+ session: app-3f · 7c1d2e9a · since 2026-09-12 14:05
123
126
 
124
127
  ## How to work this plan
125
- (the loop a session follows: set doing, run the proof, paste exit N, done on 0, update NOW, log it)
128
+ (the loop a session follows: set doing, run the proof, paste exit N, done on 0, update NOW, log it — and, before any doing, the check that no other live session holds the plan)
126
129
 
127
130
  ## Tasks
128
131
  | id | task | status | proof | evidence |
@@ -177,6 +180,27 @@ trusted pipeline.
177
180
  - **Decide whether to commit `.project-management/`.** Committing it shares plans
178
181
  and lets CI run the checker. If your repo gitignores it, the checker still runs
179
182
  locally and the plan still reloads for whoever has the files.
183
+ - **Two machines, one plan.** The `session:` line names a session on the machine
184
+ that wrote it; another machine cannot list it. There, git is the record: pull
185
+ before you take a task, and read a `doing` row with a fresh `updated:` stamp as
186
+ someone's in-flight work.
187
+
188
+ ## Several sessions in one checkout
189
+
190
+ The reload line puts an active plan into every Claude Code session opened in that
191
+ directory, so a second session can read the plan and start the task the first one
192
+ is on. A plan's NOW names the session working it
193
+ (`session: app-3f · 7c1d2e9a · since 2026-09-12 14:05`), and the plan's block tells
194
+ every session, before it sets a task `doing`, to re-read that line, list the live
195
+ sessions (`claude agents --json`; `ListAgents` inside Claude Code) and run
196
+ `git status --short`, and to stop and ask when another live session holds the plan
197
+ — even when you asked it to continue, because you can forget which window owns
198
+ it. One plan per session, one session per plan; the other reloaded plans are
199
+ context. The claim is a lead, not a lock: a line left by a closed terminal blocks
200
+ nobody, and the checker ignores the line. Field-tested with headless sessions: a
201
+ second session told "Continue the active plan." stopped and asked while the first
202
+ was live — by its id, after the first had been renamed — took over when it was
203
+ gone, and touched only the plan it was given when two were active.
180
204
 
181
205
  ## History
182
206
 
@@ -188,7 +212,8 @@ rails, as a prompt plus one checker, with a two-command CLI that only copies
188
212
  files. 0.3.0 makes learnings a reloaded part of every plan and groups the two
189
213
  installed files under `.project-management/planrails/`. 0.4.0 makes the plan
190
214
  carry its own loop, makes the checker demand `exit 0` and check the reload line,
191
- and teaches the executor to brief sub-agents from the plan. See
192
- [`CHANGELOG.md`](CHANGELOG.md).
215
+ and teaches the executor to brief sub-agents from the plan. 0.5.0 names the
216
+ session working a plan and has every session check for a live holder before it
217
+ touches the plan. See [`CHANGELOG.md`](CHANGELOG.md).
193
218
 
194
219
  MIT.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "planrails",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "Plans that survive a lost session, and \"done\" that means done. A planner prompt and a tiny checker.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // planrails 0.4.1
2
+ // planrails 0.5.1
3
3
  /**
4
4
  * check-plans — the machine-checked rails of the planner.
5
5
  *