planrails 0.1.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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +225 -0
- package/bin/planrails.mjs +7 -0
- package/docs/PLANNING_GUIDE.md +632 -0
- package/package.json +51 -0
- package/src/hooks/_lib.mjs +32 -0
- package/src/hooks/guard-never-delete.sh +29 -0
- package/src/hooks/install.mjs +173 -0
- package/src/hooks/plan-pre-tool.mjs +71 -0
- package/src/hooks/plan-session-start.mjs +46 -0
- package/src/hooks/plan-stop.mjs +60 -0
- package/src/hooks/plan-subagent-start.mjs +27 -0
- package/src/hooks/postcompact-journal.mjs +35 -0
- package/src/hooks/precompact-journal.mjs +128 -0
- package/src/hooks/selftest.mjs +128 -0
- package/src/init.mjs +155 -0
- package/src/issue.mjs +55 -0
- package/src/plan/fixtures/README.md +7 -0
- package/src/plan/fixtures/broken-cli.mjs +27 -0
- package/src/plan/fixtures/broken-hooks-root/.claude/settings.json +83 -0
- package/src/plan/fixtures/broken-hooks-root/.project-management/plans/.gitkeep +0 -0
- package/src/plan/fixtures/broken-hooks-root/CLAUDE.md +9 -0
- package/src/plan/fixtures/broken-root/.project-management/plans/broken/PLAN.md +4 -0
- package/src/plan/fixtures/broken-root/.project-management/plans/broken/gates.json +1 -0
- package/src/plan/fixtures/broken-root/.project-management/plans/broken/rules.json +1 -0
- package/src/plan/fixtures/broken-root/.project-management/plans/broken/state.json +67 -0
- package/src/plan/fixtures/broken-root/CLAUDE.md +3 -0
- package/src/plan/fixtures/broken-trial.mjs +25 -0
- package/src/plan/lib/brief.mjs +116 -0
- package/src/plan/lib/claude-md.mjs +66 -0
- package/src/plan/lib/glob.mjs +81 -0
- package/src/plan/lib/judgment.mjs +19 -0
- package/src/plan/lib/paths.mjs +65 -0
- package/src/plan/lib/schema.mjs +199 -0
- package/src/plan/lib/store.mjs +338 -0
- package/src/plan/lib/time.mjs +21 -0
- package/src/plan/plan.mjs +843 -0
- package/src/plan/run.mjs +88 -0
- package/src/plan/skill/SKILL.md +15 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-09-12
|
|
4
|
+
|
|
5
|
+
First release, extracted from the project it was built in after a day of use.
|
|
6
|
+
|
|
7
|
+
- `planrails init` sets a new or an existing project up, idempotently; `update`, `uninstall`, `doctor`.
|
|
8
|
+
- Plans as files: PLAN.md, state.json, gates.json, rules.json, append-only log / learnings / decisions / gate-runs.
|
|
9
|
+
- A task is done only through `task done`: it runs the gate, records the run, and needs every condition of done answered (C1–C7 by default, more per plan or per task).
|
|
10
|
+
- Gates declare what they do not check and a known-fail case; `gate verify` proves each can fail; a gate edited after verification cannot close a task.
|
|
11
|
+
- Hooks for Claude Code: brief at SessionStart (also after compaction), rules at the tool call, a Stop reminder to log, a subagent brief, a pre-compaction journal. Portable settings via `$CLAUDE_PROJECT_DIR`.
|
|
12
|
+
- `run`: one fresh `claude -p` session per task.
|
|
13
|
+
- `issue`: a prefilled GitHub issue with the environment.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vivmagarwal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# planrails
|
|
2
|
+
|
|
3
|
+
**Plans that survive compaction, for Claude Code.** A plan lives in files, not in the chat. A task is done only when a gate ran and every condition of done was answered. Hooks put the plan in front of the agent at the moments it matters: session start, after a compaction, at the tool call, before it stops.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx planrails init # in a new directory or an existing project
|
|
7
|
+
# restart Claude Code, then type: /plan <your raw plan>
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Built and used on a real project for a day before it was extracted, with the reviews, the black-box trial and the measurements that shaped it recorded in the guide it ships with.
|
|
11
|
+
|
|
12
|
+
## The problem it solves
|
|
13
|
+
|
|
14
|
+
A Claude Code session forgets. Compaction keeps your messages and drops every tool result, so the agent loses the record of what it already did. A new session starts blind. The usual fixes make things worse: progress notes written from memory at the end of a session go stale, and every rule learned gets pasted into CLAUDE.md until the file is huge and re-read on every call.
|
|
15
|
+
|
|
16
|
+
Three habits break under that pressure, and planrails is built against each:
|
|
17
|
+
|
|
18
|
+
| the habit | what planrails does instead |
|
|
19
|
+
|---|---|
|
|
20
|
+
| Progress is remembered, then written down late. | Progress is written **as it happens**, through a CLI, into small append-only files. A hook renders a short brief from those files at every session start and after every compaction. |
|
|
21
|
+
| Every rule goes into the always-loaded file. | A rule is attached to the step it applies to. A hook injects it at that tool call, once. The brief stays under 6,000 characters. |
|
|
22
|
+
| "Done" is a word an agent types. | A task is done only through `task done`, which runs the task's gate, records the run, and needs every condition of done answered in words. The validator refuses a done task that no recorded run backs, whoever wrote the file. |
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- Node 20.10 or newer.
|
|
27
|
+
- Claude Code (hooks measured on 2.1.269).
|
|
28
|
+
- `jq` on the PATH only if you turn on the optional never-delete guard.
|
|
29
|
+
|
|
30
|
+
## Getting started
|
|
31
|
+
|
|
32
|
+
### A new repository
|
|
33
|
+
|
|
34
|
+
You have an empty directory and an idea.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
mkdir my-app && cd my-app
|
|
38
|
+
git init
|
|
39
|
+
npx planrails init
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
What you will see:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
planrails 0.1.0 → /Users/you/my-app
|
|
46
|
+
installing planrails@0.1.0 as a devDependency…
|
|
47
|
+
|
|
48
|
+
+ wrote a minimal package.json
|
|
49
|
+
+ installed planrails@0.1.0 as a devDependency
|
|
50
|
+
+ copied docs/PLANNING_GUIDE.md
|
|
51
|
+
+ created .project-management/plans/
|
|
52
|
+
+ created CLAUDE.md with a Project management section
|
|
53
|
+
+ added the generated plans block to CLAUDE.md
|
|
54
|
+
+ added npm scripts (plan, plan:doctor, plan:brief, plan:validate)
|
|
55
|
+
+ added .planrails/ to .gitignore
|
|
56
|
+
+ wrote the hooks into .claude/settings.json
|
|
57
|
+
|
|
58
|
+
doctor: healthy
|
|
59
|
+
|
|
60
|
+
Next: restart Claude Code (or run /hooks), then type: /plan <your raw plan>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Then:
|
|
64
|
+
|
|
65
|
+
1. Start Claude Code in the directory (or run `/hooks` if it was already open) so the hooks load.
|
|
66
|
+
2. Type `/plan` followed by what you want to build, in your own words. For example: `/plan A CLI that greets people: greet.mjs prints "Hello, <name>!" from the first argument, or "Hello, world!" with none. A test file proves both. A README explains both.`
|
|
67
|
+
3. The planner asks the questions your text did not answer, in one message. Answer them.
|
|
68
|
+
4. It writes the plan, proves every gate can fail, activates the plan, and shows you its brief.
|
|
69
|
+
|
|
70
|
+
Commit everything, including `.claude/settings.json`: the hook commands use `$CLAUDE_PROJECT_DIR`, which Claude Code sets on every machine, so teammates get the hooks after `npm install`.
|
|
71
|
+
|
|
72
|
+
### An existing repository
|
|
73
|
+
|
|
74
|
+
You have a project with its own CLAUDE.md, its own hooks, its own check script. `init` adds and never replaces.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
cd my-existing-project
|
|
78
|
+
npx planrails init --dry-run # shows what would change, writes nothing
|
|
79
|
+
npx planrails init
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
What changes, and what does not:
|
|
83
|
+
|
|
84
|
+
| it | what init does |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `package.json` | adds planrails as a devDependency and four `plan:*` scripts; appends `planrails validate --all --quiet` to an existing `check` script |
|
|
87
|
+
| `CLAUDE.md` | appends a "Project management" section if there is none, and a small generated block that lists the active plans. Your text is untouched. |
|
|
88
|
+
| `.claude/settings.json` | adds the six planrails hooks next to the hooks you already have. Other settings are untouched. A backup goes to `.planrails/backups/` first. |
|
|
89
|
+
| `docs/PLANNING_GUIDE.md` | copied in, unless a file by that name exists |
|
|
90
|
+
| `.project-management/plans/` | created, empty |
|
|
91
|
+
| `.gitignore` | gains `.planrails/` |
|
|
92
|
+
|
|
93
|
+
Run it twice and the second run reports nothing added. `npx planrails uninstall` removes the hooks and the skill and leaves your plans alone.
|
|
94
|
+
|
|
95
|
+
### Planning your first feature, step by step
|
|
96
|
+
|
|
97
|
+
Say the project is a web app and you want a weekly digest email.
|
|
98
|
+
|
|
99
|
+
**1. Ask for a plan.** In a Claude Code session in the project:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
/plan Weekly digest email: every Monday 07:00, send each subscriber the five most-read posts of the week. Reuse the existing email seam. A subscriber can turn it off from their settings page.
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**2. Answer the questions.** The planner asks only what your text left open, in one message. For example: what proves done, which files may change, who approves the first real send.
|
|
106
|
+
|
|
107
|
+
**3. Read what it wrote.** A plan is one folder, `.project-management/plans/weekly-digest/`:
|
|
108
|
+
|
|
109
|
+
| file | what it holds |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `PLAN.md` | why, the method, the rules, the map of files |
|
|
112
|
+
| `state.json` | every task with its status, its gate, its evidence, its answered conditions. Written only by the CLI. |
|
|
113
|
+
| `gates.json` | what proves each task done: a command, what it does not check, a case that must fail |
|
|
114
|
+
| `rules.json` | rules a hook shows the agent when it touches a matching file or runs a matching command |
|
|
115
|
+
| `log.jsonl`, `learnings.jsonl`, `decisions.jsonl`, `gate-runs.jsonl` | append-only history |
|
|
116
|
+
|
|
117
|
+
The gates are the project's own commands: the test runner on the digest module, the lint, a script that enqueues one digest against a throwaway database. Each one declares a known-fail case, and the planner ran `gate verify --all` before activating, so you know each gate can actually fail.
|
|
118
|
+
|
|
119
|
+
**4. Execute, one task at a time.** Open a new session, or continue. The brief is already in the agent's context:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
## Plan weekly-digest — Weekly digest email
|
|
123
|
+
active · 0/4 tasks done · 0 doing · 0 blocked
|
|
124
|
+
RESUME: start T1: the digest query, lib/digest/query.ts
|
|
125
|
+
NOW T1: … — proves done by G1 · effort high
|
|
126
|
+
RULES FOR THIS PLAN: …
|
|
127
|
+
JUDGMENT OUTRANKS THE GATE: a red gate is never made to pass, a green gate is never trusted blind …
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The loop for each task:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npx planrails task start weekly-digest T1
|
|
134
|
+
# … the work …
|
|
135
|
+
npx planrails log weekly-digest --task T1 --what "query returns the top 5 posts by reads over 7 days (lib/digest/query.ts)" --next "T2: render with the email seam"
|
|
136
|
+
npx planrails task check weekly-digest T1 # every condition of done, plus the gate's blind spots
|
|
137
|
+
npx planrails task done weekly-digest T1 --answer "C4: docs/EMAIL_GUIDE.md § Digest updated" --answer "C5: the 5 is the LIMIT in query.ts:12" --answer "C6: nothing new" --answer "C7: read query.ts whole; the gate cannot see a wrong week boundary, I checked Monday 00:00 by hand"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`task done` runs the gate. If the gate fails, the task stays open and the tool says so. If a condition is unanswered, the tool prints the whole checklist and refuses. That refusal is on purpose: it is the reminder, at the moment it matters, of what "done" means here.
|
|
141
|
+
|
|
142
|
+
**5. Or let fresh sessions do it.** `npx planrails run weekly-digest --max-tasks 4` starts one `claude -p` session per task. Each starts empty, gets the brief from the hook, does one task, and exits. The driver reads `state.json` to decide what happens next, never the session's words.
|
|
143
|
+
|
|
144
|
+
**6. Review and close.** `npx planrails review weekly-digest` shows every done task with its gate run and each answered condition. When the plan is done, `npx planrails close weekly-digest --confirmed-by-owner` runs every gate fresh and removes the plan's line from CLAUDE.md. Without the flag it stops and asks you.
|
|
145
|
+
|
|
146
|
+
## The commands
|
|
147
|
+
|
|
148
|
+
| command | what it does |
|
|
149
|
+
|---|---|
|
|
150
|
+
| `init`, `update`, `uninstall`, `doctor` | set up, refresh, remove, check |
|
|
151
|
+
| `new <id> --title "…" --paths "src/**"` | scaffold a plan (the `/plan` skill does this for you) |
|
|
152
|
+
| `gate add\|verify\|run\|list <id>` | gates; `verify` proves each can fail |
|
|
153
|
+
| `task add\|check\|start\|done\|block\|unblock\|drop <id> [T]` | the task lifecycle; `done … --answer "C4: …"` runs the gate |
|
|
154
|
+
| `condition list\|add\|drop <id>` | the plan-level conditions of done |
|
|
155
|
+
| `log`, `learn`, `decide` | record progress, a learning, a choice |
|
|
156
|
+
| `brief [id]`, `status <id>`, `review <id> [T]`, `learnings --search x` | read |
|
|
157
|
+
| `validate [--all]` | fail on any plan whose state lies (put it in CI) |
|
|
158
|
+
| `agent-brief <id> <T>` | what a subagent gets instead of the whole plan |
|
|
159
|
+
| `run <id> [--max-tasks N] [--dry-run]` | one fresh session per task |
|
|
160
|
+
| `activate`, `pause`, `close`, `abandon` | plan lifecycle; the last two need `--confirmed-by-owner` |
|
|
161
|
+
| `hooks install\|status\|uninstall\|selftest` | the hooks |
|
|
162
|
+
| `issue [bug\|wish\|edge]` | open a prefilled GitHub issue |
|
|
163
|
+
| `selftest` | prove the validator's refusals can fire |
|
|
164
|
+
|
|
165
|
+
`npx planrails --help` prints the full list with flags.
|
|
166
|
+
|
|
167
|
+
## How it survives compaction
|
|
168
|
+
|
|
169
|
+
Four layers, cheapest first:
|
|
170
|
+
|
|
171
|
+
| when | what the agent gets | size |
|
|
172
|
+
|---|---|---|
|
|
173
|
+
| always | one generated block in CLAUDE.md naming the active plans | a few lines |
|
|
174
|
+
| session start, and after every compaction | a brief rendered from the plan's files: status, the RESUME line, the last log entries, the rules | ≤ 6,000 chars |
|
|
175
|
+
| at a tool call | the rules that match this file or command, once per session | ≤ 1,200 chars each |
|
|
176
|
+
| on demand | everything else, queried with the CLI | not loaded |
|
|
177
|
+
|
|
178
|
+
Before every compaction a hook writes a journal of what the session did (files written, commands run) to `.planrails/journal/`, and after the compaction another hook hands the path back.
|
|
179
|
+
|
|
180
|
+
## Done needs evidence, and judgment outranks the gate
|
|
181
|
+
|
|
182
|
+
Every task has conditions of done. Three are checked by the tool: the gate passed in this very command, a log entry names the task, the listed files exist. Four are answered in words: the docs shipped, every number has a locator, what was learned is recorded, and you read the result whole and judged it right yourself. A plan can add its own; a task can add its own.
|
|
183
|
+
|
|
184
|
+
A gate is a script. It cannot read, cannot see, and answers only the question it was written for. So the agent sees the same three lines at every closing moment:
|
|
185
|
+
|
|
186
|
+
- gate red, work right: never make the gate pass. Write what it computed and what is true. Fix the gate and re-verify it, or block the task for the owner.
|
|
187
|
+
- gate green, work wrong: refuse to close. Say what the gate cannot see.
|
|
188
|
+
- anything crashed: never edit a plan file by hand. Log it, block, stop.
|
|
189
|
+
|
|
190
|
+
The agent gets no "force pass with a reason" of its own. The override is `task block --needs owner`; the owner's word closes the task. Two rails back this: a gate edited after it was verified cannot close a task until it is verified again, and a gate that was never verified cannot close a task at all.
|
|
191
|
+
|
|
192
|
+
## Subagents and fresh sessions
|
|
193
|
+
|
|
194
|
+
`agent-brief <id> <T>` renders what a subagent needs and nothing else: what to do, which files, what done means, where to write its report, what never to do. A subagent never closes a task; the main session verifies the report and closes it. A SubagentStart hook puts the same note in front of every subagent.
|
|
195
|
+
|
|
196
|
+
`run <id>` is the answer to "clear the context after every task". Claude Code cannot compact or clear on command, so planrails starts a new session per task instead. Each session's only memory of the work is the plan's files.
|
|
197
|
+
|
|
198
|
+
## Updating, disabling, removing
|
|
199
|
+
|
|
200
|
+
- `npm install planrails@latest && npx planrails update` refreshes the guide, the skill and the hook entries. Plans are never touched.
|
|
201
|
+
- To turn one hook off, delete its entry from `.claude/settings.json`. `npx planrails doctor` will say it is missing; that is fine.
|
|
202
|
+
- `npx planrails uninstall` removes the hooks and the skill. `npm uninstall planrails` removes the package. The plans, the guide and the CLAUDE.md section stay, so nothing you wrote is lost.
|
|
203
|
+
|
|
204
|
+
## Reporting a problem, an edge case, or a wish
|
|
205
|
+
|
|
206
|
+
From any project that uses planrails:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
npx planrails issue bug # something broke or refused wrongly
|
|
210
|
+
npx planrails issue edge # a gate, rule or condition was wrong in a case we did not think of
|
|
211
|
+
npx planrails issue wish # something you wish it did
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
It opens a GitHub issue with the environment filled in: versions, platform, what `doctor` and `validate` say. No file contents and no plan text leave your machine. Add `--print` to get the URL instead, or `--gh` to file it with the GitHub CLI.
|
|
215
|
+
|
|
216
|
+
## Development
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
git clone https://github.com/vivmagarwal/planrails && cd planrails && npm install
|
|
220
|
+
npm test # the CLI, init, the hooks, the issue command — all against throwaway directories
|
|
221
|
+
npm run selftest # every refusal in the validator can fire; the hooks on payloads captured from a real run
|
|
222
|
+
npm run acceptance # needs the claude CLI: a real session must quote the injected brief
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
See CONTRIBUTING.md. MIT licensed.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The `planrails` command. Everything is dispatched by src/plan/plan.mjs.
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { resolve, dirname } from "node:path";
|
|
5
|
+
const plan = resolve(dirname(fileURLToPath(import.meta.url)), "..", "src", "plan", "plan.mjs");
|
|
6
|
+
process.argv[1] = plan;
|
|
7
|
+
await import(plan);
|