planrails 0.5.0 → 0.5.2
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 +75 -2
- package/PLANNER.md +63 -40
- package/README.md +15 -9
- package/bin/planrails.mjs +10 -2
- package/package.json +1 -1
- package/tools/check-plans.mjs +7 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,78 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.2 — 2026-09-13
|
|
4
|
+
|
|
5
|
+
A second field test of 0.5.1, four scratch projects and seven fresh sessions, and
|
|
6
|
+
a newcomer's pass over the docs. The published behaviour held: a stale claim was
|
|
7
|
+
taken over by id, a live holder stopped a second session, a session assigned one
|
|
8
|
+
of two plans touched only that one. Four gaps in the plan's block, and one in the
|
|
9
|
+
worked example, are closed here.
|
|
10
|
+
|
|
11
|
+
- **Two sessions can work two plans in one checkout.** The block's rule for an
|
|
12
|
+
unheld plan (`session: none`, or no line) said stop whenever any session in the
|
|
13
|
+
checkout was busy or any plan file was dirty, so the second of two sessions
|
|
14
|
+
always had to ask. Now a session assigned an unheld plan writes its line and
|
|
15
|
+
goes on, unless *that plan's* files are dirty or a busy session in the checkout
|
|
16
|
+
has its id on no plan's `session:` line. The same-plan rule is unchanged: a
|
|
17
|
+
live holder, matched by id, stops you.
|
|
18
|
+
- **An `owner` proof is closed only by the person.** A field session closed an
|
|
19
|
+
owner task with a check of its own and an invented exit code; the checker cannot
|
|
20
|
+
see that. The block and §4 now say: the row stays `blocked` with the reason in
|
|
21
|
+
its evidence cell, and RESUME says so.
|
|
22
|
+
- **NOW keeps its shape, and the block says how a plan closes.** A session
|
|
23
|
+
renamed RESUME to DONE, wrote the `session:` line twice and left a finished plan
|
|
24
|
+
active. The block says NOW is exactly the four lines, and points at §5 for the
|
|
25
|
+
close. The checker now fails an active plan whose NOW section has no RESUME
|
|
26
|
+
line, the one hole a renamed line opened in the NOW rule.
|
|
27
|
+
- **The session rule is a list.** Its paragraph fell from 197 words to four
|
|
28
|
+
bullets; the whole block is about 480 words, up from 443, with the owner and
|
|
29
|
+
close clauses added.
|
|
30
|
+
- **Existing 0.5.0 and 0.5.1 plans:** replace the block with the template's, so
|
|
31
|
+
they carry the unheld-plan rule and the owner clause; `init` cannot tell them
|
|
32
|
+
from 0.5.2 plans, since they already have a `session:` line.
|
|
33
|
+
- **The worked example carries the template's block, byte for byte**, pinned by a
|
|
34
|
+
test. It had shipped the 0.5.0 paragraph that matched holders by name.
|
|
35
|
+
- **`init` points out plans that predate 0.5**: an active plan with no `session:`
|
|
36
|
+
line is named, with the by-hand steps from 0.5.0. `init` still never touches a
|
|
37
|
+
plan.
|
|
38
|
+
- Timestamps come from the shell clock: `date`, or `Get-Date` on Windows, where
|
|
39
|
+
`date` prompts. The README no longer suggests `ListAgents` can do the id match.
|
|
40
|
+
The 0.4.0 entry no longer overstates the stray-backtick fix. 77 tests.
|
|
41
|
+
|
|
42
|
+
## 0.5.1 — 2026-09-13
|
|
43
|
+
|
|
44
|
+
A fresh-context review of 0.5.0, run the same day, found the block — the one copy
|
|
45
|
+
of the rule that survives into a real plan — weaker than §4, and wrong in one
|
|
46
|
+
case: it matched a holder by name, and a name changes (accepting a plan in Claude
|
|
47
|
+
Code's plan mode retitles the session; `claude -n` and a resume can rename it), so
|
|
48
|
+
a live holder could read as stale and be taken over. This repo's own plan hit it
|
|
49
|
+
within the hour.
|
|
50
|
+
|
|
51
|
+
- **The block matches by session id.** Its paragraph now says what the line holds
|
|
52
|
+
(name · first 8 characters of the session id · since), that the id is what to
|
|
53
|
+
match, which listing to run and what it shows, the three outcomes — stop; stale
|
|
54
|
+
only when the id is unlisted *and* no other session in the checkout is live;
|
|
55
|
+
write your own line on `doing` and on a takeover — and what to do without a
|
|
56
|
+
listing: git is the record. Step 1 of the loop puts the session on the line.
|
|
57
|
+
- **§4** matches the id over the whole listing before any directory filter (a
|
|
58
|
+
session can hold a plan from another directory), drops the last-activity
|
|
59
|
+
advice, and adds the no-listing rule and "a plan you wrote in this session
|
|
60
|
+
already names you"; §1 speaks of plans in the plural and who holds each; the
|
|
61
|
+
"Why" bullet names no private repository.
|
|
62
|
+
- The skill puts the listing first and no longer says "or plausibly does"; the
|
|
63
|
+
worked example's `CLAUDE.md` carries the held-by sentence; the template pin now
|
|
64
|
+
fails when the NOW line is lost or the paragraph leaves the block. 74 tests.
|
|
65
|
+
- **Existing 0.5.0 plans:** replace the block's last paragraph and its step 1 with
|
|
66
|
+
the template's.
|
|
67
|
+
- Field-tested on these files, with PLAN.md hashes recorded and each case's start
|
|
68
|
+
state committed: a holder renamed after the claim was written was recognised
|
|
69
|
+
by its id and left alone; a plan with no line, a dirty PLAN.md and a busy peer
|
|
70
|
+
stopped the newcomer; told to continue the second of two plans, a session
|
|
71
|
+
claimed only that one and left the held plan byte-identical; a stale claim
|
|
72
|
+
with nothing else live was taken over with a correctly written line, and the
|
|
73
|
+
plan finished by the loop. 0.5.0's field test ran the block pasted into a 0.4.1
|
|
74
|
+
planner: the block was tested, §4 was not; this one runs the shipped bytes.
|
|
75
|
+
|
|
3
76
|
## 0.5.0 — 2026-09-13
|
|
4
77
|
|
|
5
78
|
One method change, paid for by a real collision. The reload line loads an active
|
|
@@ -69,8 +142,8 @@ nothing but its two files.
|
|
|
69
142
|
Re-run the proof and paste its exit code.
|
|
70
143
|
- **The checker fails closed on a row it cannot read.** The cell splitter follows
|
|
71
144
|
the CommonMark rule for backtick runs, so a stray backtick or three backticks in
|
|
72
|
-
prose
|
|
73
|
-
header's is a problem instead of a silent pass. A task table inside a code fence
|
|
145
|
+
prose are read the way CommonMark reads them; a row whose cell count still
|
|
146
|
+
differs from the header's is a problem instead of a silent pass. A task table inside a code fence
|
|
74
147
|
is ignored, a table right after the task table with no heading between is no
|
|
75
148
|
longer read as tasks, and `**id**` in a header is read.
|
|
76
149
|
- **Two integrity checks keep the reload honest.** When the project has a
|
package/PLANNER.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- planrails 0.5.
|
|
1
|
+
<!-- planrails 0.5.2 -->
|
|
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
|
|
62
|
-
|
|
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
|
|
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">
|
|
@@ -151,7 +152,8 @@ Pick a short kebab-case `<id>` (`weekly-digest`). Then:
|
|
|
151
152
|
https://github.com/vivmagarwal/planrails there. Add
|
|
152
153
|
`node .project-management/planrails/check-plans.mjs` to the check command. Now the
|
|
153
154
|
build fails if a task is marked done with no evidence, if an active plan has no
|
|
154
|
-
reload line,
|
|
155
|
+
reload line, if its NOW lost its RESUME line, or if NOW points at a finished
|
|
156
|
+
task. If the project is not Node,
|
|
155
157
|
skip this; the plan still works, and you enforce the gate yourself.
|
|
156
158
|
- **Check the plan before the first task.** You, or a fresh sub-agent with no
|
|
157
159
|
chat context: open every path the plan names, start every proof command,
|
|
@@ -177,23 +179,32 @@ which plan you are on, or none. Before any task goes `doing` — even when the
|
|
|
177
179
|
person just asked you to continue; they can forget which window holds the plan —
|
|
178
180
|
re-read that line from disk (your reloaded copy may be older than the file), list
|
|
179
181
|
the live sessions, and run `git status --short`. `claude agents --json` lists every
|
|
180
|
-
live session on the machine
|
|
181
|
-
`idle`, or `waiting` for the person) and `startedAt`;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
your claim
|
|
182
|
+
live session on the machine — an interactive one with `name`, `sessionId`, `cwd`,
|
|
183
|
+
`pid`, `status` (`busy`, `idle`, or `waiting` for the person) and `startedAt`; a
|
|
184
|
+
background one with `state` — and a headless `claude -p` run is listed too. Match
|
|
185
|
+
the line's id against `sessionId` over the whole listing; `cwd` then tells which
|
|
186
|
+
peers are in this checkout. Never filter by directory before matching the id: a
|
|
187
|
+
session can hold a plan from another directory. Names are for people: accepting
|
|
188
|
+
a plan in Claude Code's plan mode retitles the session, `claude -n` and a resume
|
|
189
|
+
can rename it. In Claude Code, `ListAgents` names you and the peers, but its
|
|
190
|
+
bracketed ref is not the session id; the id is `$CLAUDE_CODE_SESSION_ID`. Then
|
|
191
|
+
decide:
|
|
192
|
+
|
|
193
|
+
- The line's id is yours, whatever your name is now: go on. A plan you wrote in
|
|
194
|
+
this session already names you.
|
|
195
|
+
- Its id is live and not yours: stop. Busy is mid-task; idle is between turns
|
|
196
|
+
and still the holder; waiting means it needs the person — tell them.
|
|
197
|
+
- Its id is not listed: the claim is stale only when no other session in this
|
|
198
|
+
checkout is live — then say so, take over, write your claim. When one is live,
|
|
199
|
+
stop and ask; it may be the holder under a new id, after a branch or a fork.
|
|
200
|
+
- `none`, or no line (an older plan): say what you saw in one line, write your
|
|
201
|
+
claim, go on — unless this plan's files are dirty in `git status`, or a busy
|
|
202
|
+
session in this checkout has its id on no plan's `session:` line; then stop
|
|
203
|
+
and ask. A busy peer whose id is on another plan's line is working that plan,
|
|
204
|
+
so two sessions can work two plans in one checkout side by side.
|
|
205
|
+
- No `claude` command (another agent), or a plan shared across machines: there is
|
|
206
|
+
no listing to consult. Git is the record: pull first, read a `doing` row with a
|
|
207
|
+
fresh `updated:` stamp as someone's work in flight, and ask.
|
|
197
208
|
- A worktree is another checkout with its own copy of the plan: a merge concern
|
|
198
209
|
later, not a clobber now. A sub-agent you briefed never runs this check; it
|
|
199
210
|
never writes the plan.
|
|
@@ -209,14 +220,20 @@ The loop for each task:
|
|
|
209
220
|
|
|
210
221
|
1. **Check who holds the plan** (above), then **set it doing.** Change the status
|
|
211
222
|
cell to `doing`. Update **NOW**, and put your session on its `session:` line
|
|
212
|
-
(`since` from
|
|
223
|
+
(`since` from the shell clock on a takeover; unchanged when it already names
|
|
224
|
+
you).
|
|
213
225
|
2. **Do the work.** Fix the cause, not the symptom. The simplest change that
|
|
214
226
|
works, end to end.
|
|
215
227
|
3. **Run the proof.** Right now, not from memory. Copy the exit code and the last
|
|
216
|
-
line of output. Take the time from `date
|
|
228
|
+
line of output. Take the time from the shell clock (`date`; on Windows
|
|
229
|
+
`Get-Date`), never from memory.
|
|
217
230
|
4. **Paste the evidence.** Into the task's evidence cell:
|
|
218
231
|
`2026-09-12 14:20 · exit 0 · "6 passed"`. Exit 0, or the task is not done.
|
|
219
|
-
5. **Set it done.** Only now.
|
|
232
|
+
5. **Set it done.** Only now. A proof of `owner` is closed only by the person's
|
|
233
|
+
words with the date, never by you: until then the row is `blocked`, the
|
|
234
|
+
reason in its evidence cell, and RESUME says so.
|
|
235
|
+
Update **NOW** to point at the next task; NOW keeps its four lines and their
|
|
236
|
+
names, RESUME included, until the plan is retired.
|
|
220
237
|
6. **Append an entry to LOG.md** — what landed, what is next, anything learned,
|
|
221
238
|
any decision made.
|
|
222
239
|
7. **If the task fought back, record the learning.** An error, a wrong turn, an
|
|
@@ -279,9 +296,9 @@ the task `blocked` with the reason, and stop. Do not hand-fix state to look done
|
|
|
279
296
|
3. **Update the docs** the work changed — in the same step, per the project's
|
|
280
297
|
documentation guide if it has one.
|
|
281
298
|
4. **Retire the plan.** Set its header to `status: done` and NOW's `session:` to
|
|
282
|
-
`none`, then wrap its reload line in backticks, or delete it. Do not move a
|
|
283
|
-
"Finished" heading: it still imports, and every retired
|
|
284
|
-
forever. The status comes first: the checker holds an `active` plan to its
|
|
299
|
+
`none`, then wrap its reload line in backticks, or delete it. Do not move a
|
|
300
|
+
bare `@` line under a "Finished" heading: it still imports, and every retired
|
|
301
|
+
plan would reload forever. The status comes first: the checker holds an `active` plan to its
|
|
285
302
|
reload line. The plan files stay on disk; they are the record.
|
|
286
303
|
5. **Graduate any lasting learning.** A learning that is true beyond this feature
|
|
287
304
|
moves to the always-loaded file (`CLAUDE.md` / `AGENTS.md`), so it outlives the
|
|
@@ -322,12 +339,13 @@ Each line here was paid for by a real failure in earlier planning systems:
|
|
|
322
339
|
path a reader cannot open is worse than none.
|
|
323
340
|
- **The `session:` line and the check before `doing`** exist because the reload
|
|
324
341
|
line loads a plan into every session opened in a checkout, not only the one
|
|
325
|
-
working it. On 2026-09-13
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
342
|
+
working it. On 2026-09-13, a second session in one repo, opened for a tool
|
|
343
|
+
upgrade, read the reloaded plan, was told to continue, committed, and was
|
|
344
|
+
starting the next task while the first session was mid-edit on the same files
|
|
345
|
+
and the same PLAN.md; the owner interrupted. The person had asked, so the check
|
|
346
|
+
runs even then. The claim is a lead and the live listing decides, so a stale
|
|
347
|
+
line from a closed terminal blocks nobody — and it is matched by session id,
|
|
348
|
+
because the same day a session's name changed within the hour.
|
|
331
349
|
- **Nothing the method needs is installed.** `npx planrails init` only copies two
|
|
332
350
|
files, and you can copy them by hand instead. The old version was a heavy
|
|
333
351
|
package, and that is what broke on the projects that were not npm — Python,
|
|
@@ -355,20 +373,25 @@ status: active · opened <YYYY-MM-DD> · id: <kebab-id>
|
|
|
355
373
|
## NOW
|
|
356
374
|
RESUME: <T2 — the one thing to do next, with the file; written for a stranger>
|
|
357
375
|
NEXT: <T3 · T4 · …>
|
|
358
|
-
updated: <YYYY-MM-DD HH:MM, from
|
|
376
|
+
updated: <YYYY-MM-DD HH:MM, from the shell clock>
|
|
359
377
|
session: <none, or the session working this plan: name · the first 8 characters of its session id · since YYYY-MM-DD HH:MM>
|
|
360
378
|
|
|
361
379
|
## How to work this plan
|
|
362
380
|
Read NOW, then Rules and Learnings; do not re-read Context. One task at a time:
|
|
363
|
-
1.
|
|
381
|
+
1. Check who holds the plan (below); set it `doing`; point NOW at it; put your session on its `session:` line.
|
|
364
382
|
2. Do the work: fix causes, not symptoms; the simplest change that works end to end.
|
|
365
|
-
3. Run the proof now. Paste `YYYY-MM-DD HH:MM · exit N · "last line"` into evidence. Every stamp comes from `date
|
|
366
|
-
4. `done` only if N is 0. Point NOW at the next task; append an entry to LOG.md: did, files, proof, next, learned.
|
|
383
|
+
3. Run the proof now. Paste `YYYY-MM-DD HH:MM · exit N · "last line"` into evidence. Every stamp comes from the shell clock (`date`; on Windows `Get-Date`), never typed from memory.
|
|
384
|
+
4. `done` only if N is 0. A proof of `owner` is closed only by the person's words with the date, never by you: until then the row is `blocked`, the reason in its evidence cell, and RESUME says so. Point NOW at the next task; append an entry to LOG.md: did, files, proof, next, learned.
|
|
367
385
|
5. If it fought back, add a Learning: the trap, then the rule. A verified fact goes in Context, a choice in Decisions.
|
|
368
386
|
|
|
369
|
-
NOW is four lines for a stranger; on a long task note the sub-step, and update
|
|
387
|
+
NOW is exactly these four lines, RESUME, NEXT, updated and session, written for a stranger; on a long task note the sub-step in RESUME, and update NOW before any turn ends. Blocked: say so in RESUME, 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 tool; 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. When no row is left open, done or dropped, close by PLANNER.md §5: proofs re-run, a fresh-context review, `status: done`, `session: none`, the reload line in CLAUDE.md wrapped in backticks. Full method: `.project-management/planrails/PLANNER.md` §4.
|
|
370
388
|
|
|
371
|
-
|
|
389
|
+
Who holds the plan. `session:` names the one session working it: `name · first 8 characters of its session id · since YYYY-MM-DD HH:MM`. Match by id, never by name: the line's 8 characters start a `sessionId` in the listing. Before any task goes `doing`, even when asked to continue: re-read NOW from disk, run `claude agents --json` (your own id is `$CLAUDE_CODE_SESSION_ID`) and `git status --short`, then:
|
|
390
|
+
- the id is yours: go on.
|
|
391
|
+
- the id is live and not yours: stop, report what you found, ask; write nothing.
|
|
392
|
+
- the id is not listed: stale if no other session in this checkout is live; say so, take over, write your line. Else stop and ask.
|
|
393
|
+
- `none`, or no line: write your line and go on, unless this plan's PLAN.md, LOG.md or the files its tasks name are dirty, or a busy session in this checkout has its id on no plan's `session:` line; then stop and ask.
|
|
394
|
+
Work only the plan assigned in this session; other reloaded plans are context. Without a `claude` command, git is the record: pull first, and ask about a fresh `doing` row.
|
|
372
395
|
|
|
373
396
|
## Goal
|
|
374
397
|
<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
|
@@ -96,7 +96,7 @@ planner method does not depend on any language.
|
|
|
96
96
|
|
|
97
97
|
### As a `/planrails` command in Claude Code
|
|
98
98
|
|
|
99
|
-
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
|
|
100
100
|
the same flow in any project that has run `npx planrails init` — the skill reads
|
|
101
101
|
the project's own copy of `PLANNER.md`, so every project follows the version it
|
|
102
102
|
has. (It is not called `/plan`, because Claude Code has a `/plan` of its own.)
|
|
@@ -161,7 +161,8 @@ structural check, biased toward catching a faked "done":
|
|
|
161
161
|
- when the project has a `CLAUDE.md`, every active plan must be reloaded by
|
|
162
162
|
`@.project-management/plans/<id>/PLAN.md` on its own line, and no such line may
|
|
163
163
|
point at a plan that does not exist
|
|
164
|
-
- an active plan
|
|
164
|
+
- an active plan keeps its `RESUME` line, and that line must name a task that
|
|
165
|
+
is still open
|
|
165
166
|
|
|
166
167
|
`--verify` goes further and **runs** each proof again, with a timeout, and shows
|
|
167
168
|
the last line a failing proof printed. Because it executes the commands written in
|
|
@@ -192,15 +193,20 @@ directory, so a second session can read the plan and start the task the first on
|
|
|
192
193
|
is on. A plan's NOW names the session working it
|
|
193
194
|
(`session: app-3f · 7c1d2e9a · since 2026-09-12 14:05`), and the plan's block tells
|
|
194
195
|
every session, before it sets a task `doing`, to re-read that line, list the live
|
|
195
|
-
sessions (`claude agents --json
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
it. One plan per session, one
|
|
199
|
-
|
|
196
|
+
sessions (`claude agents --json`, matching the line's id; `ListAgents` inside
|
|
197
|
+
Claude Code names sessions but shows no id) and run `git status --short`, and to
|
|
198
|
+
stop and ask when another live session holds the plan — even when you asked it to
|
|
199
|
+
continue, because you can forget which window owns it. One plan per session, one
|
|
200
|
+
session per plan; the other reloaded plans are context. A session assigned an
|
|
201
|
+
unheld plan claims it and goes on, unless that plan's files are dirty or a busy
|
|
202
|
+
peer in this checkout holds no plan, so two sessions can work two plans side by
|
|
203
|
+
side. A plan
|
|
204
|
+
written before 0.5 has no `session:` line; `init` points it out, and the
|
|
205
|
+
changelog says what to add by hand. The claim is a lead, not a lock: a line left by a closed terminal blocks
|
|
200
206
|
nobody, and the checker ignores the line. Field-tested with headless sessions: a
|
|
201
207
|
second session told "Continue the active plan." stopped and asked while the first
|
|
202
|
-
was live,
|
|
203
|
-
two were active.
|
|
208
|
+
was live — by its id, after the first had been renamed — took over when it was
|
|
209
|
+
gone, and touched only the plan it was given when two were active.
|
|
204
210
|
|
|
205
211
|
## History
|
|
206
212
|
|
package/bin/planrails.mjs
CHANGED
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
*
|
|
16
16
|
* planrails --version | --help
|
|
17
17
|
*/
|
|
18
|
-
import { readFileSync, copyFileSync, mkdirSync, existsSync, writeFileSync, realpathSync } from "node:fs";
|
|
18
|
+
import { readFileSync, copyFileSync, mkdirSync, existsSync, writeFileSync, realpathSync, readdirSync } from "node:fs";
|
|
19
19
|
import { join, dirname, resolve } from "node:path";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
|
-
import { checkPlans } from "../tools/check-plans.mjs";
|
|
21
|
+
import { checkPlans, isActive } from "../tools/check-plans.mjs";
|
|
22
22
|
|
|
23
23
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
24
24
|
const version = () => JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")).version;
|
|
@@ -77,6 +77,14 @@ function init(args) {
|
|
|
77
77
|
const keep = join(plans, ".gitkeep");
|
|
78
78
|
if (!existsSync(keep)) { writeFileSync(keep, ""); console.log(" + .project-management/plans/"); }
|
|
79
79
|
else console.log(" = .project-management/plans/ (your plans are never touched)");
|
|
80
|
+
// A plan written before 0.5 has no session: line and an older block. init never
|
|
81
|
+
// touches a plan, so say which ones need the by-hand update from CHANGELOG 0.5.0.
|
|
82
|
+
for (const name of existsSync(plans) ? readdirSync(plans) : []) {
|
|
83
|
+
const pf = join(plans, name, "PLAN.md");
|
|
84
|
+
if (!existsSync(pf)) continue;
|
|
85
|
+
const text = readFileSync(pf, "utf8");
|
|
86
|
+
if (isActive(text) && !/^session:/m.test(text)) console.log(` ! plan ${name} predates 0.5: add a session: line to its NOW and replace its "How to work this plan" block with the template's in .project-management/planrails/PLANNER.md; until then a second session cannot tell who holds it`);
|
|
87
|
+
}
|
|
80
88
|
const loose = ["PLANNER.md", "check-plans.mjs"].filter((f) => existsSync(join(pm, f)));
|
|
81
89
|
if (loose.length) console.log(` ! 0.2.x files at .project-management/ root: ${loose.join(", ")} — the copies now live in planrails/; delete the loose ones and point your check command at .project-management/planrails/check-plans.mjs`);
|
|
82
90
|
|
package/package.json
CHANGED
package/tools/check-plans.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// planrails 0.5.
|
|
2
|
+
// planrails 0.5.2
|
|
3
3
|
/**
|
|
4
4
|
* check-plans — the machine-checked rails of the planner.
|
|
5
5
|
*
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* - an active plan must reload: when the project has a CLAUDE.md, it must carry
|
|
12
12
|
* `@.project-management/plans/<id>/PLAN.md` on its own line, and no such line
|
|
13
13
|
* may point at a plan that does not exist. (Skipped when there is no CLAUDE.md.)
|
|
14
|
-
* - NOW must be current:
|
|
15
|
-
* finished tasks. A retired plan says `status: done` (or paused) and is exempt
|
|
14
|
+
* - NOW must be current: an active plan with a NOW section keeps its RESUME
|
|
15
|
+
* line, and that line may not name only finished tasks. A retired plan says `status: done` (or paused) and is exempt
|
|
16
16
|
* from both; a plan with no status line counts as active.
|
|
17
17
|
*
|
|
18
18
|
* The rule is biased toward catching a faked "done": a task counts as a
|
|
@@ -229,8 +229,11 @@ export function checkPlan({ id, text, verify = false, run = null }) {
|
|
|
229
229
|
if (code !== 0) problems.push(`${id} ${t.id}: proof re-run failed — \`${cmd}\` exited ${code}${r.last ? ` — ${r.last}` : ""}`);
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
|
-
// NOW must point somewhere live:
|
|
232
|
+
// NOW must point somewhere live: an active plan keeps its RESUME line, and a
|
|
233
|
+
// RESUME line that names only finished tasks is stale.
|
|
233
234
|
if (isActive(text)) {
|
|
235
|
+
const ls = text.split(/\r?\n/), fm = fenceMask(ls);
|
|
236
|
+
if (ls.some((l, k) => !fm[k] && /^#{1,6}\s+NOW\b/.test(l)) && !ls.some((l, k) => !fm[k] && /^\s*RESUME:/.test(l))) problems.push(`${id}: NOW has no RESUME line — an active plan keeps RESUME, NEXT, updated and session; a finished plan says status: done`);
|
|
234
237
|
const named = resumeIds(text, tasks.map((t) => t.id).filter(Boolean));
|
|
235
238
|
if (named.length && named.every((n) => tasks.some((t) => t.id === n && isCompletionClaim(t.status))))
|
|
236
239
|
problems.push(`${id}: NOW is stale — RESUME names ${named.map((n) => n.replace(/[`*_]/g, "")).join(", ")}, which ${named.length === 1 ? "is" : "are all"} done; point it at the next open task, or set the plan's status to done`);
|