From 653f297235a6ea103165c60fb18c860458b09a78 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Sat, 12 Sep 2026 09:58:46 +0300 Subject: [PATCH 1/2] Six proposals: explain it, picture it, suggest it, preset it Raised in review on 2026-09-12: the new capabilities are not described for a user, the screenshots are stale, the settings keep growing, and the tool could point out the non-obvious instead of waiting to be asked. - what-shipped-since-0-44: an article for a person who has the tool and does not know what is new, cited per section from the archived change it came from. - two-steps-to-a-run: a short path per common goal, refused rather than padded when it needs a third step. Records what Gemini's and Codex's CLIs actually accept for custom agents, and why neither can be given one here. - every-screenshot-is-taken-by-a-spec: twenty of twenty-six pictures are hand-taken against a requirement that has forbidden it since it was written. Captures for the standalone ones, an explicit dated list for the editor-native ones, and a check so the list cannot drift again. - a-hint-says-what-can-run-together: readiness already computes which changes can be started alongside each other; this offers it, with the fact it came from and the exact commands. No daemon, no writing, no chosen grouping. - a-preset-instead-of-twelve-fields: name the intention, get the ordinary JSON those keys live in. Not a configuration layer. - a-doctor-says-what-would-stop-a-run: ask what is missing instead of starting a run to be refused, with the preflight's own answer for a named change. openspec validate --strict --changes: 7 passed, 0 failed. Co-Authored-By: Claude Opus 5 (1M context) --- .../.openspec.yaml | 5 + .../design.md | 77 +++++++++++++ .../proposal.md | 59 ++++++++++ .../specs/ci-cli/spec.md | 62 +++++++++++ .../tasks.md | 80 ++++++++++++++ .../.openspec.yaml | 6 + .../design.md | 89 +++++++++++++++ .../proposal.md | 66 +++++++++++ .../specs/ci-cli/spec.md | 65 +++++++++++ .../specs/shared-ui/spec.md | 31 ++++++ .../tasks.md | 94 ++++++++++++++++ .../.openspec.yaml | 5 + .../design.md | 80 ++++++++++++++ .../proposal.md | 60 ++++++++++ .../specs/agentic-harness/spec.md | 61 +++++++++++ .../tasks.md | 103 ++++++++++++++++++ .../.openspec.yaml | 2 + .../design.md | 79 ++++++++++++++ .../proposal.md | 65 +++++++++++ .../specs/openspec-workbench/spec.md | 73 +++++++++++++ .../tasks.md | 90 +++++++++++++++ .../changes/two-steps-to-a-run/.openspec.yaml | 2 + openspec/changes/two-steps-to-a-run/design.md | 70 ++++++++++++ .../changes/two-steps-to-a-run/proposal.md | 74 +++++++++++++ .../specs/agentic-harness/spec.md | 50 +++++++++ openspec/changes/two-steps-to-a-run/tasks.md | 82 ++++++++++++++ .../what-shipped-since-0-44/.openspec.yaml | 2 + .../changes/what-shipped-since-0-44/design.md | 67 ++++++++++++ .../what-shipped-since-0-44/proposal.md | 55 ++++++++++ .../specs/release-quality/spec.md | 36 ++++++ .../changes/what-shipped-since-0-44/tasks.md | 77 +++++++++++++ 31 files changed, 1767 insertions(+) create mode 100644 openspec/changes/a-doctor-says-what-would-stop-a-run/.openspec.yaml create mode 100644 openspec/changes/a-doctor-says-what-would-stop-a-run/design.md create mode 100644 openspec/changes/a-doctor-says-what-would-stop-a-run/proposal.md create mode 100644 openspec/changes/a-doctor-says-what-would-stop-a-run/specs/ci-cli/spec.md create mode 100644 openspec/changes/a-doctor-says-what-would-stop-a-run/tasks.md create mode 100644 openspec/changes/a-hint-says-what-can-run-together/.openspec.yaml create mode 100644 openspec/changes/a-hint-says-what-can-run-together/design.md create mode 100644 openspec/changes/a-hint-says-what-can-run-together/proposal.md create mode 100644 openspec/changes/a-hint-says-what-can-run-together/specs/ci-cli/spec.md create mode 100644 openspec/changes/a-hint-says-what-can-run-together/specs/shared-ui/spec.md create mode 100644 openspec/changes/a-hint-says-what-can-run-together/tasks.md create mode 100644 openspec/changes/a-preset-instead-of-twelve-fields/.openspec.yaml create mode 100644 openspec/changes/a-preset-instead-of-twelve-fields/design.md create mode 100644 openspec/changes/a-preset-instead-of-twelve-fields/proposal.md create mode 100644 openspec/changes/a-preset-instead-of-twelve-fields/specs/agentic-harness/spec.md create mode 100644 openspec/changes/a-preset-instead-of-twelve-fields/tasks.md create mode 100644 openspec/changes/every-screenshot-is-taken-by-a-spec/.openspec.yaml create mode 100644 openspec/changes/every-screenshot-is-taken-by-a-spec/design.md create mode 100644 openspec/changes/every-screenshot-is-taken-by-a-spec/proposal.md create mode 100644 openspec/changes/every-screenshot-is-taken-by-a-spec/specs/openspec-workbench/spec.md create mode 100644 openspec/changes/every-screenshot-is-taken-by-a-spec/tasks.md create mode 100644 openspec/changes/two-steps-to-a-run/.openspec.yaml create mode 100644 openspec/changes/two-steps-to-a-run/design.md create mode 100644 openspec/changes/two-steps-to-a-run/proposal.md create mode 100644 openspec/changes/two-steps-to-a-run/specs/agentic-harness/spec.md create mode 100644 openspec/changes/two-steps-to-a-run/tasks.md create mode 100644 openspec/changes/what-shipped-since-0-44/.openspec.yaml create mode 100644 openspec/changes/what-shipped-since-0-44/design.md create mode 100644 openspec/changes/what-shipped-since-0-44/proposal.md create mode 100644 openspec/changes/what-shipped-since-0-44/specs/release-quality/spec.md create mode 100644 openspec/changes/what-shipped-since-0-44/tasks.md 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..4fbdcf4 --- /dev/null +++ b/openspec/changes/a-doctor-says-what-would-stop-a-run/design.md @@ -0,0 +1,77 @@ +# Design + +## Decisions + +**A binary is checked by resolving it, never by running it.** +`packages/core/src/environment-report.ts` looks each agent's executable +up on the PATH and reports found or not found. It does not spawn +`claude --version`. + +Rejected: probing versions by execution. The allowlist in +`security.ts` exists so that this product executes exactly the +invocations it was configured to execute; a version probe is an +invocation nobody allowlisted, run against a binary whose name came from +a registry, at a moment when the user asked a question rather than +started a run. The information gained — a version string — answers no +question this report asks, because no capability here is gated on an +agent's version. + +**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..2926687 --- /dev/null +++ b/openspec/changes/a-doctor-says-what-would-stop-a-run/proposal.md @@ -0,0 +1,59 @@ +# 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. + +Running an agent to see whether it works. A binary's presence on the +PATH is checked by resolving it, never by executing it — 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 — only whether a named binary resolves. 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..3e6b485 --- /dev/null +++ b/openspec/changes/a-doctor-says-what-would-stop-a-run/specs/ci-cli/spec.md @@ -0,0 +1,62 @@ +## 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 checked without invoking it + +The presence of an agent's executable SHALL be determined by resolving +it, and SHALL NOT be determined by executing it. + +Executing a binary to ask its version is an invocation no allowlist +covers, performed because somebody asked a question rather than started +a run. No behaviour here depends on an agent's version. + +#### Scenario: Reporting on an installed agent + +- **WHEN** the report covers an agent whose executable is installed +- **THEN** it reports the executable as present without running it + +### 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..2b82c76 --- /dev/null +++ b/openspec/changes/a-doctor-says-what-would-stop-a-run/tasks.md @@ -0,0 +1,80 @@ +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`: its executable resolved on the + PATH or not. Resolve it; do not execute it. A version probe is an + invocation nobody allowlisted — state this prohibition in the code + comment, not only here. +- [ ] 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/a-preset-instead-of-twelve-fields/.openspec.yaml b/openspec/changes/a-preset-instead-of-twelve-fields/.openspec.yaml new file mode 100644 index 0000000..30c7ba6 --- /dev/null +++ b/openspec/changes/a-preset-instead-of-twelve-fields/.openspec.yaml @@ -0,0 +1,5 @@ +schema: spec-driven +created: 2026-09-12 +follows: + - setup-offers-only-what-applies + - an-autonomy-level-says-what-it-does diff --git a/openspec/changes/a-preset-instead-of-twelve-fields/design.md b/openspec/changes/a-preset-instead-of-twelve-fields/design.md new file mode 100644 index 0000000..1b3b7b8 --- /dev/null +++ b/openspec/changes/a-preset-instead-of-twelve-fields/design.md @@ -0,0 +1,80 @@ +# Design + +## Decisions + +**A preset is a value, not a mode.** `HARNESS_PRESETS` in +`packages/core/src/harness-presets.ts` is a list of +`{ id, title, intention, scope, values }`, where `values` is a partial +`HarnessConfig`. Applying one merges `values` into the target file and +writes it. Nothing records that a preset was used, and nothing reads a +preset at run time. + +Rejected: a `preset` key in `harness.json` that the resolver expands. It +would be a third configuration layer over the two that exist, and every +question the reader has — what is actually set, why did this stage get +that agent — would need the preset table to answer. It also creates a +class of bug this repository has already had twice: a per-change file +that overrides part of something and silently inherits the rest +(`stage-override-keeps-the-rest`, `a-stage-override-keeps-its-custom-agent`). + +**Applying says what it will change, before it changes it.** Applying a +preset to a file that already has values produces a diff of the keys it +would overwrite, and is confirmed against that. A preset applied to an +empty file is the common case and shows the same summary. + +Rejected: applying only to keys that are unset. A preset is an +intention, and one that silently skipped the fields a person had already +set would produce a configuration matching no preset and no intention. + +**Scope is part of the preset.** Some values a global file may not carry +at all (`autonomyLevel: "autonomous"`, `reviewGate.mode: +"agent-sufficient"`, `checkpoints.requireConfirmationBetweenSteps: +false`, and the two keys global files may never set). A preset declares +whether it applies to the workspace default, a single change, or either, +and the surfaces offer only what applies — the rule +`setup-offers-only-what-applies` already established. + +**Three presets, and the constraint that there are few.** "With me +watching" (assisted, confirmation between stages, human review gate), +"Unattended, careful" (semi-autonomous, no confirmation, human review +gate, a budget), "Unattended, all the way" (autonomous, no confirmation, +agent-sufficient review gate, a budget, per-change only — it is what +lets the `git` stage push and merge). A fourth is a proposal with its +own justification: a list of presets long enough to need reading is the +problem it was meant to solve. + +**Written by core, offered by the hosts, applied by the CLI too.** +`applyHarnessPreset({ workspaceRoot, changeName?, presetId })` in core +does the merge and the write; `webui` offers the list where the settings +already are; `openspec-ui-cli harness preset ` applies one from a +terminal. No host computes a preset's values. + +## Non-Goals + +- Any new configuration key, at either file. +- Changing what any existing key does, or which keys a UI can edit. +- Presets applied automatically, at setup or anywhere else. +- Presets for settings outside the harness. + +## Risks / Trade-offs + +**A preset hides what it set.** Mitigated by writing plain JSON into the +file the reader already knows about, by showing the diff before writing, +and by the presets being documented in `HARNESS.md` with their exact +values. The failure mode avoided is the resolver-level preset, where the +file says one word and the behaviour comes from a table. + +**Three presets will not fit everybody.** That is intended: the fields +stay, directly beneath, and a preset is a starting point somebody then +edits. What would not be acceptable is a preset that is nearly right and +cannot be edited. + +**A preset can drift from what the keys mean.** `HARNESS.md` documents +the values, and a test asserts each preset resolves through +`resolveHarnessConfig` to exactly the configuration its documentation +claims — so a change to a default that silently alters a preset fails. + +**Protocol impact: none.** No command or event is added or changed. +Writing a configuration file is something both hosts already do through +the existing settings path; this change adds a value to write, not a way +to write it. diff --git a/openspec/changes/a-preset-instead-of-twelve-fields/proposal.md b/openspec/changes/a-preset-instead-of-twelve-fields/proposal.md new file mode 100644 index 0000000..ed03d60 --- /dev/null +++ b/openspec/changes/a-preset-instead-of-twelve-fields/proposal.md @@ -0,0 +1,60 @@ +# A preset instead of twelve fields + +## Why + +`HARNESS.md` documents nine top-level keys, four stage entries with four +fields each, five settings a global file may not set, and five no UI can +edit. Configuring a change to run unattended means getting +`autonomyLevel`, `checkpoints.requireConfirmationBetweenSteps`, +`reviewGate.mode`, a budget and four `stepAgents` entries consistent +with one another — where two of those five are refused in the global +file and only accepted per change. + +Raised in review on 2026-09-12: the settings keep growing and the point +is to make a person's life simpler. The repository has been answering +this one field at a time — `setup-offers-only-what-applies` removed +choices that could not apply, `an-autonomy-level-says-what-it-does` +renamed one so it read as what it did, `a-setting-reads-as-a-setting` +did the same elsewhere. Each was right and none of them changes the +shape of the problem: the unit a person configures is a whole intention +("run this without me", "be careful with this one"), and the unit the +product offers is a field. + +## Capabilities + +### New + +- Named presets — each a documented set of values for keys that already + exist, with the intention it serves stated in the same words a person + would use. +- Applying one writes the ordinary JSON those keys live in, so the + result is a file that can be read, reviewed, hand-edited and + committed. +- Both hosts offer them where the harness is configured, and the CLI + can apply one. + +### Modified + +- The harness settings surfaces lead with the presets and keep every + field beneath them, so the fields remain the way anything specific is + reached. + +## Out of scope + +A new configuration layer. A preset is not a key in `harness.json`, not +a mode the resolver knows about, and not something a running chain +consults. It writes the same JSON a person would have written and then +has no further existence — see `design.md` for why the alternative was +rejected. + +New settings. A preset that needed a key the harness does not have would +be a product change wearing a preset's clothes; each preset is expressed +entirely in keys that exist today, and a preset that cannot be is +reported as a finding. + +Deciding a preset for the user. Nothing applies one on its own, and none +is applied to an existing configuration without saying which fields it +would change. + +Presets for anything but the harness. The same complaint applies to +other settings surfaces; whether it generalizes is not decided here. diff --git a/openspec/changes/a-preset-instead-of-twelve-fields/specs/agentic-harness/spec.md b/openspec/changes/a-preset-instead-of-twelve-fields/specs/agentic-harness/spec.md new file mode 100644 index 0000000..44c786c --- /dev/null +++ b/openspec/changes/a-preset-instead-of-twelve-fields/specs/agentic-harness/spec.md @@ -0,0 +1,61 @@ +## ADDED Requirements + +### Requirement: An intention can be configured in one action + +A person SHALL be able to configure the harness by naming the intention +they have — run this with me watching, run this without me — rather than +by setting each field that intention implies. + +Such a named set SHALL be expressed entirely in configuration keys that +already exist, and applying it SHALL write those keys as ordinary values +into the file they already live in. It SHALL NOT introduce a +configuration layer of its own: nothing SHALL record which named set was +applied, and nothing SHALL consult one while a chain runs. + +A file written this way is a file a person can read, review, hand-edit +and commit. A file that named a preset and took its behaviour from a +table elsewhere would answer "what is set here" only by reference to +that table. + +Applying one to a file that already carries values SHALL report the keys +it will overwrite, and their current values, before writing. + +A named set SHALL declare which files it may be applied to, and SHALL be +offered only where it applies — not offered and refused. + +#### Scenario: Configuring an unconfigured change + +- **WHEN** a person applies a named set to a change with no + configuration of its own +- **THEN** the change's configuration file carries that set's values as + ordinary keys, and resolves exactly as those keys resolve when written + by hand + +#### Scenario: Applying over existing values + +- **WHEN** a named set is applied to a file that already sets some of + the same keys +- **THEN** the keys that would be overwritten, and their current values, + are reported before anything is written + +#### Scenario: A set whose values a global file may not carry + +- **WHEN** a named set includes a value that only a per-change file may + set +- **THEN** it is not offered for the workspace-wide file at all, and + applying it there is refused with the same error that key's validator + already raises + +### Requirement: Fields remain the way anything specific is reached + +Offering named sets SHALL NOT remove or hide the individual fields. + +A named set is a starting point. A person whose intention is nearly one +of the offered sets SHALL be able to apply it and then edit any field it +set, in the same surface, without undoing the rest. + +#### Scenario: Editing after applying + +- **WHEN** a person applies a named set and then changes one field +- **THEN** that field is changed and every other value the set wrote is + left as it was diff --git a/openspec/changes/a-preset-instead-of-twelve-fields/tasks.md b/openspec/changes/a-preset-instead-of-twelve-fields/tasks.md new file mode 100644 index 0000000..841572b --- /dev/null +++ b/openspec/changes/a-preset-instead-of-twelve-fields/tasks.md @@ -0,0 +1,103 @@ +The unit a person configures is an intention. The unit the product +offers is a field, and there are more of them every month. + +## 1. The presets + +- [ ] 1.1 `packages/core/src/harness-presets.ts` exports + `HARNESS_PRESETS: readonly HarnessPreset[]`, each + `{ id, title, intention, scope, values }` where `values` is a partial + `HarnessConfig` and `scope` is `"global" | "change" | "either"`. +- [ ] 1.2 Preset `watched`: `autonomyLevel: "assisted"`, + `checkpoints.requireConfirmationBetweenSteps: true`, + `reviewGate.mode: "human-required"`. Scope `either`. +- [ ] 1.3 Preset `unattended-careful`: `autonomyLevel: + "semi-autonomous"`, `checkpoints.requireConfirmationBetweenSteps: + false`, `reviewGate.mode: "human-required"`, a chain `budget`. Scope + `change` — two of those three may not appear in a global file. +- [ ] 1.4 Preset `unattended-all-the-way`: `autonomyLevel: + "autonomous"`, `checkpoints.requireConfirmationBetweenSteps: false`, + `reviewGate.mode: "agent-sufficient"`, a chain `budget`. Scope + `change`. State in the preset's `intention` that this is what lets the + `git` stage push, open a pull request and merge. +- [ ] 1.5 No preset introduces a key that + `packages/core/src/harness-config.ts` does not already accept. A + preset needing a new key is reported as a finding in this file rather + than added. +- [ ] 1.6 `packages/core/src/harness-presets.test.ts`: every preset's + `values` passes the config validator for its declared scope, and a + preset whose scope is `either` is accepted in a global file — the + assertion that catches a preset written for the wrong file. + +## 2. Applying one + +- [ ] 2.1 `applyHarnessPreset({ workspaceRoot, changeName?, presetId })` + in `packages/core/src/harness-presets.ts` merges `values` over the + target file's existing object and writes the ordinary JSON. It writes + no marker naming the preset: there is no preset layer to read back. +- [ ] 2.2 It returns the keys it would overwrite and their current + values before writing, so a caller can show what changes. A caller + that does not ask still gets the write; the summary is a return value, + not a prompt inside core. +- [ ] 2.3 Applying a preset to a target whose scope forbids it fails + with the same error class the config validator already raises for that + key, not a new one. +- [ ] 2.4 `packages/core/src/harness-presets.test.ts` covers: applying + to an empty file, applying over existing values (the overwritten keys + are reported), applying a `change`-scoped preset to the global file + (refused), and that the file after applying resolves through + `resolveHarnessConfig` to exactly the configuration the preset + documents. + +## 3. Where they are offered + +- [ ] 3.1 `packages/webui/src/components/HarnessSettingsView.tsx` shows + the presets applicable to the section being edited, above the fields, + each with its `title` and `intention`. The fields stay exactly where + they are: a preset is a starting point, not a replacement. +- [ ] 3.2 Choosing one shows the keys it will overwrite and their + current values, and writes only on confirmation. +- [ ] 3.3 A preset whose `scope` excludes the section is not shown — + not shown disabled. `setup-offers-only-what-applies` decided this for + the setup flow and it holds here. +- [ ] 3.4 `packages/webui/src/components/HarnessSettingsView.test.tsx`: + the global section offers only `either`-scoped presets; the per-change + section offers all three; confirming writes the merged object; the + overwrite summary lists exactly the keys that had values. + +## 4. From a terminal + +- [ ] 4.1 `openspec-ui-cli harness preset [--change ] [--cwd + ]` in `packages/cli/src/harness-preset-command.ts`, wired in + `packages/cli/src/main.ts` with its own `USAGE` entry. +- [ ] 4.2 It prints the keys it will overwrite and applies the preset; + `--dry-run` prints them and writes nothing. Exit `0` applied, `2` + refused (unknown preset, wrong scope, unwritable file). +- [ ] 4.3 `packages/cli/src/harness-preset-command.test.ts`: applying + exits 0 and writes, `--dry-run` exits 0 and writes nothing, an unknown + id exits 2 and names the ids that exist. + +## 5. Documentation + +- [ ] 5.1 `HARNESS.md` gains a presets section listing each preset's + exact values and its scope, adjacent to the key reference rather than + replacing any of it. +- [ ] 5.2 `docs/how-to/run-a-change-unattended.md`, if it exists by then, + names the preset as its first step. Do not create that file here — it + belongs to `two-steps-to-a-run`. + +## 6. Verification + +- [ ] 6.1 This change validates strictly. `check(validate-change)` +- [ ] 6.2 `npm run verify` unpiped, after the last edit, with everything + staged. Record the run and the per-package test counts. +- [ ] 6.3 A changeset exists: `core`, `webui` and `cli` minor. + `check(changeset-present)` +- [ ] 6.4 **Delegated to `claude-cli`**: apply `unattended-careful` to a + scratch change from the terminal, quote the file before and after and + the exit code, then run `openspec-ui-cli run` against that change far + enough to show it does not pause for a confirmation. Evidence: both + file contents and the run's first two stage lines. +- [ ] 6.5 **Human-only**: with the harness settings open on a repository + that has never been configured, choosing a preset is visibly less work + than filling the fields, and the person can still tell what was set. + Whether the presets removed work or only moved it is a judgement. 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..b1252e0 --- /dev/null +++ b/openspec/changes/two-steps-to-a-run/proposal.md @@ -0,0 +1,74 @@ +# 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. + +Presets. "Give me a sensible configuration in one action" is +`a-preset-instead-of-twelve-fields`, a different answer to the same +complaint, and the two are deliberately separate: a how-to explains what +a person is choosing, a preset chooses for them. 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. From 17aedb203302f345a8ee42d4376fb86c1f8d0fec Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Sat, 12 Sep 2026 10:55:26 +0300 Subject: [PATCH 2/2] Withdraw the presets proposal, and correct the doctor's detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both corrections come from reading the code while implementing the first of these changes, which is where they should have come from before they were proposed. `a-preset-instead-of-twelve-fields` is withdrawn. It proposed named sets of configuration values with a scope, applied into the ordinary JSON — which is `HARNESS_TEMPLATES` in `packages/core/src/harness-templates.ts`, shipped by `presets-by-effort`, offered in the Harness Settings view, and carrying measured ceilings this repository's own audit log produced. The proposal described existing behaviour as new. The one axis those four do not cover is supervision: all of them set `semi-autonomous` with a human-required gate, and none touches `checkpoints`. Whether that wants a second axis of named configurations, or nothing, is left open rather than answered by a change written without noticing the first one. `a-doctor-says-what-would-stop-a-run` said an agent's binary must be resolved and never executed. `detectAvailableAgentsDetailed` (`packages/core/src/agent-detection.ts`) already answers that question by probing `--version`, and the REST route, the VS Code bridge and the agent picker all read it. A command answering "is this agent here" differently from the picker in the same build is exactly the drift the same proposal objects to one decision further down. It now asks the detection that exists; if the probe is the wrong mechanism, that is a change to `agent-detection.ts` and every surface reading it. `two-steps-to-a-run` no longer points at the withdrawn change; it points at the named configurations that exist. openspec validate --strict --changes: 6 passed, 0 failed. Co-Authored-By: Claude Opus 5 (1M context) --- .../design.md | 31 ++++-- .../proposal.md | 10 +- .../specs/ci-cli/spec.md | 18 +-- .../tasks.md | 11 +- .../.openspec.yaml | 5 - .../design.md | 80 -------------- .../proposal.md | 60 ---------- .../specs/agentic-harness/spec.md | 61 ----------- .../tasks.md | 103 ------------------ .../changes/two-steps-to-a-run/proposal.md | 11 +- 10 files changed, 50 insertions(+), 340 deletions(-) delete mode 100644 openspec/changes/a-preset-instead-of-twelve-fields/.openspec.yaml delete mode 100644 openspec/changes/a-preset-instead-of-twelve-fields/design.md delete mode 100644 openspec/changes/a-preset-instead-of-twelve-fields/proposal.md delete mode 100644 openspec/changes/a-preset-instead-of-twelve-fields/specs/agentic-harness/spec.md delete mode 100644 openspec/changes/a-preset-instead-of-twelve-fields/tasks.md 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 index 4fbdcf4..8fc95e4 100644 --- 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 @@ -2,19 +2,26 @@ ## Decisions -**A binary is checked by resolving it, never by running it.** -`packages/core/src/environment-report.ts` looks each agent's executable -up on the PATH and reports found or not found. It does not spawn -`claude --version`. +**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. -Rejected: probing versions by execution. The allowlist in -`security.ts` exists so that this product executes exactly the -invocations it was configured to execute; a version probe is an -invocation nobody allowlisted, run against a binary whose name came from -a registry, at a moment when the user asked a question rather than -started a run. The information gained — a version string — answers no -question this report asks, because no capability here is gated on an -agent's version. +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 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 index 2926687..b3879fd 100644 --- 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 @@ -46,9 +46,10 @@ 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. -Running an agent to see whether it works. A binary's presence on the -PATH is checked by resolving it, never by executing it — see -`design.md`. +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 @@ -56,4 +57,5 @@ 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 — only whether a named binary resolves. +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 index 3e6b485..0ca9f72 100644 --- 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 @@ -30,19 +30,23 @@ broken one. - **THEN** the holder is reported, and the report does not treat it as a failure -### Requirement: An agent's presence is checked without invoking it +### Requirement: An agent's presence is answered by the detection that already exists -The presence of an agent's executable SHALL be determined by resolving -it, and SHALL NOT be determined by executing it. +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. -Executing a binary to ask its version is an invocation no allowlist -covers, performed because somebody asked a question rather than started -a run. No behaviour here depends on an agent's version. +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 the executable as present without running it +- **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 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 index 2b82c76..3764ddd 100644 --- 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 @@ -13,10 +13,13 @@ only be asked by starting a run and being refused. - [ ] 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`: its executable resolved on the - PATH or not. Resolve it; do not execute it. A version probe is an - invocation nobody allowlisted — state this prohibition in the code - comment, not only here. +- [ ] 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 diff --git a/openspec/changes/a-preset-instead-of-twelve-fields/.openspec.yaml b/openspec/changes/a-preset-instead-of-twelve-fields/.openspec.yaml deleted file mode 100644 index 30c7ba6..0000000 --- a/openspec/changes/a-preset-instead-of-twelve-fields/.openspec.yaml +++ /dev/null @@ -1,5 +0,0 @@ -schema: spec-driven -created: 2026-09-12 -follows: - - setup-offers-only-what-applies - - an-autonomy-level-says-what-it-does diff --git a/openspec/changes/a-preset-instead-of-twelve-fields/design.md b/openspec/changes/a-preset-instead-of-twelve-fields/design.md deleted file mode 100644 index 1b3b7b8..0000000 --- a/openspec/changes/a-preset-instead-of-twelve-fields/design.md +++ /dev/null @@ -1,80 +0,0 @@ -# Design - -## Decisions - -**A preset is a value, not a mode.** `HARNESS_PRESETS` in -`packages/core/src/harness-presets.ts` is a list of -`{ id, title, intention, scope, values }`, where `values` is a partial -`HarnessConfig`. Applying one merges `values` into the target file and -writes it. Nothing records that a preset was used, and nothing reads a -preset at run time. - -Rejected: a `preset` key in `harness.json` that the resolver expands. It -would be a third configuration layer over the two that exist, and every -question the reader has — what is actually set, why did this stage get -that agent — would need the preset table to answer. It also creates a -class of bug this repository has already had twice: a per-change file -that overrides part of something and silently inherits the rest -(`stage-override-keeps-the-rest`, `a-stage-override-keeps-its-custom-agent`). - -**Applying says what it will change, before it changes it.** Applying a -preset to a file that already has values produces a diff of the keys it -would overwrite, and is confirmed against that. A preset applied to an -empty file is the common case and shows the same summary. - -Rejected: applying only to keys that are unset. A preset is an -intention, and one that silently skipped the fields a person had already -set would produce a configuration matching no preset and no intention. - -**Scope is part of the preset.** Some values a global file may not carry -at all (`autonomyLevel: "autonomous"`, `reviewGate.mode: -"agent-sufficient"`, `checkpoints.requireConfirmationBetweenSteps: -false`, and the two keys global files may never set). A preset declares -whether it applies to the workspace default, a single change, or either, -and the surfaces offer only what applies — the rule -`setup-offers-only-what-applies` already established. - -**Three presets, and the constraint that there are few.** "With me -watching" (assisted, confirmation between stages, human review gate), -"Unattended, careful" (semi-autonomous, no confirmation, human review -gate, a budget), "Unattended, all the way" (autonomous, no confirmation, -agent-sufficient review gate, a budget, per-change only — it is what -lets the `git` stage push and merge). A fourth is a proposal with its -own justification: a list of presets long enough to need reading is the -problem it was meant to solve. - -**Written by core, offered by the hosts, applied by the CLI too.** -`applyHarnessPreset({ workspaceRoot, changeName?, presetId })` in core -does the merge and the write; `webui` offers the list where the settings -already are; `openspec-ui-cli harness preset ` applies one from a -terminal. No host computes a preset's values. - -## Non-Goals - -- Any new configuration key, at either file. -- Changing what any existing key does, or which keys a UI can edit. -- Presets applied automatically, at setup or anywhere else. -- Presets for settings outside the harness. - -## Risks / Trade-offs - -**A preset hides what it set.** Mitigated by writing plain JSON into the -file the reader already knows about, by showing the diff before writing, -and by the presets being documented in `HARNESS.md` with their exact -values. The failure mode avoided is the resolver-level preset, where the -file says one word and the behaviour comes from a table. - -**Three presets will not fit everybody.** That is intended: the fields -stay, directly beneath, and a preset is a starting point somebody then -edits. What would not be acceptable is a preset that is nearly right and -cannot be edited. - -**A preset can drift from what the keys mean.** `HARNESS.md` documents -the values, and a test asserts each preset resolves through -`resolveHarnessConfig` to exactly the configuration its documentation -claims — so a change to a default that silently alters a preset fails. - -**Protocol impact: none.** No command or event is added or changed. -Writing a configuration file is something both hosts already do through -the existing settings path; this change adds a value to write, not a way -to write it. diff --git a/openspec/changes/a-preset-instead-of-twelve-fields/proposal.md b/openspec/changes/a-preset-instead-of-twelve-fields/proposal.md deleted file mode 100644 index ed03d60..0000000 --- a/openspec/changes/a-preset-instead-of-twelve-fields/proposal.md +++ /dev/null @@ -1,60 +0,0 @@ -# A preset instead of twelve fields - -## Why - -`HARNESS.md` documents nine top-level keys, four stage entries with four -fields each, five settings a global file may not set, and five no UI can -edit. Configuring a change to run unattended means getting -`autonomyLevel`, `checkpoints.requireConfirmationBetweenSteps`, -`reviewGate.mode`, a budget and four `stepAgents` entries consistent -with one another — where two of those five are refused in the global -file and only accepted per change. - -Raised in review on 2026-09-12: the settings keep growing and the point -is to make a person's life simpler. The repository has been answering -this one field at a time — `setup-offers-only-what-applies` removed -choices that could not apply, `an-autonomy-level-says-what-it-does` -renamed one so it read as what it did, `a-setting-reads-as-a-setting` -did the same elsewhere. Each was right and none of them changes the -shape of the problem: the unit a person configures is a whole intention -("run this without me", "be careful with this one"), and the unit the -product offers is a field. - -## Capabilities - -### New - -- Named presets — each a documented set of values for keys that already - exist, with the intention it serves stated in the same words a person - would use. -- Applying one writes the ordinary JSON those keys live in, so the - result is a file that can be read, reviewed, hand-edited and - committed. -- Both hosts offer them where the harness is configured, and the CLI - can apply one. - -### Modified - -- The harness settings surfaces lead with the presets and keep every - field beneath them, so the fields remain the way anything specific is - reached. - -## Out of scope - -A new configuration layer. A preset is not a key in `harness.json`, not -a mode the resolver knows about, and not something a running chain -consults. It writes the same JSON a person would have written and then -has no further existence — see `design.md` for why the alternative was -rejected. - -New settings. A preset that needed a key the harness does not have would -be a product change wearing a preset's clothes; each preset is expressed -entirely in keys that exist today, and a preset that cannot be is -reported as a finding. - -Deciding a preset for the user. Nothing applies one on its own, and none -is applied to an existing configuration without saying which fields it -would change. - -Presets for anything but the harness. The same complaint applies to -other settings surfaces; whether it generalizes is not decided here. diff --git a/openspec/changes/a-preset-instead-of-twelve-fields/specs/agentic-harness/spec.md b/openspec/changes/a-preset-instead-of-twelve-fields/specs/agentic-harness/spec.md deleted file mode 100644 index 44c786c..0000000 --- a/openspec/changes/a-preset-instead-of-twelve-fields/specs/agentic-harness/spec.md +++ /dev/null @@ -1,61 +0,0 @@ -## ADDED Requirements - -### Requirement: An intention can be configured in one action - -A person SHALL be able to configure the harness by naming the intention -they have — run this with me watching, run this without me — rather than -by setting each field that intention implies. - -Such a named set SHALL be expressed entirely in configuration keys that -already exist, and applying it SHALL write those keys as ordinary values -into the file they already live in. It SHALL NOT introduce a -configuration layer of its own: nothing SHALL record which named set was -applied, and nothing SHALL consult one while a chain runs. - -A file written this way is a file a person can read, review, hand-edit -and commit. A file that named a preset and took its behaviour from a -table elsewhere would answer "what is set here" only by reference to -that table. - -Applying one to a file that already carries values SHALL report the keys -it will overwrite, and their current values, before writing. - -A named set SHALL declare which files it may be applied to, and SHALL be -offered only where it applies — not offered and refused. - -#### Scenario: Configuring an unconfigured change - -- **WHEN** a person applies a named set to a change with no - configuration of its own -- **THEN** the change's configuration file carries that set's values as - ordinary keys, and resolves exactly as those keys resolve when written - by hand - -#### Scenario: Applying over existing values - -- **WHEN** a named set is applied to a file that already sets some of - the same keys -- **THEN** the keys that would be overwritten, and their current values, - are reported before anything is written - -#### Scenario: A set whose values a global file may not carry - -- **WHEN** a named set includes a value that only a per-change file may - set -- **THEN** it is not offered for the workspace-wide file at all, and - applying it there is refused with the same error that key's validator - already raises - -### Requirement: Fields remain the way anything specific is reached - -Offering named sets SHALL NOT remove or hide the individual fields. - -A named set is a starting point. A person whose intention is nearly one -of the offered sets SHALL be able to apply it and then edit any field it -set, in the same surface, without undoing the rest. - -#### Scenario: Editing after applying - -- **WHEN** a person applies a named set and then changes one field -- **THEN** that field is changed and every other value the set wrote is - left as it was diff --git a/openspec/changes/a-preset-instead-of-twelve-fields/tasks.md b/openspec/changes/a-preset-instead-of-twelve-fields/tasks.md deleted file mode 100644 index 841572b..0000000 --- a/openspec/changes/a-preset-instead-of-twelve-fields/tasks.md +++ /dev/null @@ -1,103 +0,0 @@ -The unit a person configures is an intention. The unit the product -offers is a field, and there are more of them every month. - -## 1. The presets - -- [ ] 1.1 `packages/core/src/harness-presets.ts` exports - `HARNESS_PRESETS: readonly HarnessPreset[]`, each - `{ id, title, intention, scope, values }` where `values` is a partial - `HarnessConfig` and `scope` is `"global" | "change" | "either"`. -- [ ] 1.2 Preset `watched`: `autonomyLevel: "assisted"`, - `checkpoints.requireConfirmationBetweenSteps: true`, - `reviewGate.mode: "human-required"`. Scope `either`. -- [ ] 1.3 Preset `unattended-careful`: `autonomyLevel: - "semi-autonomous"`, `checkpoints.requireConfirmationBetweenSteps: - false`, `reviewGate.mode: "human-required"`, a chain `budget`. Scope - `change` — two of those three may not appear in a global file. -- [ ] 1.4 Preset `unattended-all-the-way`: `autonomyLevel: - "autonomous"`, `checkpoints.requireConfirmationBetweenSteps: false`, - `reviewGate.mode: "agent-sufficient"`, a chain `budget`. Scope - `change`. State in the preset's `intention` that this is what lets the - `git` stage push, open a pull request and merge. -- [ ] 1.5 No preset introduces a key that - `packages/core/src/harness-config.ts` does not already accept. A - preset needing a new key is reported as a finding in this file rather - than added. -- [ ] 1.6 `packages/core/src/harness-presets.test.ts`: every preset's - `values` passes the config validator for its declared scope, and a - preset whose scope is `either` is accepted in a global file — the - assertion that catches a preset written for the wrong file. - -## 2. Applying one - -- [ ] 2.1 `applyHarnessPreset({ workspaceRoot, changeName?, presetId })` - in `packages/core/src/harness-presets.ts` merges `values` over the - target file's existing object and writes the ordinary JSON. It writes - no marker naming the preset: there is no preset layer to read back. -- [ ] 2.2 It returns the keys it would overwrite and their current - values before writing, so a caller can show what changes. A caller - that does not ask still gets the write; the summary is a return value, - not a prompt inside core. -- [ ] 2.3 Applying a preset to a target whose scope forbids it fails - with the same error class the config validator already raises for that - key, not a new one. -- [ ] 2.4 `packages/core/src/harness-presets.test.ts` covers: applying - to an empty file, applying over existing values (the overwritten keys - are reported), applying a `change`-scoped preset to the global file - (refused), and that the file after applying resolves through - `resolveHarnessConfig` to exactly the configuration the preset - documents. - -## 3. Where they are offered - -- [ ] 3.1 `packages/webui/src/components/HarnessSettingsView.tsx` shows - the presets applicable to the section being edited, above the fields, - each with its `title` and `intention`. The fields stay exactly where - they are: a preset is a starting point, not a replacement. -- [ ] 3.2 Choosing one shows the keys it will overwrite and their - current values, and writes only on confirmation. -- [ ] 3.3 A preset whose `scope` excludes the section is not shown — - not shown disabled. `setup-offers-only-what-applies` decided this for - the setup flow and it holds here. -- [ ] 3.4 `packages/webui/src/components/HarnessSettingsView.test.tsx`: - the global section offers only `either`-scoped presets; the per-change - section offers all three; confirming writes the merged object; the - overwrite summary lists exactly the keys that had values. - -## 4. From a terminal - -- [ ] 4.1 `openspec-ui-cli harness preset [--change ] [--cwd - ]` in `packages/cli/src/harness-preset-command.ts`, wired in - `packages/cli/src/main.ts` with its own `USAGE` entry. -- [ ] 4.2 It prints the keys it will overwrite and applies the preset; - `--dry-run` prints them and writes nothing. Exit `0` applied, `2` - refused (unknown preset, wrong scope, unwritable file). -- [ ] 4.3 `packages/cli/src/harness-preset-command.test.ts`: applying - exits 0 and writes, `--dry-run` exits 0 and writes nothing, an unknown - id exits 2 and names the ids that exist. - -## 5. Documentation - -- [ ] 5.1 `HARNESS.md` gains a presets section listing each preset's - exact values and its scope, adjacent to the key reference rather than - replacing any of it. -- [ ] 5.2 `docs/how-to/run-a-change-unattended.md`, if it exists by then, - names the preset as its first step. Do not create that file here — it - belongs to `two-steps-to-a-run`. - -## 6. Verification - -- [ ] 6.1 This change validates strictly. `check(validate-change)` -- [ ] 6.2 `npm run verify` unpiped, after the last edit, with everything - staged. Record the run and the per-package test counts. -- [ ] 6.3 A changeset exists: `core`, `webui` and `cli` minor. - `check(changeset-present)` -- [ ] 6.4 **Delegated to `claude-cli`**: apply `unattended-careful` to a - scratch change from the terminal, quote the file before and after and - the exit code, then run `openspec-ui-cli run` against that change far - enough to show it does not pause for a confirmation. Evidence: both - file contents and the run's first two stage lines. -- [ ] 6.5 **Human-only**: with the harness settings open on a repository - that has never been configured, choosing a preset is visibly less work - than filling the fields, and the person can still tell what was set. - Whether the presets removed work or only moved it is a judgement. diff --git a/openspec/changes/two-steps-to-a-run/proposal.md b/openspec/changes/two-steps-to-a-run/proposal.md index b1252e0..4ee83d3 100644 --- a/openspec/changes/two-steps-to-a-run/proposal.md +++ b/openspec/changes/two-steps-to-a-run/proposal.md @@ -68,7 +68,10 @@ 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. -Presets. "Give me a sensible configuration in one action" is -`a-preset-instead-of-twelve-fields`, a different answer to the same -complaint, and the two are deliberately separate: a how-to explains what -a person is choosing, a preset chooses for them. +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.