planr 1.2.0-alpha.1 → 1.3.0-alpha.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/README.md +25 -0
- package/docs/CLI_REFERENCE.md +8 -3
- package/docs/HOOKS.md +34 -0
- package/docs/MODEL_ROUTING.md +6 -0
- package/docs/fixtures/mcp-contract.json +2 -0
- package/npm/native/darwin-arm64/planr +0 -0
- package/npm/native/darwin-x86_64/planr +0 -0
- package/npm/native/linux-arm64/planr +0 -0
- package/npm/native/linux-x86_64/planr +0 -0
- package/package.json +1 -1
- package/plugins/planr/.claude-plugin/plugin.json +1 -1
- package/plugins/planr/.codex-plugin/plugin.json +1 -1
- package/plugins/planr/skills/planr-goal/SKILL.md +1 -1
- package/plugins/planr/skills/planr-loop/SKILL.md +1 -1
- package/plugins/planr/skills/planr-plan/SKILL.md +7 -6
- package/plugins/planr/skills/planr-work/SKILL.md +2 -0
package/README.md
CHANGED
|
@@ -22,6 +22,30 @@ Flat todo lists break down the moment real work has structure. Planr models work
|
|
|
22
22
|
|
|
23
23
|
Three layers make that work: **Plans** (reviewable Markdown packages), the **Map** (live dependency graph with picks, reviews, logs), and **Agent loops** (skills, CLI, and MCP workflows for every major coding agent). Full model: [Task Graph Model](docs/TASK_GRAPH_MODEL.md) and [Operating Model](docs/OPERATING_MODEL.md).
|
|
24
24
|
|
|
25
|
+
## New in 1.2.0: Model Routing
|
|
26
|
+
|
|
27
|
+
Declare once which model handles which work — every task then carries its own routing, and your agents delegate automatically:
|
|
28
|
+
|
|
29
|
+
```toml
|
|
30
|
+
# .planr/agents.toml (write it with `planr agents init`)
|
|
31
|
+
[profiles.frontender]
|
|
32
|
+
client = "cursor"
|
|
33
|
+
model = "opus"
|
|
34
|
+
skill = "frontend-design"
|
|
35
|
+
|
|
36
|
+
[[routes]]
|
|
37
|
+
match = { work_type = "frontend" }
|
|
38
|
+
profile = "frontender"
|
|
39
|
+
fallbacks = ["driver"]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- **Routing travels in the pick packet** — `planr pick --json` hands the worker its profile, model, and paired skill; `planr pick --peek` lets dispatching drivers read it without taking the lease.
|
|
43
|
+
- **Rendered into your hosts' native config** — `planr install codex|claude|cursor` writes the subagent role files with model pins from the registry, in each host's exact vocabulary.
|
|
44
|
+
- **Declared vs. actual, with receipts** — workers report the profile they ran on, runs record the observed host, and `planr trace item` shows deviations as advisory markers.
|
|
45
|
+
- **Use-case pools** — free-form work types (`frontend`, `backend`, ...) declared right in the plan's task list (`### TASK-001 (backend): ...`), plus per-item pins via `planr item route`.
|
|
46
|
+
|
|
47
|
+
Routing is advisory by design: Planr never dispatches models and never blocks a pick — hosts stay the authority. Full guide: [Model Routing](docs/MODEL_ROUTING.md) · replayable walkthrough: [Worked Example: Web App](docs/EXAMPLE_WEBAPP.md) · [Release notes](https://github.com/instructa/planr/releases/tag/v1.2.0).
|
|
48
|
+
|
|
25
49
|
## Install
|
|
26
50
|
|
|
27
51
|
```bash
|
|
@@ -137,6 +161,7 @@ Mid-project work (a new feature, refactor, or fix on an existing project) works
|
|
|
137
161
|
- [Skills](docs/SKILLS.md)
|
|
138
162
|
- [Long-Running Goals](docs/GOALS.md)
|
|
139
163
|
- [Model Routing](docs/MODEL_ROUTING.md) · [Worked Example: Web App](docs/EXAMPLE_WEBAPP.md)
|
|
164
|
+
- [Host Hooks](docs/HOOKS.md)
|
|
140
165
|
- [CLI Reference](docs/CLI_REFERENCE.md)
|
|
141
166
|
- [MCP Guide](docs/MCP_GUIDE.md)
|
|
142
167
|
- [Codex](docs/CODEX.md) · [Claude Code](docs/CLAUDE_CODE.md) · [Cursor](docs/CURSOR.md)
|
package/docs/CLI_REFERENCE.md
CHANGED
|
@@ -27,9 +27,11 @@ planr item insert "title" --description "..." --after <item-id> [--before <item-
|
|
|
27
27
|
planr item amend <item-id> --note "..." [--tag amendment]
|
|
28
28
|
planr item replan <parent-id> --into "A, B, C" [--preview|--confirm]
|
|
29
29
|
planr item update <item-id> [--title "..."] [--description "..."] [--work-type <type>]
|
|
30
|
+
# plan task lists can declare work types inline: `### TASK-001 (backend): ...` / `- [ ] (frontend) ...` seed map build directly
|
|
30
31
|
planr item route <item-id> [--set <profile>|--clear]
|
|
31
32
|
planr link add <from-item> <to-item> --type blocks
|
|
32
|
-
planr
|
|
33
|
+
planr prime [--hook-json]
|
|
34
|
+
planr pick [--work-type <type>] [--plan <plan-id>] [--peek]
|
|
33
35
|
planr pick release <item-id> [--force]
|
|
34
36
|
planr pick heartbeat [item-id]
|
|
35
37
|
planr pick progress <item-id> --percent 0..100 [--note "..."]
|
|
@@ -47,6 +49,7 @@ planr artifact list [--item <item-id>]
|
|
|
47
49
|
planr event list [--item <item-id>] [--limit 50]
|
|
48
50
|
planr debug bundle [--item <item-id>] --preview
|
|
49
51
|
planr log add --item <item-id> --summary "..." [--files a --files b | --files a,b] [--cmd "..."] [--kind completion|progress|verification] [--profile <id>]
|
|
52
|
+
# machine consumers: --cmd writes the `commands` field in log JSON; --tests writes `tests`
|
|
50
53
|
planr review request <item-id>
|
|
51
54
|
planr review annotate <item-id> --message "..." [--severity info|warning|blocking] [--file path] [--line N] [--author "..."]
|
|
52
55
|
planr review ingest <item-id> (--from feedback.json|--stdin)
|
|
@@ -63,7 +66,7 @@ planr agents init --profile|--skill|--route|--default-route|--interactive
|
|
|
63
66
|
planr agents list [--json]
|
|
64
67
|
planr agents check
|
|
65
68
|
planr doctor [--client codex|claude|cursor|all]
|
|
66
|
-
planr install codex|claude|cursor [--dry-run] [--no-mcp] [--force]
|
|
69
|
+
planr install codex|claude|cursor [--dry-run] [--no-mcp] [--force] [--no-hooks]
|
|
67
70
|
planr prompt cli|mcp|http [--client codex|claude|cursor|all]
|
|
68
71
|
planr prompt routing [--client codex|claude|cursor|all]
|
|
69
72
|
planr mcp
|
|
@@ -125,7 +128,9 @@ With `--json`, responses follow one convention so agents never guess where data
|
|
|
125
128
|
|
|
126
129
|
`done` is the compound worker command: it writes a completion log, then requests a review (`--review`) or closes the item, and optionally picks the next ready item (`--next`). Without `--next`, the response's `next` field carries the exact follow-up command instead (`planr pick --work-type review --json` after a review request, `planr pick --json` after a close), so every settlement output ends in an action. It chains the same single-owner operations as `log add`, `review request`, `close`, and `pick` — identical evidence, fewer commands. `done`, `close`, and `review close` report what the settlement `unlocked` (id, title, work type of every item that became ready), `done` and `close` echo the item's `post_condition` at completion time, and a `hint` asks for `--cmd`/`--tests` evidence when downstream items depend on an item that closed without any. `done`, `close`, `review close`, and the pick packet include a `remaining` progress snapshot so loop agents can evaluate their stop condition without an extra `map status` call. `remaining.counts` always carries the full status vocabulary (`pending`, `ready`, `picked`, `running`, `in_review`, `blocked`, `failed`, `cancelled`, `closed`, `closed_partial`) with explicit zeros — a missing count never has to be inferred.
|
|
127
130
|
|
|
128
|
-
`
|
|
131
|
+
`prime` prints the compact state block host hooks inject at session start and after context compaction (project, map counts, held items with log status, goal contract, registry presence, next command); `--hook-json` emits the Claude Code SessionStart envelope. In a repo without a Planr database it exits silently and creates nothing — hooks must never break a session. Installs wire it into the host's hook system by default (`--no-hooks` skips; existing hook files are merged additively, never overwritten). Full guide: [Host Hooks](HOOKS.md).
|
|
132
|
+
|
|
133
|
+
`pick --json` returns one flat work packet in which every fact appears exactly once: `item`, `links`, `logs`, `runtime`, `recovery`, `conditions`, `approval`, recall context (`contexts`, `relevant_contexts`, `upstream_handoffs`, `review_history`, `source_links`, `possible_file_conflicts`), `close_effect`, `privacy`, `deeper_reads`, and `remaining`. Worker identity lives in `item.worker_id` and `runtime.worker_id`. Empty collections and inactive gates are omitted — a missing key means "empty". No separate `trace item` call is needed. Evidence written via `log add` or `done` by the pick owner refreshes the runtime heartbeat automatically. The same packet shape is returned by MCP `planr_pick_item`, HTTP `POST /v1/pick`, and `done --next`. `pick --peek` (MCP: `"peek": true`) returns the same packet for the next pickable item *without* leasing it — no lease, heartbeat, or pick event is written, and the packet carries `"peek": true` with the item still `ready`. Built for dispatching drivers: read the routing block, dispatch the worker, and let the worker take the lease under its own identity — replacing the pick → `pick release --force` → re-pick sequence.
|
|
129
134
|
|
|
130
135
|
`pick --work-type <type>` restricts the lease to one work type, so checker agents pick only `review` items and makers only work items. `pick --plan <plan-id>` restricts the lease to one plan's items, so plan-scoped goal runs never pick work outside their contract even when other plans share the board; an unknown plan id is an error, never a silent unscoped pick. Both filters are available on MCP `planr_pick_item` and HTTP `POST /v1/pick` (`work_type`, `plan`). A null pick is never blind: `{"item": null}` carries a `reason` (`empty_map`, `all_settled`, `nothing_ready`, `ready_items_excluded_by_filter`) and the `remaining` snapshot. When ready work exists but the active filters rejected all of it, `excluded` lists each ready item with the cause (`work_type` mismatch, outside the `--plan` scope, or just requested by this worker) and `repair` carries the exact pick commands that would lease that work — across CLI, MCP, and HTTP. On a review item, `close_effect` previews the full `--close-target` cascade: it lists the work that closing the review (and with it the reviewed item) would unlock.
|
|
131
136
|
|
package/docs/HOOKS.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Host Hooks
|
|
2
|
+
|
|
3
|
+
`planr install codex|claude|cursor` wires Planr into the host's native hook system **by default** — skip it with `--no-hooks`. The hooks solve one problem: loop state must survive the moments context is created or destroyed. Every new session and every context compaction gets the map state injected automatically via `planr prime`, so recovery stops being discipline and becomes mechanism.
|
|
4
|
+
|
|
5
|
+
## What gets installed
|
|
6
|
+
|
|
7
|
+
| Host | File | Events | Runs |
|
|
8
|
+
| --- | --- | --- | --- |
|
|
9
|
+
| Cursor | `.cursor/hooks.json` | `sessionStart` | `planr prime --cursor-json` |
|
|
10
|
+
| | | `subagentStop` | `.cursor/hooks/planr-evidence-guard.sh` |
|
|
11
|
+
| Claude Code | `.claude/settings.json` | `SessionStart` (matcher `startup\|resume\|compact`) | `planr prime --hook-json` |
|
|
12
|
+
| Codex CLI | `.codex/hooks.json` | `SessionStart` | `planr prime` |
|
|
13
|
+
|
|
14
|
+
`planr prime` prints one compact, bounded state block (project, map counts, your held items with log status, the goal contract, registry presence, and the next command) — deterministic and read-only. `--hook-json` wraps it in the Claude Code SessionStart envelope (`hookSpecificOutput.additionalContext`); `--cursor-json` emits Cursor's `additional_context` shape.
|
|
15
|
+
|
|
16
|
+
Only session-start events are wired, deliberately: they are the one place all three hosts inject hook output as context, and Claude's `compact` source plus post-compaction session starts cover the compaction case. Pre/post-compaction events (`preCompact`, `PreCompact`, `PostCompact`) cannot restore model context and are not used.
|
|
17
|
+
|
|
18
|
+
The evidence guard is advisory and identity-scoped: a Cursor subagent that stops while *its own* pick (matched via `PLANR_WORKER_ID`/`PLANR_SESSION_ID`) has no completion log gets one follow-up message naming the item and the two ways out (`planr done ...` or `planr pick release ...`). Without an explicit worker identity it stays silent rather than steering the wrong agent toward foreign items. It never blocks.
|
|
19
|
+
|
|
20
|
+
## Design rules
|
|
21
|
+
|
|
22
|
+
- **Fail open, always.** Every hook command ends in `|| true` with a 10-second timeout; a missing, broken, or uninitialized planr never blocks a session. In a repo without a Planr database, `planr prime` exits silently and creates nothing.
|
|
23
|
+
- **Additive merge, planr owns only its own entries.** Existing hook files keep every foreign entry; planr entries are reconciled on re-install (current ones untouched, outdated ones upgraded in place, entries under retired events removed) — so `planr install <client>` after an update also refreshes the hooks. A file planr cannot parse as a JSON object is left untouched with a note in the install output — add the snippet manually in that case.
|
|
24
|
+
- **Default, not mandatory.** `--no-hooks` skips hook installation entirely; deleting the planr entries from the hook files removes the behavior with no other effect.
|
|
25
|
+
|
|
26
|
+
## Codex trust model
|
|
27
|
+
|
|
28
|
+
Codex requires reviewing hooks before they run: after installing, open `/hooks` in the Codex TUI once and trust the planr entries. Codex records trust against the hook definition's hash, so if a future planr version changes the hook commands, Codex will ask again — that is the host working as intended.
|
|
29
|
+
|
|
30
|
+
## Removing hooks
|
|
31
|
+
|
|
32
|
+
Delete the planr entries from `.cursor/hooks.json` / `.claude/settings.json` / `.codex/hooks.json` (and `.cursor/hooks/planr-evidence-guard.sh`). Nothing else depends on them.
|
|
33
|
+
|
|
34
|
+
See also: [Model Routing](MODEL_ROUTING.md), [Goals](GOALS.md), [CLI Reference](CLI_REFERENCE.md).
|
package/docs/MODEL_ROUTING.md
CHANGED
|
@@ -126,6 +126,10 @@ Every host has a silent override path — the `CLAUDE_CODE_SUBAGENT_MODEL` env v
|
|
|
126
126
|
|
|
127
127
|
Everything here is advisory (ADR-001): mismatches never fail logging, reviews, or closes. No profile reported, no run recorded, or no registry means no comparison and no event.
|
|
128
128
|
|
|
129
|
+
One legitimate mismatch source to know: a driver adding a live-verification log to a routed item runs on the driver profile by design, which emits a `route_mismatch_observed` event. The payload carries `log_kind`, so audit consumers can discount `verification` entries and alarm only on `completion` mismatches.
|
|
130
|
+
|
|
131
|
+
For single-host pools (e.g. all-Cursor), declare the host's *exact* model slugs (`claude-opus-4-8-thinking-high`, not `opus`): dispatch APIs resolve slugs, not aliases, and a driver forced to map `fable-5` onto the nearest slug at dispatch time is a silent translation the audit cannot see.
|
|
132
|
+
|
|
129
133
|
## Failure Behavior
|
|
130
134
|
|
|
131
135
|
- **No registry file**: nothing changes. Pick packets simply have no `routing` key.
|
|
@@ -171,6 +175,8 @@ Create items with the use-case work type (`planr item create ... --work-type fro
|
|
|
171
175
|
|
|
172
176
|
Declare the `client` you will actually dispatch on. A loop running inside one host dispatches that host's subagents — an in-Cursor driver that dispatches Cursor subagents with per-dispatch models is running `client = "cursor"` profiles in practice, even when the model matches. A `client = "codex"` profile is only honest when the driver really spawns a Codex process (`codex exec ...`). This matters for the audit: workers report the *profile id*, so a profile whose declared client differs from the real dispatch host passes the mismatch check on the model alone — the client deviation stays invisible.
|
|
173
177
|
|
|
178
|
+
When do you actually need more than one client? Hosts with a full model catalog (Cursor) can serve an entire pool natively — an all-`cursor` registry with different models per profile is the normal case there. Cross-client profiles exist for two real situations: vendor-locked hosts (Claude Code dispatches only Anthropic models, Codex CLI only OpenAI models — a Claude-Code driver that wants a GPT implementer must process-dispatch via `codex exec`), and subscription economics (the same model can bill differently per host, so routing backend work through a flat-rate CLI subscription instead of the driver host's quota is a legitimate cost decision).
|
|
179
|
+
|
|
174
180
|
## Host Matrix
|
|
175
181
|
|
|
176
182
|
Where each host reads its model configuration from, and what silently defeats a pin there (state of July 2026):
|
|
@@ -88,6 +88,8 @@
|
|
|
88
88
|
"planr prompt routing",
|
|
89
89
|
"planr agents init",
|
|
90
90
|
"planr agents init --profile|--skill|--route|--default-route|--interactive",
|
|
91
|
+
"planr prime",
|
|
92
|
+
"planr install codex|claude|cursor [--dry-run] [--no-mcp] [--force] [--no-hooks]",
|
|
91
93
|
"planr mcp",
|
|
92
94
|
"planr review annotate",
|
|
93
95
|
"planr review ingest",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "planr",
|
|
3
3
|
"description": "Skill-driven planning and execution loop for coding agents: one planr entry point, an autonomous planr-loop, and evidence-backed task graph skills powered by the planr CLI.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.0-alpha.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "instructa"
|
|
7
7
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "planr",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0-alpha.1",
|
|
4
4
|
"description": "Skill-driven planning and execution loop for coding agents: one $planr entry point, an autonomous $planr-loop, and evidence-backed task graph skills powered by the planr CLI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "instructa",
|
|
@@ -42,7 +42,7 @@ Before `map build`, expand the plan's task list: the scaffold ships a single pla
|
|
|
42
42
|
planr link add <earlier-item> <later-item> --type blocks
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
If `planr agents list --json` shows routes with use-case `work_type` selectors (e.g. `frontend`, `backend`),
|
|
45
|
+
If `planr agents list --json` shows routes with use-case `work_type` selectors (e.g. `frontend`, `backend`), declare them in the plan's task list before building — `### TASK-00n (backend): ...` headings and `- [ ] (frontend) ...` items seed with that work type directly. For maps already built without annotations, retag (`planr item update <id> --work-type frontend`). Either way this is prep-agent work, never a question for the user.
|
|
46
46
|
|
|
47
47
|
## Store The Goal Contract
|
|
48
48
|
|
|
@@ -58,7 +58,7 @@ The loop never closes its own reviews when the host supports a second agent. Mak
|
|
|
58
58
|
|
|
59
59
|
## Skills Are The Prompts
|
|
60
60
|
|
|
61
|
-
When the host supports subagents, the driver never implements: it dispatches, audits, and synthesizes. Driver tokens go into `plan audit`, dispatch decisions, and conflict resolution — implementation and review run in the subagent roles, which the host wiring can pin to a cheaper tier (see the role files and `docs/GOALS.md` "Cost Tiering"). When the pick packet carries a `routing` block, dispatch on it: run the worker on the named profile's client and model, and move down the `fallbacks` chain when the primary hits a rate limit or is unavailable — the chain is ordered, so no mid-run registry edits. Workers report the profile they actually ran on (`done --profile <id>` or `PLANR_PROFILE`), so a host that silently overrode the pin shows up in `planr trace item` instead of staying invisible. Delegate with skill references plus an item id, nothing more:
|
|
61
|
+
When the host supports subagents, the driver never implements: it dispatches, audits, and synthesizes. Driver tokens go into `plan audit`, dispatch decisions, and conflict resolution — implementation and review run in the subagent roles, which the host wiring can pin to a cheaper tier (see the role files and `docs/GOALS.md` "Cost Tiering"). When the pick packet carries a `routing` block, dispatch on it: run the worker on the named profile's client and model, and move down the `fallbacks` chain when the primary hits a rate limit or is unavailable — the chain is ordered, so no mid-run registry edits. As the dispatching driver, read the packet with `planr pick --peek [--plan <id>]` — it returns the same packet without leasing, so the worker picks under its own identity and the maker/checker audit stays clean (never pick-and-release as the driver). Workers report the profile they actually ran on (`done --profile <id>` or `PLANR_PROFILE`), so a host that silently overrode the pin shows up in `planr trace item` instead of staying invisible. Delegate with skill references plus an item id, nothing more:
|
|
62
62
|
|
|
63
63
|
- Worker dispatch: `Use $planr-work on item <item-id>. Stop after requesting review.`
|
|
64
64
|
- Checker dispatch: `Use $planr-review on item <item-id>. Close the review with a verdict.`
|
|
@@ -53,15 +53,16 @@ A build plan must include:
|
|
|
53
53
|
|
|
54
54
|
## Route-Aware Tagging
|
|
55
55
|
|
|
56
|
-
Before
|
|
56
|
+
Before writing the task list, check whether the project declares model routing: `planr agents list --json`. If routes exist, their `work_type` selectors are the project's use-case vocabulary (e.g. `frontend`, `backend`, `design`) — and tagging is your job, not the user's; never ask a human to name work types.
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
Declare the use case in the task list itself — `map build` seeds annotated tasks with that work type directly:
|
|
59
|
+
|
|
60
|
+
```markdown
|
|
61
|
+
### TASK-001 (backend): REST API for todos
|
|
62
|
+
- [ ] (frontend) Build the form and list view
|
|
62
63
|
```
|
|
63
64
|
|
|
64
|
-
Match by the
|
|
65
|
+
Match by the task's actual work (UI/components/styling -> a `frontend` route, API/server/storage -> `backend`, and so on); unannotated tasks stay `code`, which the default route covers. For maps that were already built without annotations, retag instead: `planr item update <item-id> --work-type frontend`. The payoff: every pick packet then carries the right profile, model, and paired skill for its use case, so dispatch needs no human routing knowledge.
|
|
65
66
|
|
|
66
67
|
## Done
|
|
67
68
|
|
|
@@ -30,6 +30,8 @@ Live verification (browser flow, executed binary, real requests) gets its own lo
|
|
|
30
30
|
planr log add --item <item-id> --kind verification --summary "verified <flow>: <observed outcome>" --cmd "<exact command>"
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
The `--cmd` value must be copy-paste replayable: a real shell command (or a small script you committed), never a prose transcript like "start server; curl /; check stats". A reviewer replays your command verbatim — if it cannot run, the verification cannot be independently confirmed.
|
|
34
|
+
|
|
33
35
|
Log persistent evidence, not transient noise: a failure you immediately fixed belongs in the final log's narrative, not as a standalone failure log. Only record a failure separately when it blocks the item.
|
|
34
36
|
|
|
35
37
|
Evidence logging refreshes the heartbeat automatically — a separate `planr pick heartbeat` is only needed for long silent stretches without logs.
|