Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/planning/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "planning",
"version": "0.53.0",
"version": "0.54.0",
"userConfig": {
"surface": {
"type": "string",
Expand Down
15 changes: 15 additions & 0 deletions plugins/planning/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@
All notable changes to the `planning` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.54.0] - 2026-09-30

### Added

- **The interview page has Research this and Cancel research buttons.** They post generic `research` and `cancel-research` events that name no skill or plugin; the interviewing session decides how to fulfill them. The `wait` op stamps `waitingSince`, and a held card reads "Research in progress, started <time>". While Claude holds a question, Accept, the alternatives and Own answer stay enabled (Answer anyway, as before); disabling them is not done and is left to the owner ([#5569](https://github.com/melodic-software/claude-code-plugins/issues/5569)).
- **A question waiting for Claude's reply shows a chip, and Accept asks first.** An ask or rephrase with no later reply from Claude marks the question "Waiting for Claude's reply", and Accept then asks "Accept current recommendation anyway?" ([#5569](https://github.com/melodic-software/claude-code-plugins/issues/5569)).
- **A bare `#N` in question text links to the issue in the new `meta.repo` key.** `round.py` warns, without blocking, when text carries one and `meta.repo` is unset ([#5569](https://github.com/melodic-software/claude-code-plugins/issues/5569)).

### Fixed

- **The revising chip reads "Answer not handled yet" and shows on a question only while Claude has not handled that question's own decision.** It no longer names an upstream question; a changed prerequisite stays in the Stale and Waiting-on chips ([#5569](https://github.com/melodic-software/claude-code-plugins/issues/5569)).
- **The summary's Confirm and Something's off buttons sit in one row below the What-is-off box**, clear of it ([#5569](https://github.com/melodic-software/claude-code-plugins/issues/5569)).
- **The decide area caps at 40% of the viewport height on short screens, and the phone layout wraps the status line and no longer scrolls the question rail inside itself** ([#5569](https://github.com/melodic-software/claude-code-plugins/issues/5569)).
- **The wake contract says to re-read the events and restate after a stale-read refusal instead of forcing** ([#5569](https://github.com/melodic-software/claude-code-plugins/issues/5569)).

## [0.53.0] - 2026-09-30

### Added
Expand Down
10 changes: 6 additions & 4 deletions plugins/planning/skills/interview/context/surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ The page is the input surface SKILL.md "Question surface: the page" selects. The
- **Start** with the command in SKILL.md (it carries the configured emoji setting). It prints the page URL; give that URL to the user. A missing prerequisite exits non-zero with its name: take the degrade below.
- **Ids.** Page question ids are `Q<N>` on the session's one continuous counter, so the register rows written at ask-time match what `export-ledger` emits (it renumbers any other id set). When the register already has rows as the page starts (earlier terminal rounds, or a resumed topic whose data dir was discarded), seed the still-empty data dir with `round.sh import-ledger --ledger '<memory_dir>/<topic-slug>/interview-checklist.md'` before the first `add-round`; it keeps each row's `Q<N>` and decision. `export-ledger` writes each row's resolution as escaped named fields in a fixed order (`hold`, `proposal`, `was`, `answer`, `note`, `aside`, `commitments`; the grammar is in `surface/exporters.py`), and import restores them: the hold, a superseded-by-plan row's proposal and displaced answer, the decision on any status (an accept or a defer on a superseded-by-plan row too), the note, a decision a user hold set aside (restored still set aside), and every commitment with its confirmed or unconfirmed mark. An unknown field or a contradictory pair is refused, naming the field. Every older grammar still imports, read as it always was: `waits on::`, `awaiting user::`, `confirmed::`, `plan proposes::` and `answer::` rows, a settled row's `; confirmed::` tail, and the unescaped `waits on:`, `confirmed:` and `; confirmed:` forms. A hand-written resolution imports as the row's own text.
- **A round.** Write the frontier to `'<data_dir>/round-<n>.json'` (`{"meta": {...}, "groups": [...], "questions": [...], "visuals": [...]}`), run `round.sh add-round --file '<data_dir>/round-<n>.json' --round <n>`, and write the register's `open` rows in the same step. Each question carries `recommendation` (one line), `basis` (2-3 sentences, shown behind Why), at least two `alternatives` (`{key, text}`), `commits` (what accepting commits the user to, or `[]`), and `dependsOn` for its prerequisites. Send the round's closing constraint probe with a `note-reply` op (no `seq`) so it lands in Notes to Claude, or as a Claude thread line on the round's first question. `add` and `add-round` refuse a question without `commits` or with fewer than two alternatives.
- **Meta.** `meta` takes `title`, `eyebrow`, `stages` and `next`. `stages` is an object mapping each stage key to its label, for example `"stages": {"frame": "Frame", "decide": "Decide"}`; a list is refused (`$.meta.stages: expected object, got list`). Set with `add-round`'s `meta` object or the `meta` op; any other key is refused. `meta.next` is what Claude does after wrap-up, shown on the finished screen. Keep round numbers out of `meta.eyebrow`: the page derives the round label (`<stage> round N`) from the questions and shows it beside the eyebrow, so a number in the eyebrow goes stale at the next round.
- **Meta.** `meta` takes `title`, `eyebrow`, `stages`, `next` and `repo`. `stages` is an object mapping each stage key to its label, for example `"stages": {"frame": "Frame", "decide": "Decide"}`; a list is refused (`$.meta.stages: expected object, got list`). Set with `add-round`'s `meta` object or the `meta` op; any other key is refused. `meta.next` is what Claude does after wrap-up, shown on the finished screen. `meta.repo` (`owner/repo`) makes the page link a bare `#N` in any markdown field to that repo's issue; `owner/repo#N` always links to its own repo, and `[text](url)` links, `Qn` refs and code spans are left as written. A bare `#N` with `meta.repo` unset stays plain text and `add`, `add-round` and `apply` warn (non-blocking). Keep round numbers out of `meta.eyebrow`: the page derives the round label (`<stage> round N`) from the questions and shows it beside the eyebrow, so a number in the eyebrow goes stale at the next round.
- **Visuals** are declared by format (`svg`, `mermaid`, `image`, `markdown`, `html`, `chart`) with a `scope` (`question:<id>`, `group:<id>`, `round:<stage>:<n>`, `all`) and inline `content` or a data-dir-relative `file`. `label` is its tab name (short; the page falls back to `title`, then `id`). `group`, `order` and `primary` arrange visuals: those sharing a `group` are versions or alternatives of one another, `order` sorts them, and at most one live `primary` is allowed per `group` within a `scope`. Change a visual with `replace-visual` (a full object with the same id) or retire it with `archive-visual`; an archived visual stays in `questions.json` and the page and report never show it. `add-round` refuses an id that already exists. Describe what a visual shows; leave out the tool or skill that made it. An `html` visual runs its scripts on the page, in an opaque-origin sandbox that cannot reach the page, so an interactive prototype or an inlined chart library works; the page CSP still blocks remote content. The exported report runs no scripts: when the decision rests on what a scripted visual shows, attach an `image` of it as well. Two or more `image` visuals on a question also get a Gallery tab (one more per group holding a smaller set) with a thumbnail strip, arrow-key flip and a side-by-side compare, in full screen too. Any visual opens in a new tab from the panel or full screen, still sandboxed.
- **Arm** the watcher as a background Bash task (`run_in_background`): `bash '<surface_dir>/watch.sh' '<data_dir>'`.
- **One watcher.** One session watches an interview at a time: the first watcher holds the server's lease, and `watch.sh` exits 3 naming the holder, since when and its last poll when another session holds it. Do not re-arm. When the holder is another Claude session, coordinate with it through the cross-session messaging tooling this session provides (discover what is available; assume no particular tool) and agree which session runs the interview, or ask it to hand over with `round.sh lease --release`. When it cannot be reached, tell the user which session holds the lease and since when; the lease frees itself once the holder stops polling for `leaseTimeout` seconds (default 600). `round.sh lease` prints the current holder. `watch.sh` also exits 3 when this session's lease was released while it waited: run `round.sh lease`, and re-arm only when this session should still watch. When it exits 2 with "no watcher id" (no session id is exported and the parent pid is 1), export `WATCH_ID` with a name for this session and re-arm.
Expand All @@ -41,7 +41,7 @@ The watcher exits with one JSON line: `{"seq", "timedOut", "events": [...], "not

1. For an `ask`, `own` or `rephrase`, open the turn with a one-line status (which question, what you are doing) before the reply (R10).
2. Answer every `ask`. For decisions on one question, the latest live event wins; mark the earlier ones handled with it (R7).
3. Write `'<data_dir>/ops.json'` fresh with the Write tool on every wake; a stale file re-applies old replies.
3. Write `'<data_dir>/ops.json'` fresh with the Write tool on every wake; a stale file re-applies old replies. When `apply` refuses a `rec` because the question has a newer live user event (the stale-read refusal, R2), re-read the events, restate the reply or revision against what the user just did, and apply again instead of passing `"force": true`.
4. Run exactly one background Bash call that records and re-arms (R8):

<!-- wake-command: surface/watch.test.sh runs the fenced command below -->
Expand All @@ -67,7 +67,7 @@ When the first wake prompts for permission, offer the user one allow rule, `Bash
| `revise` | `id`, `title`, `short`, `facts`, `basis`, `rec`, `why`, `text`, `alternatives`, `commits`, `seq`, `affects`, `force` | Reword a question; `commits` replaces the list and resets its confirmations |
| `note-reply` | `text`, `seq` | Answer a note in Notes to Claude; with no `seq`, post a closing probe there |
| `add`, `add-round`, `group` | `question`; `round`, `meta`, `groups`, `questions`, `visuals`; `id`, `title`, `summary`, `dependsOn` | New questions and groups; writing a `summary` records the group's current question ids as `summaryOf`, and the page marks the summary Stale once the members differ, so rewrite the summary after adding questions |
| `meta` | `set` (`title`, `eyebrow`, `stages`, `next`) | Merge into `meta`; other meta keys stay |
| `meta` | `set` (`title`, `eyebrow`, `stages`, `next`, `repo`) | Merge into `meta`; other meta keys stay |
| `archive` | `ids`, `why` | Take off-path questions out of the open count |
| `replace-visual` | `visual` | Swap in a full visual object for the top-level visual with the same id; an unknown id is refused |
| `archive-visual` | `ids`, `why` | Hide top-level visuals from the page and report; they stay in `questions.json` |
Expand All @@ -93,7 +93,7 @@ The page header shows a Claude line: the `set-status` text with its age while on

A `wait` holds a question in one of two ways. The page labels them as follows:

- **Pending research** (`by: claude`, the default): Claude is working something out. The question shows `Pending research: <waitsOn>`, counts in the header's pending-research count, and is listed under Show: Pending. The user can still answer it (Answer anyway).
- **Pending research** (`by: claude`, the default): Claude is working something out. The question shows `Pending research: <waitsOn>`, counts in the header's pending-research count, and is listed under Show: Pending. The card reads `Research in progress, started <time>` (the time the `wait` landed). The user can still answer it (Answer anyway) and can post `cancel-research` (Cancel research) to release it; a question with no hold offers `research` (Research this) instead, and one waiting on the user offers neither.
- **Needs your answer** (`by: user`): the question needs the user again, even though a decision is recorded. The question shows `Needs your answer: <waitsOn>` and counts as open and in the needs-you navigation. The `wait` also stamps `setAsideAt` and `setAsideSeq`: a page or terminal decision recorded before it no longer counts as an answer anywhere, even after the hold clears; the user's next decision counts. A recommendation revision (`reply --rec`, `revise --rec`) stamps the same fields on a counted `own` answer without a hold: the question returns to open until a new decision counts.

Both count as not answered in the page meter, `round.sh status` and `export-ledger`.
Expand Down Expand Up @@ -127,6 +127,8 @@ When the work returns, clear both (`wait` with `"clear": true`, `set-status` wit
| `reopen` | question | clears it, keeps the note | `handle` |
| `ask` | question | no | `reply` with its `seq` |
| `rephrase` | question | no | `reply` with `"kind": "rephrase"` and its `seq` |
| `research` | question, optional `text` | no | `wait` (`by: claude`) plus `set-status`, then `reply` or `revise` when the lookup returns. Run `/discovery:research` when that skill resolves in this session, else look it up inline. See [Status, activity and holds](#status-activity-and-holds) |
| `cancel-research` | question | no | `wait` with `clear` and `set-status` with `clear`; ignore the pending result, and `handle` both seqs |
| `note` | none | no | `note-reply` with its `seq`, or `reply` on a question |
| `undo` | question, `undoSeq` | withdraws `undoSeq` | Drop that decision from the ledger; `handle` both seqs |
| `wrapup` | none | no | Run [Wrap-up](#wrap-up), then `handle` |
Expand Down
Loading
Loading