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
21 changes: 21 additions & 0 deletions .changeset/a-card-works-its-own-tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
"@openspec-ui/core": minor
"@openspec-ui/webui": minor
"@openspec-ui/server": minor
"@openspec-ui/cli": minor
"openspec-ui-vscode": minor
---

A Pipeline card works its change's tasks in the worktree made for it. A
task's hint now holds all of it, and selecting a task opens it whole beside
the board. For a change in its own worktree, a card closes or reopens a
task with a note written under it (required for a Human-only or delegated
task), commits that `tasks.md` alone and pushes the branch, and runs a
delegated task on its agent. Refused while a run works there. The change's
name opens its task list where it is worked: `tasks.md` from the worktree
in the editor, which looked only in this checkout and so opened nothing for
a change not yet merged; a page of its own in a new browser tab in the
standalone app. An open card can hide its done tasks. From a terminal:
`openspec-ui-cli task done|reopen <change> <number> [--note]` and
`openspec-ui-cli task commit <change>`. Cards read from anywhere else stay
read-only (ADR 0026, amended).
1 change: 1 addition & 0 deletions HARNESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ it — start there and come back here for the detail.
| Run two changes at the same time | [How to](docs/how-to/run-changes-side-by-side.md) | [CI CLI](README.md#ci-cli-merge-gate) |
| Find out what would stop a run here | — | `openspec-ui-cli doctor`, and `doctor --change <id>` for one change |
| Learn why a run failed, or whether one has gone quiet | — | [`supervisor`](#supervisor), and `openspec-ui-cli advise` |
| Close a task, or open a change's tasks, from its card | [How to](docs/how-to/close-a-task-from-its-card.md) | `openspec-ui-cli task` |

The harness sequences CLI-agent runs (or a mechanical action) across the
stages of one OpenSpec change: `propose → review → apply → verify →
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,12 @@ Both hosts also carry, beyond the views above:
with a reason. **By stage** arranges the same cards as a board, a column
per stage from Proposed to Archived, and each card says where its change
is, for how long, and who owns and implements it (ADR 0037). The
arrangement is kept for the next visit.
arrangement is kept for the next visit. A task opens whole beside the
board. For a change in the worktree made for it, its card also closes or
reopens a task with a note, commits and pushes that `tasks.md`, runs a
delegated task, and its name opens the change's task list there: in the
editor as `tasks.md`, in a browser as a page of its own
([how to](docs/how-to/close-a-task-from-its-card.md)).
- **The Timeline** - one change as a line of moments read from git, and a
comparison of every change on a grid of days.
- **What waits on somebody** - every task marked human-only or delegated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,45 @@ is reported, not forbidden, as the rules above already hold.
The proposal that implements this measures that cost on this repository
before it is accepted.

## Amendment, 2026-10-05: a change's own worktree is worked from its card

The owner works each change in its own worktree (ADR 0022, the runbook's
"Where an agent works"), and the card already reads that worktree. So
every action this ADR withholds from a foreign directory was also
withheld from the one place the work is: the card could not open the
change's files, and a person closing a **Human-only** item had to find
`tasks.md` under `.worktrees` by hand. The owner asked on 2026-10-05 for
the card to open the change's task list, show each task whole, and tick
or untick a task, "only for your own worktree".

**A change's own worktree is not foreign to its card.** A worktree is the
change's own when it is a working directory of this repository whose name
is the change's name (the survey's `belongsTo`), so it was made for that
change. From that change's card, and from nowhere else, a person may:

- open the change's files there, in an editor or a browser tab;
- tick or untick one task in its `tasks.md`, with a note written under it;
- commit that `tasks.md` alone and push the worktree's branch;
- start the agent a delegated task in it names.

**The directory is resolved by the host, never named by the request.** A
request carries the change's name and the task's line. The host finds the
change's own worktree with the `git worktree list` it already runs, and
refuses where there is none. A path from a request is never written to,
for the reason this ADR gives for the pair of a directory and a name.

**Every other directory stays observed, never touched.** This checkout is
not offered either: it stays on the default branch, and the work is not
done there. A worktree made for another change, or any directory not
listed by this repository, carries no action, as above.

**Nothing is written while a run works there.** A run that holds the
worktree's lease, or whose live record names the change in that
directory, may be editing the same file; a tick then refuses and says
which run. Git is run in the change's own worktree only for the commit
and push a person asks for, which this amendment allows there and nowhere
else.

## Alternatives considered

**A central registry or daemon agents report to.** Rejected: the
Expand Down
76 changes: 76 additions & 0 deletions docs/how-to/close-a-task-from-its-card.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Close a task from its card

A change worked in the worktree made for it (`.worktrees/<repo>/<change>`,
on the branch named after the change) is worked from its Pipeline card. You
do not need to find its `tasks.md` under `.worktrees`, edit it, commit it and
push it by hand.

Nothing below is offered for a change read from this checkout or from
another change's worktree: those cards stay read-only (ADR 0026, amended
2026-10-05).

## Read a task whole

Hover a task on an open card to see all of it, with what is written under
it. Select the task to open it whole beside the board.

## Close or reopen it

**1.** Select the task on its card.

**2.** Write what was checked, and press **Close the task**. A note is
required for a **Human-only** or **Delegated** task, because the merge gate
refuses one closed with nothing written under it. It is optional otherwise.

The checkbox is ticked and nothing else on its line changes. The note goes
under the task as `Closed by <your git identity> on <date>: <note>`.
**Reopen the task** works the same way, and its note is optional.

The card refuses while a run is working in that worktree, and names the
run: the run may be editing the same file. It also refuses when the line
changed since the card was read. Read it again and try once more.

## Send it

Press **…** on the card, or open any of its tasks, and press **Commit and
push tasks.md**. The commit holds that file alone, whatever else is
changed or staged in the worktree, and the worktree's branch is pushed. It
is never made on the default branch. A refused push keeps the commit and
says what git said.

## Open the change where it is worked

Select the change's name on its card:
- **In the editor**, the worktree's `tasks.md` opens in a tab of its own.
The card's panel also opens the proposal and the design, shows the specs
folder, opens the worktree in a new window, and copies its path.
**Go to line** opens `tasks.md` at the task.
- **In a browser**, a new tab shows the change's tasks, each whole, with the
same controls. **Go to line** scrolls there.

A change with no worktree of its own opens this checkout's copy, read-only.

## Run a delegated task

A task marked **Delegated to `<agent>`** offers **Run on `<agent>`**. The
agent runs in the change's worktree, as `openspec-ui-cli run --cwd` would
run it there.

## Hide what is done

**Hide done** on an open card lists only its open tasks. The choice is kept
like the open cards.

## From a terminal

```bash
openspec-ui-cli task done the-change 6.4 --note "seen in the Pipeline"
```

```bash
openspec-ui-cli task commit the-change
```

`task reopen` unticks. Each exits `1` with the reason when refused.

Every command and its exit codes: [README](../../README.md#ci-cli-merge-gate).
Binary file modified docs/images/standalone/pipeline-board.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/images/standalone/pipeline-stop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/images/standalone/pipeline.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions openspec/changes/a-card-works-its-own-tasks/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
schema: spec-driven
created: 2026-10-05
follows:
- a-card-opens-to-its-tasks
142 changes: 142 additions & 0 deletions openspec/changes/a-card-works-its-own-tasks/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
## Context

See `proposal.md` and ADR 0026's amendment of 2026-10-05. What exists:

- `changeOfWorktree` (`change-worktrees.ts`) pairs a working directory with
a change: not the main one, on a branch that is a valid change name. The
survey sets `belongsTo` only where that change is also active in the main
checkout, so a change that so far exists only in its own worktree (every
change before its pull request merges) is drawn in that directory's own
picture, as foreign, with no action.
- `parseChecklist` (`task-checklist.ts`) reads each item's line, its
`lineNumber`, and `continued` (the wrapped rest of the sentence). The
survey's `SurveyedTask` keeps the first line only, which is what the row's
`title` shows.
- The extension's `openChange` looks the change up in the workspace root
(`findActiveChange`) and opens `proposal.md` there. The standalone app's
name button opens the Change Editor tab for the workspace root.
- `writeTaskCheckStates` writes a checkbox after re-verifying its line's
text; `runDelegatedItem` runs a delegated item's agent on one line.
- The merge gate (`describeTaskDebts`) refuses a closed **Human-only** or
**Delegated** item with nothing written under it.

## Goals / Non-Goals

**Goals:**

- A task is readable whole from its card.
- A person ticks, unticks, commits and pushes a task list from its card,
in the change's own worktree, and the merge gate accepts what is written.
- The change's files open from its card wherever the change is worked.

**Non-Goals:**

- Any action in this checkout or in another change's worktree.
- Editing a task's text from the card. `tasks.md` stays the place for that.
- Committing anything but that change's `tasks.md`.
- Resolving a push that is rejected: it is reported, as `git` says it.

## Decisions

1. **"Own worktree" is resolved by the host from the change's name.**
`resolveOwnWorktree({ git, repositoryRoot, changeName })` lists the
worktrees and returns the one `changeOfWorktree` pairs with that name,
where `openspec/changes/<name>/tasks.md` exists in it, or a refusal
naming why ("no worktree of its own", "its worktree has no tasks.md").
It does not require the change to be active in the main checkout. Every
action takes the change's name and a line, never a path.
- Rejected: trusting the path the card shows. A request is data from a
page, and ADR 0026 names a change by the pair of directory and name.

2. **A task row carries its body and its line.** `SurveyedTask` gains
`lineNumber` and `body`: the indented lines under the item up to the next
item or heading, blank lines inside kept, dedented, line breaks kept,
capped at 4 000 characters with the cap said. `text` stays the checkbox
line. The row's `title` becomes the whole of it. Selecting the row opens
it whole in a panel drawn over the board, as a run's logs are, rendered
as Markdown by the renderer the Change Editor already uses.
- Rejected: opening the row inside the card. A card's height is worked
out in core from what it lists, and the cards of a column stack by
those heights; a row grown in place would run into the card below.
The panel also has room for the note a tick needs.

3. **A card for a change in its own worktree acts, wherever it is drawn.**
A directory the survey pairs with a change by `changeOfWorktree` carries
`ownChange` even where the change is not active in the main checkout; a
card read from such a directory is the change's own, and gets the
actions. A card read from this checkout or from any other directory
gets none, and the name opens this checkout's copy read-only where there
is one.

4. **Tick and untick: `setTaskDone`.**
`setTaskDone({ repositoryRoot, changeName, lineNumber, expectedText,
done, note?, by?, now })`:
- finds the own worktree (decision 1);
- refuses while a run works there: the worktree's lease is held and
fresh, or a live status record names the change in that directory.
The refusal names the run;
- re-reads the line and refuses where its text is not `expectedText`;
- refuses a tick of a **Human-only** or **Delegated** item without a
note. A note is optional otherwise, and for an untick;
- writes `[x]` or `[ ]` and nothing else on that line, then, where there
is a note, one indented line directly under the item's existing body:
`Closed by <by> on <YYYY-MM-DD>: <note>` or `Reopened by ...`. `<by>`
is the worktree's configured git author, a claim, as everywhere here;
- writes through a temporary file and a rename.

5. **Commit and push: `commitTaskList`.**
`commitTaskList({ repositoryRoot, changeName })`, in the own worktree
only:
- stages that `tasks.md` alone (`git add -- <path>`), and says so where
it has nothing to commit;
- refuses on a detached head and on the repository's default branch;
- commits with the message `tasks(<change>): ticked 6.4; reopened 2.1`,
built from the staged diff's checkbox lines, with hooks as configured;
- pushes the branch to its upstream, or to `origin` with `-u` where it
has none. A rejected push is reported with git's words; the commit
stays.
Other modified files in the worktree are left as they are.

6. **Opening.**
- VS Code: `openChangeTasks(changeName, line?)` opens `tasks.md` from the
own worktree, or this checkout's copy, with `preview: false`, at the
line where one is given. The card's menu adds proposal, design, the
specs folder revealed in the Explorer, the worktree in a new window
(`vscode.openFolder`, `forceNewWindow`), and copying its path.
- Standalone: the name opens a new browser tab,
`?view=tasks&change=<name>` with the session's token in the fragment.
The page reads the change's rows through `POST /api/change-tasks`,
shows every task whole, and offers the same controls through the same
panel; "Go to line" scrolls to the row and marks it. Framed in the
editor's Pipeline, the shell posts the request to the editor instead,
as it already does for a change's name.

7. **A delegated task runs from its row.** The row of a **Delegated** item
in an own worktree offers "Run on <agent>": `runOwnDelegatedItem` runs
the inbox's `runDelegatedItem` with the own worktree as its workspace
and runners the host builds for that root (`runnersFor`), as
`openspec-ui-cli run --cwd` does there. The run passes that directory's
allowlist, sandbox and audit, and its request and reply are recorded in
that directory's log.

8. **Hide done, and the task in hand in view.** An open card offers "Hide
done" (remembered per browser, like the open cards) and scrolls its list
to the row marked in hand or probably next when it opens.

9. **Terminal.** `openspec-ui-cli task done <change> <number> [--note
<text>]`, `task reopen <change> <number> [--note <text>]` and `task
commit <change>` call the same core functions, resolving the number to
its line in the own worktree. Exit 0 on success, 1 on a refusal with its
reason, 2 where it could not be attempted.

## Risks / Trade-offs

- **A person can tick an agent's task.** That is the point of a control;
the note says who did it, and the verify stage still rechecks what it
checks.
- **The push goes to the server.** It is one explicit control per push, it
carries one file, and it never runs on the default branch.
- **The survey grows by the task bodies.** Bounded per task, and read from
files the survey already reads.
- **A run can start between the check and the write.** The window is the
write itself; the line check refuses a line that a run changed first.
75 changes: 75 additions & 0 deletions openspec/changes/a-card-works-its-own-tasks/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
## Why

The owner, 2026-10-05: the Pipeline's cards are hardly interactive. Each
change is worked in its own worktree under `.worktrees`, and finding its
`tasks.md` there by hand takes a while. On a card:

- a task's hint shows the first line only, so it is unclear which task it
is;
- nothing ticks or unticks a task, so closing a **Human-only** item means
finding and editing the file, then committing and pushing it by hand;
- the change's name opens nothing in the editor. The extension looks the
change up in this checkout, where a change worked in its own worktree
usually is not, and gives up.

ADR 0026 withheld every action from a directory other than this one,
including the change's own worktree. Its amendment of 2026-10-05 allows a
card to act on its change's own worktree, and only there.

## What Changes

- **A task is shown whole.** Each task row carries its whole text and what
is written under it, and its line in `tasks.md`. Hovering shows all of it;
selecting the row opens it in place, keyboard included.
- **A task is ticked or unticked from its card**, in the change's own
worktree only. A note is written under the task, with who and when; for a
**Human-only** or **Delegated** task the note is required, as the merge
gate requires a record under one. Refused while a run works in that
worktree, and when the line has changed since it was read.
- **"Commit and push tasks.md"** on the card commits that file alone in the
change's own worktree and pushes its branch.
- **A delegated task can be started on its agent** from its row, in the
change's own worktree.
- **The change's name opens its task list**:
- in VS Code, `tasks.md` from the change's own worktree (or this
checkout's copy where there is none) in a new tab, and a menu beside it
for the proposal, the design, the specs folder, the worktree in a new
window, and copying its path;
- in the standalone app, a new browser tab showing the change's tasks,
whole, with the same controls.
- "Go to line" on a row opens `tasks.md` at that task (VS Code) or scrolls
the tasks tab to it.
- An open card can hide its done tasks, and scrolls to the task in hand.
- `openspec-ui-cli task done|reopen <change> <number> [--note]` and
`openspec-ui-cli task commit <change>`, for a terminal and for agents.

## Capabilities

### New Capabilities

(none)

### Modified Capabilities

- `shared-ui`: task rows shown whole; tick, untick, commit, run a delegated
task and go to a line from a card; hide done tasks; the task in hand in
view.
- `vscode-extension`: the change's name opens its task list from its own
worktree; the card's menu.
- `standalone-app`: the change's name opens its tasks in a new browser
tab; the routes the card's actions use.
- `ci-cli`: the `task` command.

## Impact

- `docs/adr/0026-...`: the amendment of 2026-10-05.
- `packages/core`: task rows gain their body and line; a module that finds
a change's own worktree, ticks or unticks one task with a note, and
commits and pushes that `tasks.md`.
- `packages/webui`: the card's task rows, controls and filter; a tasks tab
for the standalone app.
- `packages/extension`: opening a change's files from its own worktree; the
card's messages for tick, commit, run and go to line.
- `packages/server`: routes for the tasks tab and the card's actions.
- `packages/cli`: the `task` command.
- `HARNESS.md` or `README.md` where the Pipeline's card is described.
Loading
Loading