diff --git a/openspec/changes/a-doctor-says-what-would-stop-a-run/.openspec.yaml b/openspec/changes/a-doctor-says-what-would-stop-a-run/.openspec.yaml new file mode 100644 index 0000000..525d888 --- /dev/null +++ b/openspec/changes/a-doctor-says-what-would-stop-a-run/.openspec.yaml @@ -0,0 +1,5 @@ +schema: spec-driven +created: 2026-09-12 +follows: + - a-lease-says-who + - a-change-runs-from-the-terminal diff --git a/openspec/changes/a-doctor-says-what-would-stop-a-run/design.md b/openspec/changes/a-doctor-says-what-would-stop-a-run/design.md new file mode 100644 index 0000000..8fc95e4 --- /dev/null +++ b/openspec/changes/a-doctor-says-what-would-stop-a-run/design.md @@ -0,0 +1,84 @@ +# Design + +## Decisions + +**Which agents are present is asked of the detection this repository +already has.** `detectAvailableAgentsDetailed` +(`packages/core/src/agent-detection.ts`) probes every id in +`AGENT_REGISTRY` through the default allowlist and reports presence and, +for a CLI agent, the version its own `--version` gave. The REST route, +the VS Code bridge and `webui`'s agent picker all read it. +`readEnvironmentReport` calls that, and turns what it returns into +findings. + +Corrected on 2026-09-12, before implementation: this proposal first said +a binary must be resolved and never executed, on the grounds that a +version probe is an invocation nobody allowlisted. That argument reads +well and the decision was wrong — not because the reasoning is bad, but +because the question is already answered in this repository, by code +several surfaces depend on. A second implementation that answered +"is this agent here" differently from the picker in the same build is +exactly the drift this proposal objects to one decision further down, +about the preflight. If the probe is the wrong mechanism, that is a +change to `agent-detection.ts` and to every surface reading it, not a +quiet second opinion inside a new command. + +**For a named change, the answer is the preflight's own.** +`--change ` calls `resolveChainStart` with the same resolver the CLI +builds for a run and prints its `ChainStartRefusal` — reason and +`configKey` — rather than re-deriving those conditions. A second +implementation of "may this change start" would drift from the first, +and the drift would show up as a doctor that says yes to a run that is +then refused. + +**Exit codes follow the CLI's existing contract.** `0` nothing found +would stop a run; `1` something would — the same code `run`, `check` and +`ready` use for "the thing you asked about did not pass"; `2` the report +itself could not be produced. A workspace held by a live run is **not** +a failure: it is a fact, reported, and the workspace is not broken. + +Rejected: exiting non-zero for a held workspace. `lease` decided this +already — the question was answered either way — and two commands +disagreeing about whether being busy is a failure is worse than either +answer. + +**Findings are a closed list, each with a remedy or nothing.** A finding +is `{ id, severity, statement, remedy? }`, `severity` one of `stops-a-run` +or `worth-knowing`. A missing `openspec` CLI stops a run; no git +identity configured is worth knowing (a lease taken without one is +valid, as `a-lease-says-who` established). Nothing is reported without a +severity, and nothing carries a remedy that is not a command this +repository has. + +**No new report of the workspace lease.** Who holds it is read with +`readWorkspaceLeaseHolder` — the reader that never takes it — and shown +the way `lease` shows it. + +## Non-Goals + +- Installing, configuring, or repairing anything. +- Network or authentication checks. +- Agent version detection. +- A UI surface. Both hosts refuse a run with a reason already; whether + they should carry this report is a separate question and a separate + change. +- Reading or printing any environment variable's value. + +## Risks / Trade-offs + +**A report that says everything is fine, and a run then fails.** The +mitigation is the decision above: for a named change the report *is* the +preflight, so the two cannot disagree about that change. Without +`--change`, the report is explicitly about the machine and the +workspace, and says so — it does not claim any particular change will +run. + +**A check nobody updates.** Every agent checked comes from +`AGENT_REGISTRY`, so an agent added later is covered without a second +list; the test asserts the report covers exactly the registry's +executables, which fails when a new agent is added and the report is +not. + +**Protocol impact: none.** No command or event is added or changed; this +is a CLI command over existing core reads. `server` and `extension` are +untouched. diff --git a/openspec/changes/a-doctor-says-what-would-stop-a-run/proposal.md b/openspec/changes/a-doctor-says-what-would-stop-a-run/proposal.md new file mode 100644 index 0000000..b3879fd --- /dev/null +++ b/openspec/changes/a-doctor-says-what-would-stop-a-run/proposal.md @@ -0,0 +1,61 @@ +# A doctor says what would stop a run + +## Why + +`resolveChainStart` in `packages/core/src/chain-preflight.ts` already +resolves every precondition of a run before anything is spent — the +change exists, its configuration reads, its autonomy level starts a +chain, a confirmation can be answered, no blocker is unmet, every stage +resolves an agent this build carries. Its header says why: "an +unattended run cannot" discover a missing agent at the stage that needs +it, because the two stages already paid for were paid for nothing. + +Every one of those answers is available only by naming a change and +asking to run it. A person setting this tool up for the first time, or +on a new machine, finds out what is missing by being refused — which is +precisely the shape `a-lease-says-who` rejected for the workspace lease: +"a strange way to ask a question, and one that only answers it at the +moment you are being told no." + +Raised in review on 2026-09-12 as part of making the tool easier to +start using. What is missing is the same command the lease got: ask the +question directly. + +## Capabilities + +### New + +- `openspec-ui-cli doctor`: what this machine and this workspace have, + and what would stop a run — the runtime against the pinned engines, + the `openspec` CLI, which agent binaries are on the PATH, whether the + workspace's harness configuration reads, who holds the workspace, and + whether a git identity is configured. +- `--change `: the same report plus the real preflight for that + change, so the answer is the one the run would give, not a second + opinion. +- `--format json`, the same shape the reader returns. + +### Modified + +- Nothing. This reports on what exists; no setting, refusal or default + changes. + +## Out of scope + +Fixing anything. The doctor installs nothing, writes nothing and +configures nothing. Where a finding has a remedy the repository already +has a command for, it names that command. + +Deciding for itself which agents are installed. That is +`detectAvailableAgentsDetailed`'s question, already answered for the +REST route, the VS Code bridge and the agent picker; this report asks it +rather than probing a second way — see `design.md`. + +Network checks. Whether an agent's service is reachable, whether a +token is valid, whether GitHub is up: each is a question about a remote +system at one moment, and a report that says "reachable" is stale before +it is read. The doctor reports what is configured locally. + +Anything about secrets. No environment variable's value, no token, no +credential is read or printed — a finding may name a variable, never +what it contains. diff --git a/openspec/changes/a-doctor-says-what-would-stop-a-run/specs/ci-cli/spec.md b/openspec/changes/a-doctor-says-what-would-stop-a-run/specs/ci-cli/spec.md new file mode 100644 index 0000000..0ca9f72 --- /dev/null +++ b/openspec/changes/a-doctor-says-what-would-stop-a-run/specs/ci-cli/spec.md @@ -0,0 +1,66 @@ +## ADDED Requirements + +### Requirement: What would stop a run can be asked before starting one + +It SHALL be possible to ask what this machine and this workspace are +missing, without naming a change and attempting to run it. + +The answer SHALL cover the runtime against the versions this repository +pins, the presence of the OpenSpec CLI, the presence of each registered +agent's executable, whether the workspace's harness configuration reads, +who holds the workspace, and whether a git identity is configured. + +Each finding SHALL state whether it stops a run or is only worth +knowing, and SHALL name a remedy where the repository has a command for +one. + +A workspace held by a live run SHALL be reported as a fact and SHALL NOT +be reported as something that stops a run: a busy workspace is not a +broken one. + +#### Scenario: A machine missing something a run needs + +- **WHEN** something a run requires is absent +- **THEN** it is named, marked as stopping a run, and the command that + remedies it is named where one exists + +#### Scenario: A workspace held by a live run + +- **WHEN** a run holds the workspace +- **THEN** the holder is reported, and the report does not treat it as a + failure + +### Requirement: An agent's presence is answered by the detection that already exists + +Whether an agent is installed SHALL be answered by the same detection +every other surface of this product reads, and SHALL NOT be determined a +second way inside this report. + +A command that answered "is this agent here" differently from the agent +picker in the same build would make the product disagree with itself, +and a person would have no way to tell which answer to believe. If the +way presence is detected is wrong, it is wrong for every surface and is +changed in one place. + +#### Scenario: Reporting on an installed agent + +- **WHEN** the report covers an agent whose executable is installed +- **THEN** it reports what the shared detection reports, including the + version where that detection has one + +### Requirement: A report about one change is the preflight's own answer + +Where the question is asked about a named change, the answer SHALL come +from the same resolution that would run it, including the setting that +governs any refusal — not from a second implementation of the same +conditions. + +Two implementations of "may this change start" drift, and the drift +appears as a report that says yes to a run which is then refused. + +#### Scenario: A change whose configuration refuses an unattended run + +- **WHEN** the report is asked about a change whose configuration would + refuse to start here +- **THEN** it states that refusal's own reason and the setting that + governs it diff --git a/openspec/changes/a-doctor-says-what-would-stop-a-run/tasks.md b/openspec/changes/a-doctor-says-what-would-stop-a-run/tasks.md new file mode 100644 index 0000000..3764ddd --- /dev/null +++ b/openspec/changes/a-doctor-says-what-would-stop-a-run/tasks.md @@ -0,0 +1,83 @@ +Everything that stops a run is already decided in one place, and can +only be asked by starting a run and being refused. + +## 1. The report + +- [ ] 1.1 `packages/core/src/environment-report.ts` exports + `Finding` (`{ id, severity, statement, remedy? }`, `severity` one of + `"stops-a-run" | "worth-knowing"`) and + `readEnvironmentReport({ workspaceRoot }): Promise`. +- [ ] 1.2 The runtime: the running Node.js and npm versions against the + root `package.json`'s `engines`. Outside the range is `stops-a-run`, + with the pinned range quoted in the statement. +- [ ] 1.3 The `openspec` CLI: resolved on the PATH or not. Absent is + `stops-a-run` — `validate-change` and the `archive` stage both call + it. +- [ ] 1.4 Each agent in `AGENT_REGISTRY`: present or absent, from + `detectAvailableAgentsDetailed` in + `packages/core/src/agent-detection.ts`, including the version it + reports where it has one. Do not probe a second way: the picker, the + REST route and the VS Code bridge all read that function, and a + command answering "is this agent here" differently from the picker in + the same build is the drift this change objects to elsewhere. +- [ ] 1.5 The workspace's harness configuration: `resolveHarnessConfig` + on the global file reads, or the error it raised, as `stops-a-run`. +- [ ] 1.6 The workspace lease, via `readWorkspaceLeaseHolder`: who holds + it, or that it is free. Held is `worth-knowing`, never `stops-a-run` + — a busy workspace is not a broken one. +- [ ] 1.7 The git identity, via `readGitAuthor`: present or not. Absent + is `worth-knowing`, with the remedy naming `git config user.email`. A + lease taken without one is valid. +- [ ] 1.8 No environment variable's value is read or reported. A finding + may name a variable; it may not print what it contains. +- [ ] 1.9 `packages/core/src/environment-report.test.ts`: a report over + a fixture with everything present has no `stops-a-run` finding; a + missing `openspec` produces one; an out-of-range Node version produces + one quoting the range; a held lease produces a `worth-knowing` + finding; every executable in `AGENT_REGISTRY` appears in the report — + the assertion that fails when an agent is added and this is not + updated. + +## 2. The command + +- [ ] 2.1 `openspec-ui-cli doctor [--cwd ] [--change ] + [--format text|json]` in `packages/cli/src/doctor-command.ts`, wired + in `packages/cli/src/main.ts` with its own `USAGE` entry and its exit + codes documented there. +- [ ] 2.2 `--change ` additionally calls `resolveChainStart` with the + same resolver `runChange` builds, and prints its refusal's `reason` + and `configKey`. Do not re-derive any precondition it answers. +- [ ] 2.3 Exit `0` where no finding stops a run, `1` where one does, `2` + where the report could not be produced. A held workspace alone exits + `0`. +- [ ] 2.4 `--format json` prints the `EnvironmentReport` shape unchanged + from core. +- [ ] 2.5 `packages/cli/src/doctor-command.test.ts`: a clean report exits + 0; a `stops-a-run` finding exits 1 and names the remedy; a held + workspace alone exits 0; `--change` on a change whose configuration + refuses prints the preflight's own reason and `configKey` and exits 1; + an unreadable workspace exits 2. + +## 3. Documentation + +- [ ] 3.1 `README.md`'s "CI CLI (merge gate)" section documents `doctor` + and its three exit codes. +- [ ] 3.2 `HARNESS.md`'s task index gains a row: "Find out what would + stop a run here" → `openspec-ui-cli doctor`. + +## 4. Verification + +- [ ] 4.1 This change validates strictly. `check(validate-change)` +- [ ] 4.2 `npm run verify` unpiped, after the last edit, with everything + staged. Record the run and the per-package test counts. +- [ ] 4.3 A changeset exists: `core` and `cli` minor. + `check(changeset-present)` +- [ ] 4.4 **Delegated to `claude-cli`**: run `openspec-ui-cli doctor` on + this repository and quote the output and exit code; then run it with + `PATH` stripped of `openspec` and quote the output and exit code. + Evidence: both outputs verbatim, showing the same command reporting a + healthy machine and a broken one. +- [ ] 4.5 **Delegated to `claude-cli`**: while a run holds the + workspace, run `openspec-ui-cli doctor` and quote the lease finding + and the exit code, which must be `0`. Evidence: the output and the + exit code. diff --git a/openspec/changes/a-hint-says-what-can-run-together/.openspec.yaml b/openspec/changes/a-hint-says-what-can-run-together/.openspec.yaml new file mode 100644 index 0000000..e693d94 --- /dev/null +++ b/openspec/changes/a-hint-says-what-can-run-together/.openspec.yaml @@ -0,0 +1,6 @@ +schema: spec-driven +created: 2026-09-12 +follows: + - what-can-start-now + - changes-run-side-by-side + - a-graph-of-what-is-running diff --git a/openspec/changes/a-hint-says-what-can-run-together/design.md b/openspec/changes/a-hint-says-what-can-run-together/design.md new file mode 100644 index 0000000..23d0224 --- /dev/null +++ b/openspec/changes/a-hint-says-what-can-run-together/design.md @@ -0,0 +1,89 @@ +# Design + +## Decisions + +**A hint is a pure function of facts already gathered, in core.** +`buildHints(report: ChangeReadinessReport, options): Hint[]` in +`packages/core/src/hints.ts` takes the readiness report and returns +hints. It reads no file, spawns nothing, and has no clock of its own +beyond a timestamp passed in. Both hosts and the CLI call it on the +report they already have. + +Rejected: a background service that analyses the workspace on a timer +and pushes suggestions. It was the review's own suggestion and it buys +nothing here: the readiness report is already rebuilt on the events that +change it, so a timer would recompute the same answer between changes, +hold a lease-shaped question open about which host owns the analysis, +and make "hints are off" mean "still computed, not shown". A hint that +appears because a file changed is the same hint, arriving for a reason a +reader can explain. + +Rejected: computing hints in `webui`. ADR 0001: behaviour lives in core. +A hint computed in the shared UI could not be printed by the CLI, and +`packages/cli` is where a person automating this would want it. + +**Every hint carries the exact commands.** A hint is +`{ id, kind, subject, because, commands: string[] }`. `because` states +the fact it was derived from ("both are ready, and their deltas touch no +capability in common"), and `commands` are lines a person can paste. A +suggestion without a command makes the reader translate advice into +action, which is the work the hint was supposed to save. + +**All maximal sets, or none.** Where more than two changes can run +together, the hint names every maximal compatible set rather than one. +Three changes with one collision between two of them produce two sets, +and naming both preserves the decision `readChangeReadiness` documents: +the reader chooses, and can see that there was a choice. Where the sets +would exceed a stated cap, the hint says how many there are and names +none — a truncated list looks like a recommendation. + +Rejected: ranking sets by size and showing the best. "Optimal" here is +not a property of the repository; it depends on which change the person +wants finished first, which the repository does not know. + +**Off means not computed.** The setting is read where the report is +built, and with hints off `buildHints` is not called. A hint that is +computed and hidden costs the same and is a different promise than the +one the switch makes. + +**No new transport.** Hints ride on the readiness payload the standalone +server and the extension bridge already return; no command and no event +is added to the protocol. `a-graph-of-what-is-running` added the +readiness facts in core and the client both hosts use +(`packages/core/src/change-readiness-facts.ts`, +`packages/webui/src/change-readiness-client.ts`); this change adds a +component beside them, never a second client. + +## Non-Goals + +- Creating, editing, or starting anything. +- Generating an OpenSpec change. +- Task-level parallelism inside one change. Tasks declare no file paths + today — `collisionsBetween` works from a change's spec delta and its + branch's changed files, neither of which exists per task — so a hint + about two tasks would be inference from prose, which is exactly the + guess this design refuses. Making tasks declare paths is a separate + change, and this one is written so that hints over it would be another + `Hint` kind rather than a new mechanism. +- Any timer, watcher, or background process. +- Scoring, ranking, or recommending one plan. + +## Risks / Trade-offs + +**Advice that is wrong once is never trusted again.** Every hint states +the fact it came from, so a reader can check it in one step; and every +hint kind is derived from a collision type `readChangeReadiness` already +computes, rather than from a new heuristic. The failure mode to avoid is +a hint that "looks plausible": a wrong hint whose reason is printed is +reported, a wrong hint without one becomes folklore. + +**Hints become noise.** Mitigated by the cap on maximal sets, by hints +being derived only where an action exists, and by the switch. What is +not mitigated: a repository with twenty ready changes will produce a lot +of hints. The verification includes a fixture of that size, and the +outcome — whether the list is usable — is a human-only item. + +**Protocol impact: none.** No command or event is added or changed. The +readiness payload gains an optional `hints` array, which existing +adapters ignore; `server` and `extension` both pass the payload through, +and a host built before this change renders exactly what it does today. diff --git a/openspec/changes/a-hint-says-what-can-run-together/proposal.md b/openspec/changes/a-hint-says-what-can-run-together/proposal.md new file mode 100644 index 0000000..3af4e91 --- /dev/null +++ b/openspec/changes/a-hint-says-what-can-run-together/proposal.md @@ -0,0 +1,66 @@ +# A hint says what can run together + +## Why + +Everything needed to tell somebody "these two changes can be started at +once, and here is the command" is already computed. +`readChangeReadiness` in `packages/core/src/change-readiness.ts` returns, +per active change, its run state, the capabilities its delta touches, +`canJoin`, `blockedFrom` with the exact collisions, and `needsWorktree` +with the remedy. `openspec-ui-cli ready` prints it. Nobody is told +anything: a person has to read a report and work out what to do with it. + +Raised in review on 2026-09-12: the tool should point out the +non-obvious — that these changes could run in parallel, that this work +could be split — and offer to set it up, with the whole thing +switchable off. + +The gap is not a computation. It is that a fact is printed where an +offer would help, and that the fact is only printed when somebody thinks +to ask for it. + +ADR 0001 decides where such a thing may live: all behaviour in +`packages/core`, with `server` and `extension` as thin adapters. A hint +computed in a UI would exist in one host and not the other, and could +not be tested without that host. + +## Capabilities + +### New + +- A hint: a named, explained suggestion derived from facts the + repository already computes — which ready changes can be started + alongside each other, which ready change has nowhere to run and the + one command that gives it one, which change is held by a run that + ended. +- The commands a hint suggests, quoted exactly, so acting on one is + copying a line rather than translating a suggestion. +- `openspec-ui-cli advise`: the same hints in a terminal, text or JSON. +- A setting that turns hints off, under which nothing is computed at + all. + +### Modified + +- The standalone shell and the extension show hints where they already + show readiness, using the shared component rather than one each. + +## Out of scope + +Writing anything. A hint never creates a worktree, edits a change, or +starts a run. It names the command; a person runs it. + +Writing a change. "Let me write a small change that runs all of this +optimally" was the review's own phrasing, and generating an OpenSpec +change from a guess is the one thing this must not do: a change is a +document somebody is accountable for. What this offers is the plan and +the commands. If a generator is wanted later, it is a change of its own, +gated on an explicit confirmation, and it starts from hints that have +been in use long enough to be trusted. + +Choosing one grouping. `readChangeReadiness` is deliberately pairwise — +"three changes where A and B collide and C collides with neither have no +single correct grouping, and presenting one would choose for the reader +and hide that a choice existed". A hint that announced the optimal plan +would undo that decision quietly. + +Anything running in the background on a timer. See `design.md`. diff --git a/openspec/changes/a-hint-says-what-can-run-together/specs/ci-cli/spec.md b/openspec/changes/a-hint-says-what-can-run-together/specs/ci-cli/spec.md new file mode 100644 index 0000000..a49dcab --- /dev/null +++ b/openspec/changes/a-hint-says-what-can-run-together/specs/ci-cli/spec.md @@ -0,0 +1,65 @@ +## ADDED Requirements + +### Requirement: What the repository already knows is offered as a suggestion + +Facts the repository computes about which changes can be started +alongside each other SHALL be available as suggestions that name the +action they imply, not only as a report a reader must interpret. + +A suggestion SHALL carry the fact it was derived from and the exact +commands that act on it. A suggestion without its reason cannot be +checked and becomes folklore the first time it is wrong; a suggestion +without its commands leaves the reader to translate advice into action, +which is the work it was meant to save. + +A suggestion SHALL NOT create, edit, or start anything. + +Where several sets of changes could run together, every maximal set +SHALL be named, or — beyond a stated limit — none, with their number +reported. One chosen set presented as the plan would decide for the +reader and hide that a choice existed. + +#### Scenario: Two changes that can run side by side + +- **WHEN** two ready changes collide over nothing +- **THEN** a suggestion names both, states that their deltas touch no + capability in common and their branches no file in common, and quotes + the commands that give each a working directory and start it + +#### Scenario: More sets than can usefully be listed + +- **WHEN** the number of maximal sets exceeds the stated limit +- **THEN** the suggestion reports how many there are and names none + +#### Scenario: Suggestions turned off + +- **WHEN** suggestions are turned off +- **THEN** none are computed, and the payload carries no suggestion + field at all + +### Requirement: Suggestions can be asked for from a terminal + +The suggestions SHALL be available from the command line, in a form a +person reads and a form a machine parses. + +The command SHALL report success whether or not there are any +suggestions: the question was answered either way, and a script asking +"is there anything to do" should read the output rather than infer it +from a failure code — the same contract `ready` and `lease` already +have. + +#### Scenario: A workspace with suggestions + +- **WHEN** suggestions exist and the command is run +- **THEN** they are printed and the command reports success + +#### Scenario: A workspace with none + +- **WHEN** no suggestion applies +- **THEN** the command says so in words and reports success + +#### Scenario: A workspace whose report cannot be built + +- **WHEN** the readiness report cannot be built +- **THEN** the command reports that it could not complete, distinctly + from having nothing to suggest diff --git a/openspec/changes/a-hint-says-what-can-run-together/specs/shared-ui/spec.md b/openspec/changes/a-hint-says-what-can-run-together/specs/shared-ui/spec.md new file mode 100644 index 0000000..8eb38fd --- /dev/null +++ b/openspec/changes/a-hint-says-what-can-run-together/specs/shared-ui/spec.md @@ -0,0 +1,31 @@ +## ADDED Requirements + +### Requirement: A suggestion is shown by one component in both hosts + +Where a host shows what the repository suggests, it SHALL render it with +the shared component, from the payload the shared client already +fetches, and SHALL NOT compute a suggestion of its own. + +A suggestion computed in a host exists in that host only, cannot be +printed by the command line, and cannot be tested without starting that +host — the same reasoning ADR 0001 gives for keeping behaviour in the +core package. + +A suggestion SHALL be shown with its reason and its commands as text a +person can select and copy. It SHALL NOT be shown with a control that +runs those commands: a suggestion that acts is no longer a suggestion, +and nothing in this capability writes to a repository. + +Where there is nothing to suggest, nothing SHALL be shown — not an empty +region with a heading. + +#### Scenario: A host renders suggestions + +- **WHEN** the payload carries suggestions +- **THEN** each is shown with its subject, its reason and its commands + as selectable text, by the shared component in either host + +#### Scenario: Nothing to suggest + +- **WHEN** the payload carries no suggestion +- **THEN** the host shows nothing in their place diff --git a/openspec/changes/a-hint-says-what-can-run-together/tasks.md b/openspec/changes/a-hint-says-what-can-run-together/tasks.md new file mode 100644 index 0000000..17deeb4 --- /dev/null +++ b/openspec/changes/a-hint-says-what-can-run-together/tasks.md @@ -0,0 +1,94 @@ +`readChangeReadiness` already knows which ready changes can be started +alongside each other, which has nowhere to run, and what any two would +collide over. Nobody is told; the facts are printed and the reader does +the rest. + +## 1. The hint itself + +- [ ] 1.1 `packages/core/src/hints.ts` exports `Hint` + (`{ id, kind, subject, because, commands }`) and + `buildHints(report: ChangeReadinessReport, options: HintOptions): Hint[]`. + It reads no file and spawns no process: everything it needs is in the + report it is given. +- [ ] 1.2 Kind `can-run-together`: every maximal set of ready changes + that collide with none of each other, each with the `worktree add` and + `run` lines for its members. Not one chosen set — see `design.md`. +- [ ] 1.3 Kind `needs-a-worktree`: a ready change with no working + directory of its own, carrying the exact `openspec-ui-cli worktree + add ` line already present in `ChangeReadiness.needsWorktree`. +- [ ] 1.4 Kind `held-by-a-finished-run`: a change whose worktree is held + by a lease whose holder is gone, carrying `openspec-ui-cli lease + release --cwd `. Derive "gone" from the readiness report's + own holder record; do not check a process here — core decides that in + `releaseWorkspaceLease` and a second opinion would drift from it. +- [ ] 1.5 `because` on every hint names the fact it came from, in the + vocabulary `describeCollision` already uses. A hint whose reason is + absent is not emitted. +- [ ] 1.6 More maximal sets than `HintOptions.maxSets` (default 5) emits + one hint saying how many there are and naming none. A truncated list + reads as a recommendation, which is the thing this must not produce. +- [ ] 1.7 `packages/core/src/hints.test.ts`: two compatible changes + produce one set; three with one collision produce two sets; a change + with no worktree produces `needs-a-worktree`; a stale holder produces + `held-by-a-finished-run`; six sets produce the count-only hint; a + report with nothing ready produces no hints and is not an error. + +## 2. The switch + +- [ ] 2.1 `hints.enabled` in `openspec/agent-harness.json`, default + `true`, validated by `packages/core/src/harness-config.ts` like every + other key — an unknown value is rejected, not coerced. +- [ ] 2.2 With `hints.enabled: false`, `buildHints` is not called at the + call site, and the payload carries no `hints` key. Assert the function + is not invoked, not merely that the array is empty: "computed and + hidden" is a different promise than the switch makes. +- [ ] 2.3 `HARNESS.md`'s key reference documents `hints.enabled`, its + default, and that off means not computed. + +## 3. Where it is seen + +- [ ] 3.1 `packages/server/src/rest.ts` includes `hints` on the readiness + payload, and omits the key entirely when hints are off. +- [ ] 3.2 The extension's bridge returns the same payload through the + same core call. No second computation, and no hint computed in + `packages/extension`. +- [ ] 3.3 `packages/webui/src/components/HintList.tsx` renders a hint as + its subject, its `because`, and its commands as selectable text. It + consumes `packages/webui/src/change-readiness-client.ts`, which both + hosts already use; do not add a second client. +- [ ] 3.4 A hint's commands are never a button that runs them. This + change writes nothing and starts nothing; a control that ran a command + would make it the kind of thing that does. +- [ ] 3.5 `packages/webui/src/components/HintList.test.tsx`: a hint + renders its reason and its commands; no hints renders nothing at all, + not an empty panel with a heading. + +## 4. In a terminal + +- [ ] 4.1 `openspec-ui-cli advise [--cwd ] [--format text|json]` + in `packages/cli/src/advise-command.ts`, wired in + `packages/cli/src/main.ts` with its own `USAGE` entry. +- [ ] 4.2 Exit `0` whether or not there are hints — the question was + answered either way, the same reasoning `ready` and `lease` use — and + `2` where the report could not be built. +- [ ] 4.3 `--format json` prints the `Hint[]` shape unchanged from core. +- [ ] 4.4 `packages/cli/src/advise-command.test.ts`: hints exit 0, no + hints exits 0 and says so in words, an unreadable workspace exits 2. + +## 5. Verification + +- [ ] 5.1 This change validates strictly. `check(validate-change)` +- [ ] 5.2 `npm run verify` unpiped, after the last edit, with everything + staged. Record the run and the per-package test counts. +- [ ] 5.3 A changeset exists: `core` and `cli` minor, `server`, + `webui` and the extension as their changes warrant. + `check(changeset-present)` +- [ ] 5.4 **Delegated to `claude-cli`**: in a repository with at least + three active changes, run `openspec-ui-cli advise` and quote its + output and exit code; then set `hints.enabled: false`, run it again, + and quote the output and exit code. Evidence: both outputs verbatim. +- [ ] 5.5 **Human-only**: with the standalone shell open on a repository + that has a dozen ready changes, the hint list is something a person + reads rather than scrolls past. Whether advice is useful at that + volume is a judgement, and it is the one thing that decides whether + this capability was worth adding. diff --git a/openspec/changes/every-screenshot-is-taken-by-a-spec/.openspec.yaml b/openspec/changes/every-screenshot-is-taken-by-a-spec/.openspec.yaml new file mode 100644 index 0000000..2b596d1 --- /dev/null +++ b/openspec/changes/every-screenshot-is-taken-by-a-spec/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-09-12 diff --git a/openspec/changes/every-screenshot-is-taken-by-a-spec/design.md b/openspec/changes/every-screenshot-is-taken-by-a-spec/design.md new file mode 100644 index 0000000..e99aedd --- /dev/null +++ b/openspec/changes/every-screenshot-is-taken-by-a-spec/design.md @@ -0,0 +1,79 @@ +# Design + +## Decisions + +**The check is a list of pictures, not of specs.** `scripts/check-screenshots.mjs` +walks `docs/images/**/*.png`, and for each file requires either a spec +that writes that exact path (found by reading the `e2e` sources) or an +entry in a baseline file naming the picture and why no spec can take it. +A new picture added by hand fails until one of the two is true. + +Rejected: checking image modification dates against package versions. It +would fail every picture whenever anything released, including the ones +that are still correct, and a check that cries wolf gets a baseline +entry for everything. + +Rejected: comparing rendered screens pixel by pixel against the +committed picture. That is a visual regression suite — it fails on font +rendering between machines, and its job is to catch design changes, +which is not the defect here. + +**A webview screen is captured from the standalone shell, not from the +editor.** `packages/webui` renders the same components in both hosts. +The picture of the harness settings panel is a picture of the web UI, +and it is reachable from Playwright; the fact that a reader sees it +inside VS Code does not make it an editor surface. The extension's +documentation says where the panel is opened from and shows the +captured picture. + +Rejected: driving VS Code's own window. The integration tests run inside +a real editor but have no screenshot API, and adding a screen-capture +dependency to take pictures of an editor chrome that changes with every +VS Code release buys a picture that fails for reasons unrelated to this +product. + +**A genuinely editor-native surface is documented without a picture, or +with one marked as hand-taken and dated.** The existing requirement says +the first; this change adds the second as an explicit, listed exception +rather than a silent one, because a tree view is the thing a reader is +looking for and a paragraph describing an icon column is worse than a +dated picture. What the baseline forbids is an unlisted hand-taken +picture, which is the actual defect: nobody knows it is stale. + +**Capture specs stay where the ones that exist are.** +`packages/server/e2e/` already holds `harness-screenshots.spec.ts`; new +captures are added there, in files named for the screens they take, and +run under the existing browser suite. A selective run is how a second +`role="status"` region once broke a spec nobody ran, so the suite stays +one suite. + +## Non-Goals + +- A visual regression test. +- Any change to a screen, a layout, or a component. +- Capturing the pipeline view — that belongs to + `a-graph-of-what-is-running`, in flight at the time of writing. +- Pictures of anything outside this repository's documentation. + +## Risks / Trade-offs + +**More browser specs mean a longer browser suite.** The suite already +has a budget check (`scripts/check-test-budgets.mjs`); the new captures +are added to it deliberately, and a capture that cannot fit is a finding +rather than a silent budget bump. + +**The baseline is an escape hatch.** Anything can be added to it, which +is how it stops being a check. Two rules keep it honest: every entry +states the reason no spec can take that picture, and the reason is one +of a closed set — an editor-native surface, or an external product. +A reviewer can see the whole list in one file. + +**A capture that lands while this one is being written.** +`a-graph-of-what-is-running` added `packages/server/e2e/pipeline.spec.ts` +and the picture it takes, in `main` as #414 while this was being +proposed. The check reads the `e2e` sources rather than a list of +expected pictures, so a capture added by another change is found without +this one being updated — which is the property that makes the check +survive the next change to do the same thing. + +**No protocol impact.** No command, event, or adapter is touched. diff --git a/openspec/changes/every-screenshot-is-taken-by-a-spec/proposal.md b/openspec/changes/every-screenshot-is-taken-by-a-spec/proposal.md new file mode 100644 index 0000000..22d620f --- /dev/null +++ b/openspec/changes/every-screenshot-is-taken-by-a-spec/proposal.md @@ -0,0 +1,65 @@ +# Every screenshot is taken by a spec + +## Why + +`openspec/specs/openspec-workbench/spec.md` already requires it: +"A screenshot used in this repository's documentation SHALL be captured +from the running standalone UI by an end-to-end test, not taken by +hand", because "a hand-taken screenshot goes stale silently: the screen +changes, the picture does not, and nothing fails." + +Twenty of the twenty-six pictures in `docs/images/` predate that +requirement and nothing has brought them under it. Six standalone images +are produced by `packages/server/e2e/harness-screenshots.spec.ts`, +`change-charts.spec.ts` and `pipeline.spec.ts`; the rest are hand-taken, +and their dates +say how well that works: eleven standalone images and nine extension +images, most last touched on 22 August, describing a product that has +shipped twenty-eight changes since. + +Raised in review on 2026-09-12 as "update all the screenshots". Taking +twenty new pictures by hand would satisfy that sentence and reproduce +the defect in six weeks. The requirement already says what to do +instead, and this change is the work of honouring it. + +## Capabilities + +### New + +- A check that fails when a picture in `docs/images/` is neither + produced by a named end-to-end spec nor listed, with its reason, as + one no spec can take. +- End-to-end capture for every standalone screen that has a hand-taken + picture today. + +### Modified + +- The existing screenshot requirement covers the extension's + documentation too: a screen that is the shared web UI inside a webview + is captured from the standalone shell, and a screen that is genuinely + editor-native (tree views, quick picks, menus) is either documented + without a picture or carries one that is explicitly marked as + hand-taken and dated. + +## Out of scope + +Redesigning any screen. A capture that fails because the screen changed +is reported, and the picture is retaken; nothing about the product is +adjusted to make a screenshot nicer. + +`docs/images/standalone/pipeline.png` and the spec that produces it. +That picture and `packages/server/e2e/pipeline.spec.ts` belong to +`a-graph-of-what-is-running`; the check this change adds finds that +capture like any other, and no capture for the pipeline view is added +here. + +Screenshots in articles under `docs/articles/`. They reference the same +files in `docs/images/`, so they are covered by the check without being +a separate surface. + +## Out of scope: taking a picture of a VS Code quick pick + +The editor draws quick picks and menus outside any page the tests can +reach, and the existing requirement already says what to do there: +write the documentation to stand without the picture rather than carry +one nothing checks. diff --git a/openspec/changes/every-screenshot-is-taken-by-a-spec/specs/openspec-workbench/spec.md b/openspec/changes/every-screenshot-is-taken-by-a-spec/specs/openspec-workbench/spec.md new file mode 100644 index 0000000..ae2cd2f --- /dev/null +++ b/openspec/changes/every-screenshot-is-taken-by-a-spec/specs/openspec-workbench/spec.md @@ -0,0 +1,73 @@ +## MODIFIED Requirements + +### Requirement: Documentation screenshots are captured from a running product + +A screenshot used in this repository's documentation SHALL be captured +from the running standalone UI by an end-to-end test, not taken by hand. + +A hand-taken screenshot goes stale silently: the screen changes, the +picture does not, and nothing fails. One captured by a test fails when the +element it waits for is gone, so a screen that changed is reported rather +than quietly misrepresented. + +This applies to the extension's documentation as well as the standalone +application's. A screen that is the shared web UI rendered inside a +webview is not an editor surface: it is reachable from the browser and +SHALL be captured there, whichever host a reader sees it in. + +Where a surface cannot be driven this way — the editor's own tree views, +menus and quick-picks are not reachable from a browser — documentation +SHALL be written to stand without a picture of it, or SHALL carry one +that is explicitly listed as hand-taken, with the reason and the date it +was taken. An unlisted hand-taken picture SHALL NOT be used: the defect +is not that a picture was taken by hand, it is that nobody can tell +which pictures those are. + +#### Scenario: A screen changes under a captured screenshot + +- **WHEN** a screen a documentation screenshot depends on changes +- **THEN** the capture fails on the element it can no longer find, rather + than producing a picture of the wrong screen + +#### Scenario: A webview screen documented for the extension + +- **WHEN** documentation for the VS Code extension shows a screen that + the shared web UI renders +- **THEN** the picture is captured from the standalone shell by an + end-to-end test + +#### Scenario: An editor-native surface + +- **WHEN** a documented surface is drawn by the editor itself +- **THEN** either the documentation stands without a picture, or the + picture is listed as hand-taken with its reason and the date it was + taken + +## ADDED Requirements + +### Requirement: An unaccounted-for documentation picture fails a check + +Every image file under the repository's documentation images directory +SHALL be either produced by a named end-to-end capture or listed, with a +reason from a closed set and the date it was taken, as one no capture +can produce. A file that is neither SHALL fail a repository check that +names it. + +The requirement above states what must be true; without a check, what is +true is that twenty pictures drifted for six weeks and nothing said so. + +A listing that names a file which no longer exists SHALL fail the same +check: a list that outlives what it describes stops being a description +of the repository. + +#### Scenario: A picture is added by hand + +- **WHEN** an image is added to the documentation images directory with + neither a capture that writes it nor an entry explaining why none can +- **THEN** the check fails and names that file + +#### Scenario: A listed picture is deleted + +- **WHEN** a listed hand-taken picture is removed from the repository + and its entry is left behind +- **THEN** the check fails and names the entry diff --git a/openspec/changes/every-screenshot-is-taken-by-a-spec/tasks.md b/openspec/changes/every-screenshot-is-taken-by-a-spec/tasks.md new file mode 100644 index 0000000..6060d90 --- /dev/null +++ b/openspec/changes/every-screenshot-is-taken-by-a-spec/tasks.md @@ -0,0 +1,90 @@ +Twenty of twenty-six pictures in `docs/images/` are hand-taken, most +from 22 August, against a requirement that has said since +`openspec/specs/openspec-workbench/spec.md` that they must not be. + +`docs/images/standalone/pipeline.png` is not among them: +`a-graph-of-what-is-running` added the spec that takes it. The check +must find that capture without being told about it. + +## 1. The check + +- [ ] 1.1 `scripts/check-screenshots.mjs` walks `docs/images/**/*.png` + and, for each file, finds either a `packages/server/e2e/*.spec.ts` + that writes that exact path or an entry in + `scripts/screenshot-baseline.json`. Anything else fails with the + picture's path. +- [ ] 1.2 A baseline entry carries `path`, `reason` (one of + `editor-native` or `external-product`) and `captured` (an ISO date). + An entry with any other reason fails to parse rather than being + accepted. +- [ ] 1.3 `scripts/check-screenshots.test.mjs` covers: a picture with a + spec passes, a picture with neither fails, a baseline entry with an + unknown reason fails, and a baseline entry naming a picture that no + longer exists fails — a baseline that outlives its picture is how the + list stops describing the repository. +- [ ] 1.4 `npm run lint:screenshots` runs it, and `lint` in the root + `package.json` includes it. + +## 2. Standalone captures + +Each task adds a capture to `packages/server/e2e/` for a picture that is +hand-taken today, and replaces the committed file with the spec's +output. + +- [ ] 2.1 `view-summary.png` — the change summary view. +- [ ] 2.2 `change-editor.png` — the change editor. +- [ ] 2.3 `diff-preview.png` — the diff preview. +- [ ] 2.4 `templates.png` — the template catalog. +- [ ] 2.5 `processes.png` — the process list. +- [ ] 2.6 `run-command.png` — the run command surface. +- [ ] 2.7 `run-with-harness.png` — the "Run with Agentic Harness" entry + point. +- [ ] 2.8 `01-permission-request.png`, and no other file in this task — + the permission request surface. +- [ ] 2.9 `02-human-only-inbox.png` — the "Waiting on somebody" block. +- [ ] 2.10 `03-observation-record.png` — the observation record. +- [ ] 2.11 `04-mechanical-checks.png` — the mechanical-check result. +- [ ] 2.12 Each capture waits on a named element and fails when it is + absent. A capture that screenshots the page after a fixed delay is not + a capture that reports a changed screen, which is the entire reason + for this requirement. + +## 3. Extension pictures + +- [ ] 3.1 `docs/images/extension/overview-compact.png` and + `overview-expanded.png`: decide per picture whether the surface is the + shared web UI in a webview (capture it from the standalone shell) or + an editor tree view (baseline it as `editor-native` with today's + date). Record which, and why, in this task. +- [ ] 3.2 `archive-actions.png`, `archive-tasks.png`, + `nested-tasks.png`, `specs-list.png`, `template-actions.png`: tree and + menu surfaces. Baseline each as `editor-native`, retaken by hand + against the current build, with the date recorded in the baseline. +- [ ] 3.3 `repository-setup.png` and `specs-editor.png`: same decision as + 3.1, recorded the same way. +- [ ] 3.4 Any extension picture whose surface no longer exists is + deleted, and the paragraph that carried it is rewritten to stand + without it. Do not keep a picture of a screen the product does not + have. + +## 4. Verification + +- [ ] 4.1 `npm run lint:screenshots` passes with every picture either + spec-produced or baselined. +- [ ] 4.2 **Delegated to `claude-cli`**: run the whole browser suite + (`npm run test:e2e` in `packages/server`, not a selected spec) and + record the spec count, the pass count and the wall-clock time. A + selective run once reported green while a second `role="status"` + region broke a spec the change never mentioned. +- [ ] 4.3 The captured files are committed, and `git status` after a + second suite run reports no modification — a capture that differs + between two runs of the same build is a flaky picture, not a + screenshot. +- [ ] 4.4 This change validates strictly. `check(validate-change)` +- [ ] 4.5 `npm run verify` unpiped, after the last edit, with everything + staged. Record the run and the per-package test counts. +- [ ] 4.6 The browser suite's entry in `scripts/test-budget-baseline.json` + is updated to its measured new value in the same commit that adds the + captures, with the measurement quoted here. +- [ ] 4.7 A changeset exists for each package whose documentation + changed. `check(changeset-present)` diff --git a/openspec/changes/two-steps-to-a-run/.openspec.yaml b/openspec/changes/two-steps-to-a-run/.openspec.yaml new file mode 100644 index 0000000..2b596d1 --- /dev/null +++ b/openspec/changes/two-steps-to-a-run/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-09-12 diff --git a/openspec/changes/two-steps-to-a-run/design.md b/openspec/changes/two-steps-to-a-run/design.md new file mode 100644 index 0000000..c660b94 --- /dev/null +++ b/openspec/changes/two-steps-to-a-run/design.md @@ -0,0 +1,70 @@ +# Design + +## Decisions + +**A how-to is a goal, two steps, and a link — and it is refused if it +needs three.** The constraint is the design. A goal that cannot be +reached in two steps is evidence about the product, not about the +document: it says the configuration for that goal is spread too thin, +and the fix is a change to the product rather than a longer page. A +how-to that grows a third step is recorded as an open question in this +change, not padded. + +Rejected: a tutorial that walks through the whole harness. That is what +`HARNESS.md` is, and a second one would drift from it. Every how-to +links into the reference for detail and repeats none of its tables. + +**They live in `docs/how-to/`, one file per goal, and `HARNESS.md`'s +existing task index links them.** The index at the top of `HARNESS.md` +already maps a task to a key; each row gains the page that does it, so +there is one place a reader starts and two documents behind it. + +Rejected: putting the how-tos inside `HARNESS.md`. It is already long +enough that the table of contents is how it is used, and a short path +buried in a reference is not a short path. + +**Each how-to states which file it edits, and shows the JSON.** Both +UIs edit the same files, and five settings no UI can edit at all. A +page that says only "open the settings tab" is wrong for the settings +that are not there, so each page names the file and shows the object it +writes. + +**The Gemini/Codex finding is written where custom agents are +documented, with the date it was checked and the mechanism each CLI +actually uses.** Not as "unsupported": the reason matters. Discovery is +possible for both; selection is not, because neither documents a flag, +and this repository's mechanism is a flag on an allowlisted invocation. +A prompt-prefix mechanism (`@name`) would put the agent's name inside +the prompt instead of the argument list, which the allowlist does not +constrain — a different security question, and one nobody has asked for +yet. + +Rejected: adding `.gemini/agents` and `.codex/agents` to +`custom-agents.ts` now, listing them and greying the selection. A list +whose selection does nothing is the "ceiling that cannot act" defect +`agentsAcceptingCustomAgents` exists to prevent. + +## Non-Goals + +- Any code change in `packages/`. This change writes documentation and + edits `HARNESS.md`; a how-to that cannot be written without a code + change is recorded as a finding instead. +- Changing which settings the UIs can edit. +- Installing or testing the Gemini and Codex CLIs. The finding is what + their published documentation states, dated, and it says that. + +## Risks / Trade-offs + +**Documentation that duplicates a reference goes stale.** Mitigated by +the link rule: a how-to states the goal, the file, the two steps and the +object to write, and every table, accepted value and default stays in +`HARNESS.md` alone. A verification task greps the how-tos for the +per-agent effort values and the accepted-key list to prove neither was +copied. + +**A published convention can change.** The two CLIs' documented +behaviour was read on 2026-09-12 and is recorded with that date, which +is the same treatment `custom-agents.ts` already gives its own +"verified on 2026-09-09" note. + +**No protocol impact.** No command, event, or adapter is touched. diff --git a/openspec/changes/two-steps-to-a-run/proposal.md b/openspec/changes/two-steps-to-a-run/proposal.md new file mode 100644 index 0000000..4ee83d3 --- /dev/null +++ b/openspec/changes/two-steps-to-a-run/proposal.md @@ -0,0 +1,77 @@ +# Two steps to a run + +## Why + +`HARNESS.md` documents nine top-level configuration keys, a per-agent +effort table, a stage sequence, a mechanical-check set, and a table of +which five settings no UI can edit. It is accurate and it is a +reference: organised by key, answering "what does this setting accept". + +Raised in review on 2026-09-12: the settings keep growing and the tool +is getting harder to start using. Both halves of that are true, and the +second is not fixed by removing settings. `setup-offers-only-what-applies` +and `an-autonomy-level-says-what-it-does` each made one field easier to +read; a person who wants to run a change unattended still has to +assemble that answer out of `autonomyLevel`, `checkpoints`, +`reviewGate`, `stepAgents` and a budget, from a document organised by +none of those goals. + +`HARNESS.md` already admits the shape of the fix in its own first table +— "Hand one numbered task to an agent → `taskAgents`" — which is a +task-to-key index with nowhere to send the reader except back into the +reference. + +The same review asked whether custom agents configured for Gemini and +Codex can be offered the way Claude's and Copilot's are. The answer is a +documentation fact, and it belongs with the rest of what +`HARNESS.md` says about custom agents: + +- **Gemini CLI** reads subagents from `.gemini/agents/*.md` and + `~/.gemini/agents/*.md` — Markdown with YAML frontmatter, the same + shape Claude uses. It documents **no flag** that selects one for a + single non-interactive run: selection is `@name` at the start of the + prompt, or the interactive `/agents` command. +- **Codex CLI** reads subagents from `.codex/agents/*.toml` and + `~/.codex/agents/*.toml` — TOML, whose `name` field, not the file + name, is the agent's name. It documents no `codex exec` flag that + selects one either; delegation is named in the prompt. + +So `customAgentFlag`, which is what this repository's `customAgent` +support is built on, has nothing to bind to in either CLI. That is worth +writing down: the question will be asked again, and "we checked, and +here is what they accept" is the only answer that stops it being +re-derived. + +## Capabilities + +### New + +- A short how-to per common task, each stating the goal, the two steps + that reach it, and a link into `HARNESS.md` for the detail — reached + from `HARNESS.md`'s existing task index and from `README.md`. + +### Modified + +- `HARNESS.md`'s custom-agent section states what Gemini's and Codex's + CLIs actually accept, and why neither can be offered a custom agent + the way Claude's and Copilot's can. + +## Out of scope + +Any change to what a setting does, or to which settings a UI can edit. +This change writes paths through the configuration that exists. + +Discovering or offering Gemini and Codex custom agents. Their +definitions could be listed — the conventions are data — but nothing +could be done with a selection, because neither CLI documents a flag to +pass one. Offering a picker that cannot change what runs is worse than +offering none. If either CLI gains a flag, that is its own change, and +this one leaves the finding recorded for whoever proposes it. + +Named configurations. "Give me a sensible configuration in one action" +is already answered: `HARNESS_TEMPLATES` +(`packages/core/src/harness-templates.ts`) offers four, chosen by the +effort they ask for, in the same settings view. A how-to and a named +configuration answer the same complaint differently — one explains what +a person is choosing, the other chooses for them — and each page here +names the configuration to start from where one applies. diff --git a/openspec/changes/two-steps-to-a-run/specs/agentic-harness/spec.md b/openspec/changes/two-steps-to-a-run/specs/agentic-harness/spec.md new file mode 100644 index 0000000..0c9b571 --- /dev/null +++ b/openspec/changes/two-steps-to-a-run/specs/agentic-harness/spec.md @@ -0,0 +1,50 @@ +## ADDED Requirements + +### Requirement: A common configuration goal has a short path + +A goal a person configures the harness for SHALL have a document that +states the goal, the file it edits, at most two steps, and the object +those steps write — linking the reference documentation for accepted +values rather than restating them. + +Reference documentation organised by configuration key answers "what +does this key accept". It does not answer "what do I set to get this +outcome", and a person assembling that answer out of four keys in a +reference is the cost that grows with every setting added. + +A goal that cannot be expressed in two steps SHALL be recorded as such, +naming what stands in the way, rather than written up as a longer path. +The length is evidence about the configuration, not about the document. + +#### Scenario: A goal that fits in two steps + +- **WHEN** a common goal can be reached by editing one file in at most + two steps +- **THEN** a document states the goal, that file, those steps and the + object written, and links the reference for the detail + +#### Scenario: A goal that does not fit + +- **WHEN** a common goal cannot be reached in two steps +- **THEN** the goal and the obstacle are recorded, and no page is + written that pads the path to make it fit + +### Requirement: What a CLI cannot be given is stated with its reason + +Where an agent's own CLI cannot accept a configuration this harness +offers for other agents, the documentation of that configuration SHALL +say so, with the date the CLI's published behaviour was read and the +mechanism that CLI uses instead. + +"Unsupported" alone invites the same question to be asked and +re-derived. A statement that names what the CLI does document — a +directory it reads, a selection syntax it accepts — is what lets a +future reader tell whether the situation has changed. + +#### Scenario: An agent's CLI has no flag for a capability offered to others + +- **WHEN** an agent's CLI reads custom agent definitions but documents + no flag selecting one for a single non-interactive run +- **THEN** the documentation states the directories it reads, the + selection mechanism it does document, the date read, and that this is + why no custom agent can be offered for it diff --git a/openspec/changes/two-steps-to-a-run/tasks.md b/openspec/changes/two-steps-to-a-run/tasks.md new file mode 100644 index 0000000..39c191d --- /dev/null +++ b/openspec/changes/two-steps-to-a-run/tasks.md @@ -0,0 +1,82 @@ +`HARNESS.md` answers "what does this key accept". Nothing answers "I +want to do this one thing" in fewer than a page of reading. + +## 1. The pages + +Each page states the goal, names the file it edits, gives at most two +steps, shows the JSON object it writes, and links `HARNESS.md` for the +detail. A page that needs a third step is not padded — see 3.1. + +- [ ] 1.1 `docs/how-to/run-a-change-unattended.md`: `autonomyLevel`, + `checkpoints.requireConfirmationBetweenSteps`, which file they go in, + and that a global file may set neither. +- [ ] 1.2 `docs/how-to/hand-a-task-to-an-agent.md`: `taskAgents` keyed by + the task number exactly as `tasks.md` writes it, and where the result + is seen — the "Waiting on somebody" block and **OpenSpec UI: Run This + Delegated Item**. +- [ ] 1.3 `docs/how-to/cap-what-a-run-can-spend.md`: the chain-level + `budget` and the per-stage one, which unit each agent reports in, and + a link to `LIMITS.md` for what actually caps a run. +- [ ] 1.4 `docs/how-to/use-your-own-agent-definition.md`: where Claude's + and Copilot's definitions are read from, and `stepAgents..customAgent`. +- [ ] 1.5 `docs/how-to/run-a-change-from-a-terminal.md`: + `openspec-ui-cli run `, and the one refusal that stops it + most often — a change whose configuration asks for a confirmation no + terminal can answer. +- [ ] 1.6 `docs/how-to/run-changes-side-by-side.md`: `worktree add`, + `worktree list`, `worktree remove`, and that the lease is per + workspace. +- [ ] 1.7 No page restates an accepted-value table, a default, or a + per-agent effort list. Each links `HARNESS.md` for those instead. + +## 2. The way in + +- [ ] 2.1 `HARNESS.md`'s task index at the top of the file gains a column + linking the how-to page for each row that has one. Do not remove the + key column: the index's job is still to name the setting. +- [ ] 2.2 `README.md`'s "Agentic Harness" section links `docs/how-to/` + once, by directory. Do not list the pages in `README.md`. + +## 3. What two steps could not reach + +- [ ] 3.1 Any goal above that could not be written in two steps is + recorded in this change's `design.md` under a "Could not be done in + two steps" heading, naming the goal and what stands in the way. An + empty section is a valid outcome and is written as such; a page padded + to three steps is not. + +## 4. What Gemini and Codex accept + +- [ ] 4.1 `HARNESS.md`'s custom-agent section states, with the date it + was read (2026-09-12) and a link to each CLI's own documentation: + Gemini reads `.gemini/agents/*.md` and `~/.gemini/agents/*.md` + (Markdown with YAML frontmatter) and documents no flag selecting one + for a single run — selection is `@name` at the start of the prompt or + the interactive `/agents` command. +- [ ] 4.2 The same section states: Codex reads `.codex/agents/*.toml` + and `~/.codex/agents/*.toml` (TOML, whose `name` field rather than the + file name is the agent's name) and documents no `codex exec` flag + selecting one. +- [ ] 4.3 The same section states the consequence in one sentence: this + repository's `customAgent` is passed as a flag on an allowlisted + invocation, so neither CLI can be offered one until it has such a + flag. Do not write "not supported" without the reason — the reason is + what stops the question being re-derived. +- [ ] 4.4 No code in `packages/core/src/custom-agents.ts` or + `packages/core/src/custom-agent-family.ts` is modified by this change. + `check(path-unchanged, packages/core/src/custom-agents.ts)` + +## 5. Verification + +- [ ] 5.1 Every file path, key name and command quoted in the new pages + exists: keys against `packages/core/src/harness-config.ts`, commands + against `USAGE` in `packages/cli/src/main.ts`, VS Code command titles + against `packages/extension/package.json`. +- [ ] 5.2 This change validates strictly. `check(validate-change)` +- [ ] 5.3 `npm run verify` unpiped, after the last edit, with everything + staged. Record the run and the per-package test counts. +- [ ] 5.4 **Human-only**: somebody who has not configured the harness + before follows one page start to finish and reaches the goal without + opening `HARNESS.md`. The point of a two-step path is that it works + for a person who does not already know the answer, and only a person + who does not already know the answer can check that. diff --git a/openspec/changes/what-shipped-since-0-44/.openspec.yaml b/openspec/changes/what-shipped-since-0-44/.openspec.yaml new file mode 100644 index 0000000..2b596d1 --- /dev/null +++ b/openspec/changes/what-shipped-since-0-44/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-09-12 diff --git a/openspec/changes/what-shipped-since-0-44/design.md b/openspec/changes/what-shipped-since-0-44/design.md new file mode 100644 index 0000000..41a32fc --- /dev/null +++ b/openspec/changes/what-shipped-since-0-44/design.md @@ -0,0 +1,67 @@ +# Design + +## Decisions + +**One article for the range, not one per change.** Twenty-eight archived +changes are not twenty-eight things a user got; they are about eight, +each arrived at over several changes. The article is organised by what a +person can now do. + +Rejected: a per-release note generated from changesets. Changesets say +what changed in a package, which is the right granularity for a +maintainer deciding whether to upgrade and the wrong one for somebody +asking what this tool does now. That document already exists per package +as `CHANGELOG.md`, and duplicating it in prose would leave two records +of the same facts to drift. + +Rejected: a page per capability in `docs/`. A reference organised by +capability is what `HARNESS.md` already is. A second one would compete +with it, and the one thing a reader gets from a dated article — "this is +what is new since the version I have" — would be lost. + +**Each section names the one way in.** A capability described without +the command or screen that reaches it is an announcement, not +documentation. Every section ends with the exact command +(`openspec-ui-cli ready`), the exact VS Code command title +(**OpenSpec UI: Run This Delegated Item**), or the exact tab. + +**Written from the archived changes, not from memory.** Each section +cites the change it came from, so a reader who wants the detail has the +`openspec/changes/archive//` entry, and so the author of the article +cannot describe a capability the repository does not have. This is the +same rule `a-date-is-one-day-in-every-source` applied to dates: the +source is named. + +Rejected: writing it from the packages' `CHANGELOG.md`. Those entries +are one line each and several say only "internal"; the archived change +carries the reasoning that makes a capability explainable. + +**The teaser is short and links the article.** The shape is set by +`docs/articles/2026-09-09-teaser-0.44.md`: a few lines that can be +posted somewhere, not a summary that has to be maintained in parallel. + +## Non-Goals + +- Restructuring `HARNESS.md` or `LIMITS.md`. +- Any new screenshot, or any change to how screenshots are produced. +- A marketing page, a site, or anything published outside this + repository. +- Describing capabilities that are proposed and not yet archived — + including the five changes proposed alongside this one. + +## Risks / Trade-offs + +**An article dates.** It states a range (0.44 → 0.50) in its title and +its file name, so it dates honestly rather than pretending to be current +forever. The cost is that the next release needs another one; the +alternative — a living "features" page — has to be corrected on every +change and is wrong between corrections. + +**Prose can claim more than the build does.** Mitigated by citing the +archived change per section, and by a verification task that opens the +named command or screen for each claim. A claim whose source is named is +checkable; one written from memory is not. + +**No protocol impact.** This change adds no command and no event, and +touches no adapter. Nothing in `packages/` is modified except a +`README.md` link. diff --git a/openspec/changes/what-shipped-since-0-44/proposal.md b/openspec/changes/what-shipped-since-0-44/proposal.md new file mode 100644 index 0000000..5c5e067 --- /dev/null +++ b/openspec/changes/what-shipped-since-0-44/proposal.md @@ -0,0 +1,55 @@ +# What shipped since 0.44 + +## Why + +The last thing written for a person who uses this tool rather than +builds it is `docs/articles/2026-09-09-what-a-run-tells-you-0.40-to-0.44.md`. +The extension is 0.50.2 and twenty-eight changes have been archived +since that article was written. + +Raised in review on 2026-09-12: the new capabilities are not described +for a user anywhere. That is exactly true. They are described in +`HARNESS.md` and `LIMITS.md`, which are reference tables organised by +configuration key — the right shape for somebody who already knows the +feature exists and the wrong shape for somebody finding out that it +does. `openspec/specs/persistent-workbench-runs/spec.md` already +requires release documentation to treat package versions as +authoritative; nothing requires that a release be explained in the terms +of the person using it, and so it has not been. + +What is missing is one document that says, in order: a change can be run +from a terminal; changes can run side by side in their own working +directories; a task can be handed to a named agent and then actually +run; a run can be scheduled; mechanical checks run before a verifying +agent is spent; a change can declare a step, and a blocker; who holds a +workspace can be asked. + +## Capabilities + +### New + +- An article covering 0.44 → 0.50 in a user's terms: what each new + capability is for, what it replaces, and the one command or screen it + is reached from. +- A short teaser for the same release, following the shape + `docs/articles/2026-09-09-teaser-0.44.md` already set. + +### Modified + +- `README.md`'s Status section points at the current article rather than + leaving the most recent one to be found by directory listing. + +## Out of scope + +`HARNESS.md` and `LIMITS.md`. They are reference documents and they are +accurate; this change does not restructure them. Turning a common task +into a short path is `two-steps-to-a-run`, which is its own change. + +Screenshots. Every picture this article wants already exists or is the +subject of `every-screenshot-is-taken-by-a-spec`; this change writes +prose against whatever is in `docs/images/` when it lands, and adds no +hand-taken picture of its own. + +A changelog. `CHANGELOG.md` per package is generated from changesets and +is already correct. An article is not a second changelog: it says what a +capability is for, which a changeset entry deliberately does not. diff --git a/openspec/changes/what-shipped-since-0-44/specs/release-quality/spec.md b/openspec/changes/what-shipped-since-0-44/specs/release-quality/spec.md new file mode 100644 index 0000000..1f95044 --- /dev/null +++ b/openspec/changes/what-shipped-since-0-44/specs/release-quality/spec.md @@ -0,0 +1,36 @@ +## ADDED Requirements + +### Requirement: A release is explained in the terms of the person using it + +What a release changed SHALL be written for the person who uses this +tool, not only for the person who builds it. + +A package's `CHANGELOG.md` states what changed in that package. It does +not state what a capability is for, what it replaces, or how it is +reached, and a reader who does not already know a feature exists cannot +learn it from a changeset entry. Reference documentation (`HARNESS.md`, +`LIMITS.md`) is organised by configuration key, which has the same +property: it answers questions about a capability already known to the +reader. + +Such a document SHALL state the version range it covers and the package +versions it was written against, and SHALL cite, for each capability it +describes, the archived change that introduced it — so a claim can be +checked against the repository rather than believed. + +It SHALL NOT describe a capability that is not archived at the time it +is written. + +#### Scenario: A release adds a capability a user must be told about + +- **WHEN** a release adds a capability a person using the tool would + have to be told about to use +- **THEN** it is described in the user's terms, with the command or + screen it is reached from, and with the archived change it came from + named + +#### Scenario: A capability is proposed but not archived + +- **WHEN** a capability has been proposed and not archived +- **THEN** it is absent from the document, whatever state its + implementation is in diff --git a/openspec/changes/what-shipped-since-0-44/tasks.md b/openspec/changes/what-shipped-since-0-44/tasks.md new file mode 100644 index 0000000..5198a6e --- /dev/null +++ b/openspec/changes/what-shipped-since-0-44/tasks.md @@ -0,0 +1,77 @@ +The last article for a user covers 0.40 → 0.44. The extension is +0.50.2, and twenty-eight archived changes later nobody has been told +what they can now do. + +## 1. The article + +- [ ] 1.1 `docs/articles/2026-09-12-what-you-can-run-now-0-44-to-0-50.md` + exists, opens by naming the version range, and states which package + versions it was written against (`core`, `server`, `webui`, + `extension`, `cli`) read from each `package.json` rather than assumed. +- [ ] 1.2 A section on running a change from a terminal: `openspec-ui-cli + run `, what its three exit codes mean, and that it takes the + same workspace lease the two interactive hosts take. Cites + `openspec/changes/archive/2026-09-11-a-change-runs-from-the-terminal/`. +- [ ] 1.3 A section on changes running side by side: one git worktree per + change, `openspec-ui-cli worktree add `, and that overlap is + decided from the file paths tasks declare. Cites + `.../2026-09-11-changes-run-side-by-side/`. +- [ ] 1.4 A section on what can start now: `openspec-ui-cli ready`, and + that an empty queue reports success rather than failure. Cites + `.../2026-09-11-what-can-start-now/` and + `.../2026-09-11-an-empty-queue-is-not-a-failure/`. +- [ ] 1.5 A section on handing one numbered task to an agent: + `taskAgents`, the Human-Only Inbox, and **OpenSpec UI: Run This + Delegated Item**. Cites `.../2026-09-11-a-delegated-item-runs-its-agent/` + and `.../2026-09-10-human-only-inbox-in-the-shell/`. +- [ ] 1.6 A section on scheduling a run. Cites + `.../2026-09-10-a-run-can-be-scheduled/` and + `.../2026-09-11-a-schedule-keeps-its-promise/`. +- [ ] 1.7 A section on mechanical checks before `verify`: the closed set + of six names, and that a failing check skips the verifying agent + instead of spending it. Cites `.../2026-09-10-a-check-that-passes-checked-something/`. +- [ ] 1.8 A section on a change declaring a step and declaring a blocker. + Cites `.../2026-09-11-a-change-can-declare-a-step/` and + `.../2026-09-11-a-declared-blocker-blocks/`. +- [ ] 1.9 A section on asking who holds a workspace: `openspec-ui-cli + lease`, `lease release`, and that the recorded git identity is + attribution and never authentication. Cites + `.../2026-09-12-a-lease-says-who/`. +- [ ] 1.10 A closing section naming what is configured where — + `HARNESS.md` for every key, `LIMITS.md` for what caps a run — so the + article ends by handing the reader the reference rather than + paraphrasing it. +- [ ] 1.11 No section describes a capability that is not archived. The + six changes proposed on 2026-09-12 are absent from the article, even + where one of them is already being implemented. + +## 2. The teaser + +- [ ] 2.1 `docs/articles/2026-09-12-teaser-0-50.md`, following the shape + of `docs/articles/2026-09-09-teaser-0.44.md`: a few lines and a link + to the article, with no fact that is not in the article. + +## 3. The pointer + +- [ ] 3.1 `README.md`'s "Status" section links the new article by path. + Do not add a list of articles to `README.md` — a directory listing is + already that list, and a second one in the README goes stale. + +## 4. Verification + +- [ ] 4.1 Every command and VS Code command title quoted in the article + exists: each `openspec-ui-cli` invocation appears in `USAGE` in + `packages/cli/src/main.ts`, and each command title appears in + `packages/extension/package.json`'s `contributes.commands`. +- [ ] 4.2 Every `openspec/changes/archive//` path cited in the + article exists in the repository. +- [ ] 4.3 This change validates strictly. `check(validate-change)` +- [ ] 4.4 `npm run verify` unpiped, after the last edit, with everything + staged. Record the run and the per-package test counts. +- [ ] 4.5 A changeset exists for the `README.md` edit — a documentation + patch for the package whose README moved, and none for packages this + change does not touch. `check(changeset-present)` +- [ ] 4.6 **Human-only**: the article reads as something written for a + person who has the tool installed and does not know what is new, + rather than as a list of changes. No automated check can make this + judgement, and it is the whole point of the change.