mirror of
https://github.com/Fission-AI/OpenSpec.git
synced 2026-10-14 05:19:53 +08:00
@fission-ai/openspec@1.13.2
34
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
072de6bc39 |
fix(verify): do not report unverified dimensions as passing (#1732)
* fix(verify): do not report unverified dimensions as passing Step 5 gates task and spec coverage on `contextFiles.tasks` and `contextFiles.specs`. `contextFiles` is an artifact-id map and artifact ids come from the active schema, so on a schema that defines neither, both branches are no-ops: nothing is checked, no issues are raised, and step 8 concludes "All checks passed. Ready for archive." The Graceful Degradation guardrail already asks the agent to note skipped checks, but nothing stopped the all-clear verdict. Mark an unchecked dimension `Not verified` in the scorecard and require the final assessment to name it. * fix(verify): map skipped checks to report outcomes * fix(verify): retain no-task and skipped-check context * fix(verify): harden evidence gaps and final assessments * fix(verify): preserve optional workflows and task artifact fallback * fix(apply): resolve tracked task globs by schema path * fix(verify): preserve unavailable task evidence * fix(verify): distinguish untracked tasks from missing evidence * docs(apply): document tracked globs and JSON evidence * test(parity): regenerate hashes after merging #1940 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(parity): restore the #1837 regression tests dropped in the merge The earlier conflict resolution took our whole side of the parity file, which discarded the two threshold tests main gained in #1940. Take main's file verbatim and regenerate the hashes instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(parity): regenerate hashes after merging #1955 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(parity): regenerate hashes after merging #1795 and #1926 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(parity): regenerate hashes after merging #1731 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Clay Good <hi@claygood.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
72fbe4c904 |
fix(update): close the dead end for partially populated glob artifacts (#1733)
* fix(update): close the dead end for partially populated glob artifacts `artifactOutputExists` returns true as soon as a single file matches a glob `generates`, so a `specs/**/*.md` artifact is `done` after the first delta spec. `/opsx:continue` selects only `ready` artifacts and never revisits it. The update templates forbid creating new files under a glob artifact and point the user to `/opsx:continue` instead, which cannot act on it. A capability spec the coherence review finds missing therefore has no supported way to be created. Allow update to write that file: concrete path only, rules fetched from `openspec instructions`, and the same confirm-before-write rule as every other revision. Creating an artifact that has no files at all stays out of scope - that one is genuinely `/opsx:continue`'s job. * fix(update): tighten glob gap write guardrails * fix(update): narrow continue handoff to empty artifacts * fix(update): harden glob gap creation guidance * fix(update): preserve creation safeguards for glob companions * fix(update): integrate glob guidance with current workflows * docs(skills): note update's glob companion-file exception docs-lab/reference/skills.md said update creates nothing new, which this change makes false for glob artifacts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(parity): restore the #1837 regression tests dropped in the merge The earlier conflict resolution took our whole side of the parity file, which discarded the two threshold tests main gained in #1940. Take main's file verbatim and regenerate the hashes instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(explore-docs): allow the update guidance line that says "no files yet" main's #1833 guard flags any docs line containing "no files". The new /opsx:update guidance describes which artifacts update leaves to continue, not what explore writes, so allow that exact line. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Clay Good <hi@claygood.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ed5d386a55 |
fix(tasks): keep tests and docs inside each task group (#1955)
* fix(tasks): keep tests and docs inside each task group Closes #1952 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(tasks): carry the per-group rule to onboarding and the docs Teach the same rule where a user first meets task groups, and stop the published schema reference from quoting instruction text that drifted two revisions behind schema.yaml. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(tasks): scope the per-group rule to the work each group does The MUST read as an absolute per-group requirement while the worked example's Setup group carries neither tests nor docs. Scope the rule to what a group's work calls for and name the scaffolding case explicitly, so the rule and its example agree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
fd56e12c9e |
fix(artifact-graph): support brace expansion and extglob output patterns (#1885)
* fix(artifact-graph): support brace expansion and extglob output patterns (#1854) * fix(artifact-graph): preserve literal output filenames * fix(artifact-graph): confine expanded brace patterns * test(artifact-graph): cover later brace ranges and specify glob contract * test(artifact-graph): resolve brace globs with Windows separators --------- Co-authored-by: 胥寅 <xuuyin@dingtalk.com> Co-authored-by: Clay Good <hi@claygood.com> |
||
|
|
fe429a13dc |
fix(kilocode): generate commands in canonical directory (#1938)
* fix(kilocode): generate commands in canonical directory * fix(kilocode): preserve unrelated legacy workflows |
||
|
|
5b55263775 |
fix(init): clarify Codex desktop skill usage (#1744)
* fix(init): clarify Codex desktop skill usage * fix(init): clarify Codex desktop skill usage * test(init): cover Codex hints across delivery modes --------- Co-authored-by: Clay Good <hi@claygood.com> |
||
|
|
91f2925c63 |
docs(config): document opener settings (#1945)
* docs(config): document opener settings * test(config): harden opener argument contract |
||
|
|
416599a85e |
docs(copilot): document workflow rediscovery (#1943)
* docs(copilot): document workflow rediscovery * test(copilot): pair discovery claims * docs(copilot): harden workflow recovery guidance |
||
|
|
bae58cf614 | docs: fix Docslab links to unfinished pages (#1903) | ||
|
|
3312af4799 |
fix(templates): open generated artifacts with a top-level heading (#1777)
* fix(templates): open generated artifacts with a top-level heading
Generated proposal.md, design.md, spec.md and tasks.md started on a
section header, so every OpenSpec artifact tripped markdownlint MD041
("first line in a file should be a top-level heading") in editors that
run it. The files were also, literally, documents without a title.
Each packaged template now opens with `# Proposal`, `# Design`,
`# Spec Delta` or `# Tasks` followed by a blank line, and
`openspec schema init` scaffolds custom templates the same way. The
schema's own examples and the customization docs match.
Titles are inert to every reader downstream: the parsers anchor on `##`
and `###`, and archive builds a new main spec from the delta's sections,
so the main spec keeps its own generated `# <capability> Specification`
and only that one.
Closes #1138
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(templates): compare template headings on normalized line endings
The Windows runner checks out CRLF, so splitting the template on "\n"
left the blank second line as "\r" and the new guards failed there while
passing everywhere else. Normalize before splitting; verified against a
CRLF copy of the templates locally.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(templates): pin each template to its own title
Review feedback: the guards accepted any top-level heading, so a wrong
title would have passed, and the archive check counted `# ` lines only,
so a demoted `## Spec Delta` would have slipped through. Assert the exact
heading per artifact, the blank line under it in both guards, and that no
heading of any level named "Spec Delta" survives archive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(workflows): teach the artifact titles everywhere the shape is shown
The templates were only half the story. The onboarding walkthrough drafts
each artifact in the conversation and then saves what it drafted, so its
previews would have written untitled proposal.md, spec.md, design.md and
tasks.md whatever the template said. The sync workflow's delta format
reference had the same gap, sitting directly beneath a main-spec
reference that does carry a title. Both now show the template's title,
and `docs/opsx.md` no longer documents a `template` value the CLI never
returned.
Guards added:
- The template guard now enumerates every artifact of every packaged
schema from schema.yaml rather than a hardcoded list of four, and the
exact-title table must name every artifact the schema declares.
- A drift guard reads the titles out of the packaged templates and
requires the onboard and sync surfaces to show those same titles, so
guidance and template cannot part ways again.
- Parser tests pin the claim the fix rests on: a title is inert, and a
spec or proposal parses identically with and without one.
Regenerated the skill mirrors and parity hashes for the two workflows
touched; no other hash moved.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: move the artifact-title update to the canonical docs-lab tree
The live site builds from docs-lab/, so the exact-format page there is the
one readers see. It still showed all four templates opening on a section
header and described the delta as starting with `## Purpose`.
- reference/schemas/spec-driven/index.md: each template block now matches
the shipped template byte for byte, and the quoted spec/tasks
instructions match schema.yaml again.
- customize/schemas.md: one line telling fork authors to keep the `#`
title on the first line.
Reverts the edits to docs/customization.md and docs/opsx.md: that tree is
no longer published and docs-lab/README.md keeps it as source material
only, so editing it would leave two versions of the same fact.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(show): keep the change id as title for a bare `# Proposal` heading
The packaged proposal template now opens with `# Proposal`, which
`extractTitle` read as the change's title, so `show --json` and
`change list --json/--long` titled every templated change "Proposal"
instead of its id. Treat that bare heading as untitled.
Also re-quote the proposal and specs instructions in the canonical
docs-lab schema page after #1700 changed schema.yaml.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
9827762d2d |
fix(skills): stop workflows from adopting a project that never ran init (#1787)
* fix(skills): stop workflows from adopting a project that never ran init Generated skills and commands are installed once per machine and offered in every repository the agent opens, including ones with no OpenSpec at all. Nothing stopped the workflow there: root resolution falls back to an implicit root at the current directory, so `openspec new change` quietly creates `openspec/` in whatever repo the agent happened to be standing in (#1645). Two changes, both in the generated instructions: - Every workflow now carries a shared project check. Before the first step that writes, the agent reads `root.source` from `openspec status --json`; `implicit` (or a `No OpenSpec root found` error) means the project is not set up, and the agent stops and asks the user whether to run `openspec init`, target a store, or drop OpenSpec for that request. It may not initialize the project on its own or let a command create the root as a side effect. - Every deployed skill description now names OpenSpec. Hosts pick skills by description, and "Enter explore mode - a thinking partner..." reads as a generic offer in a repository that has never heard of OpenSpec. Closes #1645 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(changeset): note the uninitialized-project guard Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(skills): let onboarding run init once the user asks for it The guard read as an absolute ban on `openspec init`, which contradicts the option it offers one sentence earlier and the onboard workflow's job. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(troubleshooting): explain an OpenSpec workflow starting in an unset-up project Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(skills): check the root with a command that never fabricates one `openspec status --json` demands --change once a project has changes, so the guard's own check could fail in exactly the projects it should wave through. `openspec list --json` answers in one shape everywhere: a root object when the project is set up, `root: null` both when nothing is set up and when only stores are registered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(skills): pin the guard against every write, not the first fence CodeRabbit's point: checking only the first ```bash fence would miss a write outside a fence. Assert instead that nothing preceding the guard runs a command or writes, and that the guard sits directly under the store-selection guidance - both fail when the guard is moved down a workflow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(new-change): say when the command had to create the root itself The generated workflows now check for a root before writing, but the guard is instructions - an agent that ignores it, or a human running the CLI directly, still turned an unset-up directory into an OpenSpec project without a word. Creating the root stays zero-config; it is no longer silent. Human output only: --json is unchanged, and `root.source` already carried the same fact for programmatic callers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(skills): tell agents the root check's non-zero exit is the answer `openspec list --json` exits 1 when there is no root. An agent that reads that as a broken CLI is one step from hand-creating `openspec/` instead, which is the failure the guard exists to prevent. Also drops a vacuous assertion: the notice test now checks that the note names the directory it created and that the change really landed there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * style(skills): read the guard back and untangle its two 'in that case' clauses Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(skills): carry the store flag into the root check; pin the notice path exactly CodeRabbit, both valid: - With a store selected the store IS the root, so the check has to run as `openspec list --json --store <id>`. The store-selection paragraph above already says to append the flag to every command it lists, but leaving it implicit here invited a check against the wrong directory. - The notice assertion matched any `openspec/` suffix. It now pins the exact rendered path, and a new case runs the command from a subdirectory to show the note names the directory actually adopted (and that the repo above it is left alone). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: move the no-root contract to the canonical docs-lab pages alfred-openspec on #1787: docs-lab/README.md makes docs-lab/ canonical and the old docs/ tree legacy, and the canonical pages were stale in the two places the review named. - docs-lab/reference/cli.md, 'openspec new': documents the implicit-root notice after the 'Next:' line, with the exact output the CLI prints, that it goes to stdout and never appears with --json, and that JSON carries the same fact as root.source: implicit. Verified against a real run in an empty directory with an isolated HOME. - docs-lab/reference/skills.md: states the shared response and stop behavior once, above the index table, since it now holds for every skill: confirm the resolved root before the first write, stop when there is none, offer init, a store, or dropping OpenSpec, wait for the answer, never create openspec/ on its own. Drops the legacy docs/troubleshooting.md addition. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(skills): make the no-root answer depend on how the workflow was reached alfred-openspec's product call on #1787. One answer could not serve both arrivals: #1645 asks the workflow to get out of the way ('it can go through the normal general propose not the openspec'), while a user who typed the skill's name is owed an answer about OpenSpec. The guard now branches after the same `openspec list --json` check: - Auto-selected: the model picked this workflow without the user naming OpenSpec, naming the skill, or running its command. Drop OpenSpec and answer the request normally, with no setup question and no mention of OpenSpec. - Explicit OpenSpec request: stop before writing and ask whether to run `openspec init`, target a store, or continue without OpenSpec, then wait. Neither branch may create the root as a side effect, stated once for both. One text serves both surfaces rather than a command-only variant, because apply-change and onboard render a single body into the skill and the command alike; a command-only constant would mean threading a surface flag through bodies that deliberately have none (#1515). The bullets scope themselves instead, and a slash command is an explicit invocation, so only the ask branch can apply there. A test pins that branch reaching every generated opsx command. Four regressions: the auto-selected branch (asserting it does not mention `openspec init` or `--store`), the explicit branch, the explicit branch's presence in every command file, and the shared no-side-effect rule. docs-lab/reference/skills.md said every no-root invocation asks. It now carries the same two branches as scan anchors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(skills): keep the root guard off store-only projects and fix its docs A store-only project whose `store:` line names a store this machine has not registered reports `"root": null` from `openspec list --json`, so the guard read a real OpenSpec project as uninitialized. The guard now checks for the `Declared in` status message first and shows the store error instead. A stale global defaultStore reports the same codes in unrelated repositories, which is why the message prefix, not the code, decides. Propose's context step from #1657 offered `openspec init` on `no_openspec_root` regardless of how the workflow was reached. It now defers to the project check, so an auto-selected propose skill stays silent. The changeset names both no-root branches, and the `new change --json` docs separate the initialized example from the verified `implicit` one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(skills): keep the root guard off projects with a malformed store line A config-only project whose `store:` line is malformed reports `"root": null` with an `Invalid store declaration in` message, not `Declared in`, so the project check read it as never initialized and would drop OpenSpec or offer `openspec init` there. The guard now names both prefixes, and a root-selection test pins that every declaration failure starts with one of them while a stale global defaultStore starts with neither. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(skills): check guard ordering in the skill body, not its frontmatter A skill's YAML frontmatter is metadata a host reads to choose the skill, not instructions the agent runs, so a description that quotes a command name must not trip the ordering check. Scope the scan to the text after the closing frontmatter delimiter; a command injected into the body ahead of the guard still fails. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
11a9691524 |
fix(tasks): count unrecognised checkbox markers as not done (#1773)
* fix(tasks): count unrecognised checkbox markers as not done A checkbox marker the task parser did not recognise was dropped from progress entirely: it counted toward neither the numerator nor the denominator. A tasks.md whose remaining work was written `- [~] ...` therefore reported `✓ Complete` in `openspec list`/`status`, and `openspec archive` raised no incomplete-task warning for it. Marking open items with such a marker *shrank* the denominator instead of leaving them counted as not-done. Widen the marker to any single non-`]` character and keep `x`/`X` as the only done state, so an unrecognised marker reads as not-done - the fail-safe direction the pattern's own docblock argues for. OpenSpec adopts no new marker semantics; it just stops losing the line. Closes #1761 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(tasks): close the remaining silent-loss holes in checkbox parsing Hardening pass on the #1761 fix. Parser: an empty `[]` and a padded `[ x]` were dropped exactly the way `[~]` was - checkbox-like lines counting toward neither the numerator nor the denominator, so archive stopped warning about them. The marker is now "at most one non-whitespace token", which covers all three. Multi-character brackets stay unmatched on purpose: widening to `[^\]]*` would match `- [Some doc](./doc.md)`, whose `]` is followed by `(` rather than a space, and turn every Markdown link list into phantom unfinished work. The docblock states that boundary and a test pins it. Guidance: archive, bulk-archive and verify told agents to count `- [ ]` vs `- [x]` by hand, which reproduced the same bug one layer up - an agent following it saw no incomplete tasks in a `[~]` file even with the CLI fixed. All six bodies (skill + command per workflow) now state the rule the parser implements; skills/ mirror and parity hashes regenerated. Coverage now spans every consumer of the shared counter: `openspec list`, archive's gate, the apply task list, validate's task-numbering check and the parser itself, including the reported 42-done/17-deferred ratio. Reverting only src/utils/task-progress.ts fails 7 of them. Verified empirically: the widened pattern newly matches 0 lines across all 1090 .md/.ts files in the repo, templates and docs included. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(tasks): state the padded-tick rule in workflow guidance too CodeRabbit review, verified and valid: the new guidance named only exact `[x]`/`[X]` as complete, while the parser also counts `[ x]`, `[x ]` and `[ x ]`. An agent hand-counting by that wording would have reported a padded tick as unfinished work and disagreed with `openspec list` - the same guidance/CLI split this PR set out to close. All six bodies now phrase it as the parser implements it: complete means the box holds only `x`/`X`, spacing inside the brackets ignored; every other marker, including an empty box, is incomplete. skills/ mirror and parity hashes regenerated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(tasks): keep one-character link bullets out of the task count alfred-openspec on #1773: the widened marker class let a one-character Markdown link label parse as a checkbox, so `- [A](https://example.com)` and `- [1](./one)` read as unfinished tasks and made progress and archive report phantom work. The multi-character guard did not cover them: a one-character label is one token. The closing bracket may now not be followed by `(` or `[`, the only two characters that continue Markdown link syntax. Nothing that used to count is lost: a checkbox is followed by its description or by end of line, and `- [x]done` still parses. Regressions cover both link forms, the reference form, and a link inside a real task description. Also updates the canonical contract, which said tasks not written `- [ ]` are not tracked while this change deliberately tracks `[~]`, `[-]`, `[]` and a padded `x`. Fixed in schemas/spec-driven/schema.yaml and in the docs-lab page that quotes it, so the quote stays faithful. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: say that a done checkbox is case-insensitive CodeRabbit on #1773: the parser lowercases the marker before comparing it with `x`, so `- [X]` is done, but the contract I added said only `- [x]` counts. Corrected in both copies, which are the same text. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(tasks): keep empty boxes followed by link syntax in the task count The one-character link guard also dropped `- [ ](...)` and `- [ ][...]`, which the strict pre-#1761 pattern counted as unfinished tasks. A line the parser drops is one archive stops warning about, so a whitespace-only box now bypasses the guard. The canonical tasks instruction also names `[-]` and the padded `x` rule, in schema.yaml and docs-lab in lockstep. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(changeset): drop em dashes and state the padded-x done rule Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
605d9e7a2b |
fix(config): leave an unparseable global config untouched (#1876)
* fix(config): leave an unparseable global config untouched A typo in config.json made getGlobalConfig() fall back to defaults, which telemetry read as consent: any command, even a read-only list, minted a new anonymous id and wrote it over the whole file, dropping a telemetry.enabled false opt-out and every other setting. config set, unset and profile likewise saved the defaults over it. saveGlobalConfig() and telemetry's writeConfig() now refuse to overwrite a file they cannot parse, telemetry and the update check treat such a file as opted out, and config set, unset and profile exit with an error pointing to config edit. config reset --all can still replace the file, and the existing warning is unchanged. * fix(config): treat a non-object global config as unreadable Valid JSON that is not an object (null, an array, a string) also makes getGlobalConfig() fall back to defaults, silently, so `config set` still saved those defaults over the user's file. isGlobalConfigUnreadable() now reports such a file as unreadable, which keeps telemetry off and routes every save through the same refusal as a parse failure. This matches how completion-tip already treats a non-object config. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(config): document the refusal to rewrite an unparseable config docs-lab/reference/cli.md said `config unset` always exits 0. With an unparseable global config, `config set`, `config unset` and `config profile` now exit 1 and leave the file unchanged; say so, show the message and the two fixes, and note telemetry and the update check stay off until it is fixed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(config): warn about an unparseable global config once per command Telemetry, the update check and the command each read the global config, and now that none of them rewrites the broken file, the "Invalid JSON" warning printed two or three times per command. Warn once per path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(migration): skip profile migration for a config that is not a JSON object A global config holding [] reached saveGlobalConfig, which now refuses it, so init and update failed. null already crashed on a property read. migrateIfNeeded now skips such a file, as it does for a parse failure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(telemetry): refuse to write over a non-object global config The telemetry writer had its own notion of an unreadable config: only a JSON parse failure counted. Valid JSON that is not an object slipped through, so updateTelemetryConfig() merged into it and replaced the file -- an array, a number or a boolean became a bare telemetry object, a string spread into numeric character keys, and null threw a TypeError instead of the actionable refusal every other writer reports. Funnel both notions through one predicate: isConfigRootObject() in core/global-config.ts now backs isGlobalConfigUnreadable() and the telemetry reader, so every shape the global guard rejects is classified invalid on read and refused on write. Both writers report the same one-line message via unreadableGlobalConfigMessage(). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(config): read a non-object global config as plain defaults getGlobalConfig() spread the parsed root into its result before the unreadable predicate was consulted, so the shape of the root leaked to every caller: a config of "abc" returned defaults plus the numeric character keys 0, 1 and 2. Check isConfigRootObject() right after parsing and answer with plain defaults, as for a file that did not parse at all. Reported by CodeRabbit as an outside-the-diff finding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(config): stop `config list` crashing on a null config root `config list` re-reads the raw file to mark each value explicit or default, and assigned JSON.parse() straight to rawConfig. A root of `null` then crashed the command with a TypeError stack trace, the one failure mode this PR is meant to remove, and it did so on a read-only command. Normalize a non-object root to {} through the shared isConfigRootObject() predicate so the listing shows plain defaults. Reported by CodeRabbit as an outside-the-diff finding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(config): show the telemetry notice before the first-run write check Since #1835, nothing is tracked until the notice has been shown, so the first-run test must show it before tracking the command. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Clay Good <hi@claygood.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
626269ed73 |
fix(templates): stop generated skills naming workflows the profile omits (#1775)
* fix(templates): stop generated skills naming workflows the profile omits The `core` profile installs six of the twelve workflows, but the update and apply templates named `/opsx:continue` (6 times) and `/opsx:new` (twice) regardless. On a default install those became `/openspec-continue-change` and `/openspec-new-change` — skills that were never written — so `update-change` refused to create a missing artifact and handed off to a dead end. The only guard was a sentence asking the model to check availability at runtime, 70 lines above the two places it hits the wall. `command-references.ts` decides how a reference is spelled; nothing decided whether it should be emitted at all. Add that: templates author both wordings with `optionalWorkflow()`, and `getSkillTemplates()` / `getCommandTemplates()` — the one place every generation path already funnels the resolved workflow set through — pick a branch before the reference transformers run. A profile that omits a workflow now gets a concrete `openspec status` / `openspec instructions` fallback instead of a reference to a skill that does not exist. Closes #1734 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(changeset): describe profile-aware workflow references Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(init): assert the core profile names no uninstalled workflow The end-to-end init test pinned the runtime availability hedging that #1734 is about, and asserted `/opsx:continue` appears in the default profile's generated update workflow — the bug itself. Assert the fixed behavior instead: neither `/opsx:continue` nor `/opsx:new` appears, and the CLI fallback is stated outright, for both the update and apply surfaces. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(templates): resolve every cross-workflow reference, not just core's The first commit fixed the two templates the default profile broke on. Every other cross-workflow handoff had the same shape, and arbitrary subsets are reachable: a `custom` profile is whatever the user picked, and `openspec update` re-derives a workflow set from what it finds on disk (legacy tool overrides, inferred Codex workflows) without passing it through getProfileWorkflows. So resolve all of them: - `apply` -> archive; `continue` -> apply, archive; `ff` -> apply; `new` -> continue; `propose` -> apply; `update` -> apply, archive; `archive` and `bulk-archive` -> sync. - `onboard`'s two command-reference tables are built from the installed set rather than printed in full with an "only if installed" caveat, and its explore, resume and next-step prompts are resolved the same way. Two supporting changes: - `onlyWithWorkflow()` plus a whole-line rule in the resolver: a conditional that owns its line takes the line with it when it resolves to empty, so a dropped table row cannot leave a blank line that ends the table in markdown. - `generateSkillContent()` and `generateCommand()` now throw on an unresolved marker. A generation path that skips the choke point fails loudly instead of writing `[[opsx:...]]` into a user's SKILL.md. The propose and ff surfaces keep their deliberate wording difference (#258): the command surface never invites "ask me to implement", so its missing-`apply` fallback names the CLI rather than a conversation. The guard test now runs the property over every subset that could expose a reference — each workflow alone, everything but one, the empty set, and the two shipped profiles — for skills and commands, in both spellings. Twenty-plus of those cases fail against the previous commit. Only `openspec-onboard` changes in the skills/ mirror: with every workflow installed, all other templates render byte for byte as before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(changeset): cover the full cross-workflow reference fix Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(templates): validate conditionals before choosing a branch Resolution discarded the unselected branch and only then checked for residual markers, so a truncated block inside the *missing* branch was accepted for a profile that installs the workflow and rejected for one that does not. Profile-dependent authoring errors are exactly what this module exists to remove. Validate the authored text up front instead: every marker must be one of the three recognized forms, and they must appear as a flat sequence of if / else / end. A malformed block now throws identically for every profile. The post-resolution check stays as a backstop. Caught by CodeRabbit on #1775. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: state the profile-aware handoff rule in the skills reference alfred-openspec on #1775: docs-lab/reference/skills.md described several handoffs as unconditional while this change deliberately emits a CLI or conversational fallback when the profile omits the target. Stated once, above the entries, rather than as a caveat on each of the eleven affected Response and Creates rows: the page's recipe is one fact per row, and repeating the same conditional eleven times would bury the contracts it exists to state. The rows keep naming the skill that owns the next step, which is the fact a reader looks up; the rule above them says what happens when that skill is not installed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(workflows): fold #1735 into the profile-aware references #1735 fixes the same issue (#1734) by removing the optional handoffs outright. This PR resolves them at generation time instead, which is strictly better for the template layer: an install that has `continue` still gets told about it. So the mechanism here wins and #1735's content is folded in, rather than the two competing for the same lines. What #1735 had that this did not: - src/commands/workflow/instructions.ts. The CLI's own runtime strings named the openspec-continue-change skill. Those are chosen at run time, so optionalWorkflow() cannot reach them; taken from #1735 verbatim. - The blocked-state fallback. It was a one-line pointer; it now carries #1735's full CLI recovery (select the next `ready` artifact, not `skipped` or `blocked`, read its rules with `openspec instructions`, keep the selected `--store` on both commands) plus the tracking-file repair path and the `missingArtifacts` field it branches on. The installed branch still names `/opsx:continue`, so neither audience loses. #1735's update-change.ts rewrite is not carried over: this PR already covers all six of those sites conditionally, which is the better answer. Both of #1735's test suites come across, and they are worth more here than there. test/core/templates/profile-handoffs.test.ts asserts that no generated file names an uninstalled workflow across every tool and all three delivery modes, which is the property this PR's mechanism exists to provide, and it passes against it. test/commands/profile-handoffs.test.ts covers the runtime CLI strings. The two guards are complementary: that one is broad on tools and deliveries, this PR's own profile-workflow-references.test.ts is broad on workflow subsets. #1735's command-references.test.ts assertions could not be carried as written, since they assume the reference is gone unconditionally. Replaced with a case that resolves the template against a set without `continue` and asserts the fallback carries the whole recovery. Verified it fails when the fallback is shortened. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: name the core-profile fallback on the two rows that hit it The rule above the entries covers every profile, but apply-change and update-change are Core skills whose rows name openspec-continue-change, which the core profile never installs. On the default install those rows now say what the generated skill points to instead: openspec status and openspec instructions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(changeset): drop em dashes from the release note Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7090e16d74 |
fix(schema): validate the apply block against declared artifacts (#1868)
* fix(schema): validate the apply block against declared artifacts parseSchema checked each artifact's requires but never the apply block, so schema validate passed a schema whose apply.requires named an artifact that does not exist or whose apply.tracks named a file no artifact generates. At run time apply skipped the unknown id, turning the apply gate off, or blocked forever on a tracked file nothing produces. Reject both in parseSchema, naming the bad value and what the schema declares. tracks is compared to generates by exact string, the same comparison the tracked-tasks lookups use, so any schema that parses is one they can resolve. * fix(schema): warn instead of failing the load on an unmatched apply.tracks apply.tracks is a path that apply reads as written, not an artifact id. A schema that tracks a hand-written TODO.md, or one file under a glob generates such as tasks/main.md, loads and applies correctly on main. Rejecting it in parseSchema made every command on that schema fail. Keep the unknown apply.requires id as a load error, the same as an unknown artifact requires. Report an apply.tracks path that matches no artifact's generates as a warning from `openspec schema validate`, which still exits 0. Move the docs note from legacy docs/customization.md into docs-lab (schema-yaml.md validation section, cli.md schema validate). The schema-yaml.md table had said unknown apply.requires IDs go unreported. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(schema): describe apply.tracks as a generates mismatch, not as ungenerated The apply.tracks check compares `tracks` to each artifact's `generates` with exact string equality, but its warning said the tracked file "is not generated by any artifact". That is false for the case this PR deliberately supports: `tracks: tasks/main.md` under `generates: tasks/*.md`, where the glob really does generate the file and only the strings differ. The diagnostic now names the real condition (the `tracks` value does not exactly match any `generates` value, so OpenSpec cannot tell which artifact's progress it tracks) and keeps both remedies. Both docs-lab pages, the changeset and the JSDoc that repeated the claim are corrected the same way, and a new CLI test pins that the glob case is described as a mismatch and never as ungenerated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(schema): pin apply.tracks matching across Windows path separators The tracked-tasks lookup compares apply.tracks and generates as plain strings, so a backslash on one side and a forward slash on the other must warn, and the same backslash spelling on both sides must not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Clay Good <hi@claygood.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8fc65b7f70 |
fix(tasks): count checkboxes under every list marker (#1862)
* fix(tasks): count checkboxes under every list marker The task counter behind list, status, view, instructions apply, validate --archived and archive's incomplete-task check read only `-` and `*` bullets. A GFM task is a list item, and CommonMark also allows `+` and ordered `1.` / `1)` markers, so unchecked ordered or plus tasks were dropped from every count: the change read as complete and archive skipped its incomplete-task warning. Accept every CommonMark list marker in TASK_LINE_PATTERN, keeping its existing tolerances (indentation, CRLF, a missing space after the marker). Every consumer goes through parseTaskLines, so this one change fixes them all, and task-numbering validation now sees these tasks too. * docs(tasks): list every counted checkbox marker in schema.yaml reference The apply.tracks section listed only - and * checkbox forms. Task lines under + and ordered markers now count, so show them and name the marker rule. Also use American spelling in the changeset. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Clay Good <hi@claygood.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
92fb72d1dc |
fix(workflows): create the main spec when a capability is new (#1701)
* fix(workflows): create the main spec when a capability is new The agent-driven archive workflow told agents to "compare each delta spec with its corresponding main spec" and said nothing about the case where that main spec does not exist yet. Comparing against nothing reads as "already synced", so the agent took the archive branch and the new capability's main spec was never written — the change landed in changes/archive/ with openspec/specs/ still empty. `openspec archive` already handles this: buildUpdatedSpec creates the spec from the delta's ADDED requirements, rejects MODIFIED/RENAMED with "only ADDED requirements are allowed for new specs", and warns past REMOVED. The guidance now says the same thing, so the agent path and the CLI path agree: - archive-change: a missing main spec counts as changes needed and is named in the summary as a spec the sync will create — never as already synced. - sync-specs: MODIFIED and RENAMED have no requirement to act on when the main spec is absent, so the sync stops and reports rather than inventing one; REMOVED is skipped with a warning. Guidance text only — no CLI, parser, or archive behavior changes. Closes #1222 Closes #1264 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(sync): never create a main spec with nothing to put in it CodeRabbit caught a gap in the previous commit: step 4b now tells the agent a REMOVED-only delta has nothing to remove, but step 4d still read as "create the main spec if the capability doesn't exist yet" unconditionally. Following both would write a spec whose `## Requirements` section is empty. Verified against the CLI on a REMOVED-only delta targeting a capability with no main spec: Specs to update: parking: create ⚠️ Warning: parking - 1 REMOVED requirement(s) ignored for new spec. Validation errors in rebuilt spec for parking (will not write changes): ✗ Spec must have at least one requirement Aborted. No files were changed. So step 4d is now gated on the delta having ADDED requirements to seed the spec with, and says what the CLI reports when it does not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: define "main spec" and close the no-ADDED archive loop Hardening pass over the two fixes in this branch. Guidance: the archive step's verification pass re-runs the same comparison the fix touched, so a delta that can create nothing — no ADDED requirements, no main spec to merge into — would have been reported as "still needs sync" after a sync that correctly created nothing, and an agent could loop on it. That case now short-circuits with the reason, matching `openspec archive`, which refuses it with "Spec must have at least one requirement". Docs: the glossary defined "delta spec" but never "main spec", which is half of #1647's terminology complaint. It now defines the term and says that for a new capability the main spec is created by the archive rather than written up front; concepts.md says the same in the delta-section table and the archive process. The docs site generates from docs/ at build time, so no website files change. Changeset rewritten in the house style (prose, no commit header; the changelog-github action supplies attribution) and renamed descriptively. All three CLI branches this guidance describes were verified end to end: ADDED against a greenfield repo creates the spec and carries its Purpose; MODIFIED reports "target spec does not exist; only ADDED requirements are allowed for new specs"; REMOVED-only aborts with "Spec must have at least one requirement" and writes nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(glossary): a standalone sync creates the main spec too The "Main spec" entry said the spec is created "by the archive", but the "Sync" entry two sections down says /opsx:sync creates it as well, without archiving — and specs-sync-skill's "New capability spec" scenario is the sync's own behavior. Names both paths so the two entries agree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(workflows): clarify removed deltas for missing specs * fix(workflows): preserve explicitly retired missing specs * fix(archive): preserve explicit skip-sync choice for blocked deltas --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2ef6fbde3d |
fix(config): run an EDITOR that carries arguments (#1878)
* fix(config): run an EDITOR that carries arguments config edit passed the whole EDITOR/VISUAL value to spawn as the program name with shell: false, so common settings such as `code --wait` failed with ENOENT, and the uncaught rejection printed a raw Node stack trace. Run the value the way git does: through sh -c '<editor> "$@"' with the config path as a positional argument, and through cmd.exe on Windows so .cmd shims resolve. A value that is itself the absolute path of an existing file still runs directly, so unquoted paths with spaces keep working. A failed start, a non-zero exit or a signal is now reported as a one-line error and the command exits 1. * fix(config): split EDITOR into argv instead of running a shell Run the editor without a shell. The EDITOR or VISUAL value is split into a program and arguments (double quotes everywhere; single quotes and backslash escapes on POSIX; literal backslashes on Windows) and the config path is appended as its own argument, spawned via cross-spawn with shell: false so Windows .cmd shims such as code.cmd still resolve. Shell metacharacters in the value are now inert. Show the install hint only when the program is missing (ENOENT), not for EACCES or EPERM. Move the EDITOR documentation from legacy docs/cli.md to docs-lab/reference/cli.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Clay Good <hi@claygood.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9f8dec5dd9 |
fix(store): refuse to remove a store that contains another store (#1880)
* fix(store): refuse to remove a store that contains another store store remove deleted the target folder recursively after checking only the target's own metadata. Another registered store living inside that folder, such as a shared store vendored as a git submodule (a layout store register accepts), was deleted with it, uncommitted work included, and its registry entry was left pointing at a missing path. Refuse with store_remove_contains_registered_store when another registration's canonical root is inside the folder. The check runs in the beforeCommit hook, under the registry lock that commits the removal, which now receives the registrations that will remain. * docs(store): move nested-store remove refusal to docs-lab Document store_remove_contains_registered_store on the canonical docs-lab CLI reference instead of legacy docs/cli.md. docs/agent-contract.md keeps the error-code entry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Clay Good <hi@claygood.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5d221456e5 |
fix(store): let setup --no-init-git run inside a git repository (#1884)
* fix(store): let setup --no-init-git run inside a git repository The nested-repo guard refused a setup path inside another Git repository even with --no-init-git, which creates no repository, so a store at ~/openspec/<id> was impossible for anyone whose home directory is a dotfiles repo. The CLI never passed the existing bypass either: prepareSetupInput ignored its options. Skip the guard when initGit is false, and forward --init-git and --no-init-git into the prepare step. The default setup and an explicit --init-git still refuse. Two store.test.ts cases passed --no-init-git while asserting the refusal; they now run the default setup the guard exists for. * docs(store): move setup nested-repo note to docs-lab The setup --no-init-git explanation belongs on the canonical docs-lab CLI reference, not legacy docs/cli.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(store): compare canonical paths in setup --no-init-git test Canonicalize payload.store.root and the registry local_path with fs.realpathSync.native before comparing, per review. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Clay Good <hi@claygood.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e01ed070f1 |
fix(archive): refuse delta files the merge path never reads (#1870)
* fix(archive): refuse delta files the merge path never reads validate and archive read a change's deltas only from specs/<capability-path>/spec.md, but the spec-driven artifact graph counts any markdown file under specs/ as written. A delta at specs/user-auth.md was reported done by status and ready by apply, rejected by validate only as having no deltas, and then archived with exit 0 and nothing merged. Name every markdown file under specs/ that carries delta sections but is not a capability's spec.md. validate reports it as an error with the spec.md its requirements belong in, archive runs that validation and refuses the change, and apply lists it in its warnings. --no-validate and changes with no spec files behave as before. * docs(schemas): move delta file placement note to docs-lab The legacy docs/ tree is frozen; the canonical page for the spec-driven delta layout is docs-lab/reference/schemas/spec-driven/index.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Clay Good <hi@claygood.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3b8e5b6616 |
fix(nix): install shell completions with the flake package (#1785)
* fix(nix): install shell completions with the flake package The flake exposed `openspec completion generate SHELL` but installed no completion scripts, so a Nix install had no completions at the standard locations. Generate the Bash, Fish, and Zsh scripts during postInstall and place them with installShellCompletion. Closes #1740 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: note that cross-compiled Nix builds omit completions Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci: run the Nix job when the completion generator changes The Nix build now runs `openspec completion generate`, so a change under src/core/completions can break packaging without touching flake.nix. Also drops the cross-compilation caveat from the Nix install docs: every package this flake exposes is native (`legacyPackages.<system>` has buildPlatform == hostPlatform), so `canExecute` is always true and the completions are never omitted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: move the Nix completions note to the canonical pages docs-lab/README.md makes docs-lab/ canonical and the old docs/ tree legacy, so the fact is recorded in the two docs-lab pages that own it and docs/cli.md and docs/installation.md are back to their state on main. - docs-lab/start/installation.md, Nix: the package ships the scripts at the standard locations, so completion install is not needed. - docs-lab/reference/cli.md, openspec completion: the same exception, stated where a reader looking up the command will hit it, linking to the Nix section rather than repeating the paths. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7de24044ef |
fix(init): make the universal tool target findable in the picker (#1778)
* fix(init): make the universal tool target findable in the picker Closes #653 `openspec init`'s tool picker is a searchable list of product names. The vendor-neutral target every unlisted assistant is meant to use was named "Shared .agents skills" — after the directory it writes, which is not a word anyone in that position searches for. Typing "universal", "other" or "generic" returned "No matches", so the escape hatch was unreachable and the reporter had to open an issue to find it. Rename the entry to "Other / Universal (shared .agents skills)" and give choices optional `searchAliases` the filter also matches. The picker also dropped every non-alphanumeric keystroke: readline reports punctuation only in `key.sequence`, leaving `key.name` undefined, so ".agents" and "amazon-q" could not be typed at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(init): point at the universal target when a tool search matches nothing "No matches" is where someone whose assistant is not on the list gives up — the picker knows the answer and does not say it. Add an optional `emptyHint` to the searchable multi-select, and have init name the vendor-neutral entry there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(init): close every dead end that hides the universal tool target Hardening pass over the same defect. Reviewing the first fix turned up four more places the answer was withheld: - `openspec update`'s tool picker builds its own choices and never passed searchAliases through, so the same search failed there. - `--tools <unknown>` printed a bare list of ids. It now names the fallback, the scripted counterpart of the picker's empty hint. The hint I first put on validateTools sat on an unreachable branch; the path users actually hit is the "Invalid tool(s)" parse error, and a test now pins it. - The search box dropped pasted text as well as punctuation. Any sequence whose characters are all printable is now accepted, which also lets a space reach the box so "claude code" filters. Escape sequences carry control characters and are still rejected, and the `name` fallback stays single-character so readline names like 'tab' are never typed. - docs-lab still taught the old label in two places. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: keep the FAQ a router and finish the alias list - help/faq.md is a one-liner surface (README's "FAQ is one-liners" rule), so the answer points at the support matrix's Other / Universal section instead of restating the picker's search terms. Drops the em dash that writing.md forbids. - reference/supported-tools.md keeps the search terms, now all nine the picker actually matches: `vendor-neutral` and `agents.md` were added to searchAliases after the first draft of this page. Same correction in the legacy docs/supported-tools.md paragraph. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(init): keep the tool-not-listed hint ASCII The non-interactive fallback hint is new terminal output and carried an em dash, an ambiguous-width glyph in the class #983 covered. A colon reads the same and cannot misalign a terminal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: drop the legacy docs/ tool-matrix edit docs-lab/reference/supported-tools.md already carries the label and search terms. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(init): prove picker punctuation through real readline key events The prompt tests fed a multi-character sequence that readline never emits: it splits a paste into one key per character, so a pasted space still toggles. Drive the handler with real emitKeypressEvents output (fails on main with 'amazonq'), pin the space limit, and stop claiming multi-word paste in the changeset and code comment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8b99c07bd0 |
fix(status): name the command that resumes a change (#1786)
* fix(status): name the command that resumes a change `openspec status` printed the artifact checklist and stopped. The command that moves the change forward was computed already and shipped in the JSON `nextSteps` sentence, but the text surface never rendered it, so anyone resuming a change had to know the next command by heart. Extract `resolveNextStep` so the command and the published sentence come from one place, and print it as a `Next:` line — matching the idiom `openspec new change` already uses to hand off to `openspec status`. The completion case matters most: "All planning artifacts complete!" reads as "you are done" even while implementation tasks remain, and it is now followed by the `openspec instructions apply` command that resumes the work. Closes #906 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * style(status): keep the loadStatus comment on loadStatus The store-flag note landed between the "single definition" comment and the declaration it describes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(status): cover the next-step line, and record it in the spec The behavior change shipped without the OpenSpec change this repo requires of user-facing work, and without coverage for the paths a reviewer would reasonably ask about. Adds `add-status-next-step`, whose delta modifies `Next Artifact Discovery` in `cli-artifact-workflow` - the requirement that already says status is how you learn what comes next. It now also says status names the command. Coverage added: - Unit tests for `resolveNextStep`, pinning the published `nextSteps` sentences verbatim. Confirmed byte-identical to main's output, so the split into command + sentence provably did not reword the contract. - Custom-schema case: the line is built from the resolved artifact id, so a project with neither proposal/specs/design/tasks still gets a usable command. - Skipped artifacts are never named - they satisfy dependents but must not be created. - `--json` stays parseable and carries no `Next:` line. - `--all` gives every change its own line, and a failed entry none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(status): assert the next-step line closes the output The spec delta says the text output ends with the `Next:` line, but the assertions used toContain, which a later line would still satisfy. Compare the last non-empty line instead, across all four ready/complete cases and the skipped one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(status): read the closing line CRLF-safely Split on /\r?\n/ so a CRLF stream cannot leave a carriage return attached to the line under comparison, and route the parity check through the same helper instead of its own scan. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: move the status Next: line to the canonical CLI page docs-lab/README.md makes docs-lab/ canonical and the old docs/ tree legacy, so the entry now lives under 'openspec status' in docs-lab/reference/cli.md and docs/cli.md is back to its state on main. Both documented outputs were captured from real runs rather than written by hand: the blocked case in a change with proposal and specs, and the complete case with all four artifacts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(status): drop em dashes from the changeset and proposal Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
09a999bbb2 |
fix(changes): report a change nested in a namespace folder (#1849)
* fix(changes): report a change nested in a namespace folder Specs can be nested by domain (`specs/mobile/tutorial-videos/spec.md`), so laying changes out the same way looks reasonable - but a change is only ever a directory directly under `changes/`. `changes/mobile/refresh-token/` left the real change invisible to every command while `mobile`, the folder around it, was reported as an ordinary task-less change. Nothing said so, and `openspec archive mobile` moved the unfinished change into the archive under the namespace's name with none of its deltas applied. A directory under `changes/` is now recognised as a namespace folder when it carries no change-root artifact of its own and wraps a directory that does. The probe is deliberately conservative: a miss behaves exactly as before, and an ordinary change - including a scaffolded one with no artifacts yet, and one whose only content is a root delta spec - is never reported as a folder. - `openspec list` marks it `not a change` and explains the flat-only layout instead of printing `No tasks`; `--json` gains an additive `warnings` entry. - `openspec show` and `openspec status --change` say the same rather than reporting a change that has no proposal yet. - `openspec validate` reports the nesting instead of "must have at least one delta", with next steps that name the fix rather than delta authoring. - `openspec archive` refuses it outright - burying an active change is data loss, not something to warn about. Closes #1846. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(agent-contract): document the list --json nesting warning Agents read 4.1 as the shape contract, so the additive `warnings` array and per-entry `nested` field have to appear there, along with the instruction not to treat a flagged entry as a change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(changes): harden the nested-change probe and cover status --all Adversarial review of the first commit found three holes. A custom schema may generate every root artifact into a subdirectory (`generates: rfc/proposal.md`). Created by hand, such a change carries no `.openspec.yaml`, so the marker probe read it as a namespace folder wrapping a change called `rfc` - a legitimate change that validated clean before, now refused by validate, status, instructions and archive, with no flag to get past it. The same probe missed the case it was written for whenever the nested change started from its delta specs rather than a proposal: `mobile/refresh-token/` holding only `specs/auth/spec.md` still archived as `mobile`, deltas dropped. A nested change is always hand-made - `openspec new change` rejects a name with a separator - so "mkdir the tree, write the deltas first" is a common way to arrive here. Both come from asking one question. A directory is now recognised as a change by a root artifact OR a populated `specs/`, and a directory holding any file of its own is never a namespace folder. The `specs/**/*.md` shape is fixed by the delta format rather than by the schema, which is what makes it safe to lean on. `change show archive` also reached the probe - it has no reserved-name guard - and offered to rename every dated archive entry into an active change. The reserved name is rejected in the probe itself, so no caller can repeat it. `status --all` read each directory straight through `loadStatus`, bypassing the lookup guard, and printed a whole artifact plan for work that is not there. It now carries the same per-change diagnostic a malformed change does, and the sweep continues past it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(changeset): state the nesting-detection bound CodeRabbit asked for the three-level limit in the release note. Stated as a closing sentence rather than a qualifier on the headline, so the note still leads with what changed for users. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(changes): trust the resolved schema before calling a folder a namespace A hand-made change under a custom schema that generates into a subdirectory (rfc/proposal.md), with no .openspec.yaml and no delta specs yet, was read as a namespace folder and refused by status, show, validate, instructions and archive. The probe now also counts a file at any path the resolved schema generates, the same check status uses to mark an artifact done. Move the flat-change-folder docs from legacy docs/ to docs-lab reference/cli.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
09984b8242 |
fix(validate): warn when tracked tasks have no checkboxes (#1774)
* fix(validate): warn when tracked tasks have no checkboxes Progress counts checkboxes and nothing else, so a tasks.md written as plain bullets or a numbered list is worse than an empty one: `openspec list` and `openspec status` report "No tasks", and `openspec archive` has no incomplete task to warn about. The file reads as finished to the tool and unfinished to a human. `openspec validate` now warns when every task file the change's schema tracks contains list items but not one checkbox, pointing at the first offending line. Reported per change, not per file, so a checklist alongside a prose file stays silent, and only files an artifact actually declares are linted - a bare tasks.md no schema tracks is left alone. Closes #354 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(validate): honor fence delimiter length and width CommonMark closes a fence only on the same character, a run at least as long as the opener's, and no info string. Comparing the first character alone let an inner ``` end an outer ```` block, exposing the bullets of a nested code sample as a task list. The delimiter pattern also loses its end anchor: `.` does not match `\r`, so an anchored info-string group matched nothing in a CRLF file and blinded the scan to fences. Adds the nested-fence, annotated-closer, tilde/backtick, longer-closer and CRLF cases, plus an e2e change whose nested task files are all bullets, asserting both reported paths stay POSIX-separated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(validate): scan rendered content and complete evidence only Hardening pass over the checkbox warning. The scan for the offending line now skips YAML front matter and HTML comment blocks alongside fenced code. A list under `tags:` is metadata about the file rather than the work it tracks, and a commented-out list is not work either; each exclusion can only silence a warning, never drop a real task, which is the opposite trade from the task parser. An unterminated `---` opener rewinds to the top, because that is a thematic break and everything below it is still content. Only a comment opening its own line hides that line, so the template's `## 1. <!-- Task Group Name -->` heading cannot swallow the checklist beneath it. A tracked file that exists but cannot be read now withdraws the warning entirely: "no file here holds a checkbox" is a claim about the whole tracked set, and the checkboxes may be in exactly the file that would not open. `validate --archived` stays the surface that reports an unreadable task file loudly (#205). The message leads with the consequence rather than an accusation, since a file may legitimately carry a bulleted note and no tasks yet. New coverage: every packaged tasks template is asserted checkbox-shaped (the guard fails if a template loses its boxes), a schema tracking tasks by artifact id with no `apply` block, the deprecated `change validate` text output, and an unreadable tracked file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(validate): match CommonMark on fence indent and front matter Two block-scanning defects, both of which hid list items. A fence indented four spaces is an indented code block, not an opener. Accepting it left the scan inside a block that never began, so every list below it went unseen. Fence recognition now stops at three spaces. `----` is a thematic break, not a YAML front-matter delimiter. Matching three-or-more dashes let one open a block that swallowed the list under it until the next `---`. Front matter is now exactly three dashes. Two test defects alongside them. The deprecated-command test claimed to assert the reported line, but the text renderer prints no line for any issue; it now asserts the level and path prefix that surface actually emits, with the line left to the JSON assertion that already covers it. The unreadable-file fixture would have passed for the wrong reason had the mode not taken, since the checkbox it hides would have silenced the warning by itself; the read failure is now asserted first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(validate): name task files relative to a canonical change dir Windows CI caught the report naming a task file `../../../../../../../../runneradmin/AppData/.../tasks.md` instead of `tasks.md`. `resolveArtifactOutputs` hands back real paths while `changeDir` carries whatever spelling the caller resolved, and a short 8.3 alias against its expanded form is a difference in spelling, not in location, so the relative path escaped the change. A symlinked project directory reproduces it off Windows. Canonicalizing both sides recovers the relationship. A path that still escapes falls back to the file name, so no report can leak an absolute filesystem path. Numbering issues are named through the same helper and gain the same fix. The deprecated-command test now asserts the `[WARNING] tasks.md:` prefix that exposed this, and the Windows job is its regression guard: the mismatch cannot be staged on POSIX, where the spawned CLI's cwd is already physical. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(validate): keep indented code out of the uncheckboxed-task scan alfred-openspec on #1774: the scan said it reads rendered content, but LIST_ITEM began with \s* and so reported top-level four-space-indented code such as ' - example output' as an uncheckboxed task list. Under --strict that false positive failed validation on a correct file. A list-shaped line is now taken only below four visual columns of indent, the same cut the fence logic already applies, with tabs counting as four. Genuine nested lists are untouched: this scan reports the first list item it finds and a nested item always sits under a shallower parent, so the parent is reported exactly as before. A list-shaped line four columns deep with nothing shallower above it is not nested under anything, which is what makes it code. Regressions cover space-indented, tab-indented and numbered code samples, and pin both the nested-list case (parent still reported) and three-space indent (not code). Verified the guard bites: removing the column test fails them. Also moves the documentation to its canonical home. docs-lab/README.md says the old docs/ tree is legacy and must stay untouched, so the docs/concepts.md line is dropped and the warning is documented under 'openspec validate' in docs-lab/reference/cli.md, beside the archive merge findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(validate): skip BOM-prefixed front matter and cap ordered markers at nine digits A prose-only task file that opened with a UTF-8 BOM before its front matter was warned about, because the opener never matched and the tags list was scanned. A number longer than nine digits followed by a period also matched as a list item, which CommonMark does not allow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(changeset): drop the em dash Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(tasks): use a multi-character marker for the unrecognised-checkbox case A single-character marker such as `[~]` becomes a task once #1773 lands, which would flip this expectation. `[ab]` is not a task under either parser, so the test keeps asserting that checkbox-looking list items that count as no task still warn, whichever PR merges first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
fbd4160b37 |
docs: reroute unfinished store reference links (#1767)
Co-authored-by: Clay Good <hi@claygood.com> |
||
|
|
db03c6c4b0 |
feat(validate): add findings-only bulk reports (#1713)
* feat(validate): propose findings report * docs(validate): clarify findings report contract * feat(validate): implement and harden bulk findings reports * test(validate): canonicalize store paths natively --------- Co-authored-by: Clay Good <hi@claygood.com> |
||
|
|
a4fcdbece6 |
feat(validate): report the deltas archive would refuse (#1710)
* feat(validate): report the deltas archive would refuse validate checked a change's deltas against themselves and, for MODIFIED blocks, against the main spec's scenarios. It never checked whether the main spec can supply the target a delta acts on, so a MODIFIED naming a requirement that is not there, a RENAMED whose source is gone, or an ADDED whose name already exists all validated clean and failed at archive instead - typically weeks later, after the implementing PR had shipped and the authoring session was gone. Run the merge archive runs and report what it refuses. buildUpdatedSpec returns the rebuilt content without writing it, so the preflight is the same function on the same inputs with the result discarded, and cannot disagree with the code that does the writing. That matters here: several of those preconditions deliberately read a missing target as already-synced rather than as a failure, and a second copy of the rules would be free to drift. Reported as INFO so no verdict changes in any mode. A MODIFIED whose target is missing is also what a change modifying a sibling's unarchived requirement looks like, and validate stays valid for that case today; telling the two apart needs the opt-in marker #1112 asks for. What is missing until then is the information, not the verdict. Refs #1112 * fix(validate): skip preflight for deltas whose errors come after the loop missingHeaderSpecs and emptySectionSpecs are collected inside the per-spec loop but only become issues after it, so a suppression set built from the issues raised so far could not see them. A headerless or empty-section delta has nothing for the merge to apply, so the preflight reported that as a blocker of its own, on top of the error that names the actual mistake. * fix(validate): harden archive preflight diagnostics * fix(validate): preserve reports when archive preflight cannot start --------- Co-authored-by: Clay Good <hi@claygood.com> |
||
|
|
2fa679f180 |
fix(schema): make schema init --default actually set the default (#1709)
* fix(schema): make schema init --default actually set the default --default wrote defaultSchema to openspec/config.yaml, but the config loader only reads schema, so new changes kept using spec-driven while the command reported success. Write the key that is read, and drop the dead one a previous run may have left behind. Fixes #1708 * fix(schema): preserve supported configs when setting default * docs(schema): specify default config file handling * docs(schema): protect default config migration * fix(schema): make default initialization atomic * fix(schema): hide init staging and backup dirs from discovery `schema init` stages into `.init-staging-<rand>` and moves an existing schema aside to `<name>.init-backup-<pid>-<ts>`, both inside the schemas dir. `schema fork` already did this and the resolver filters its temp names out of discovery; the init names were never added, so `listSchemas` and `listSchemasWithInfo` surfaced them as real schemas -- in shell completions, "available schemas" error lists, and change-metadata validation. The backup is the durable case: cleanup failure is deliberately tolerated with a warning, so a blocked cleanup (or a crash mid-transaction) leaves a permanent phantom schema behind. Generalize the fork-only filter to cover both commands' staging and backup names. Real schema names are kebab-case, so excluding these dot-bearing names can never hide a legitimate schema. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Clay Good <hi@claygood.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
dd7cea3ffe |
feat(show): diff delta requirements against the main specs (#980)
* Proposed feature: openspec show --diff to see changed requirements more clearly * Implementation of the --diff feature, which led to some spec changes during implementation. * Update the proposal to be clearer. Thanks coderabbit * Fix a bug identified by coderabbit with excessive trimming, and add a testcase for it. * fix(show): harden --diff for review feedback Keeps `openspec show <change>` without `--diff` a raw proposal passthrough, reports when a change has no delta specs instead of returning silently, preserves the Reason/Migration body of a REMOVED requirement, and resolves main specs through the command's root so `--store <id>` diffs against that store. Text mode and JSON mode now render from one shared collection pass, the CLI tests drive argv arrays from a mkdtemp project instead of interpolated shell strings, `--diff` is registered for shell completions, and the stray package-lock.json is gone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * build(deps): declare the diff dependency and refresh the flake hash Adds the `diff` runtime dependency that requirement-diff.ts imports, updates pnpm-lock.yaml, and regenerates the flake's pnpmDeps hash so `nix build` matches the new lockfile. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(show): diff nested capabilities too collectSpecDiffs enumerated only top-level directories under the change's specs/, so a nested capability (specs/<area>/<id>/spec.md) was skipped: text mode printed nothing for it and its MODIFIED deltas came back from --json with no diff. It now uses the same discoverSpecFiles() helper ChangeParser uses, so the capability ids match the `spec` field of the JSON deltas. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(show): report header mismatches instead of hiding them Two cases where --diff quietly showed something misleading: A MODIFIED requirement whose capability has no main spec was rendered as all-additions, which reads like a new capability. It is an authoring error archive will reject, so it now prints the raw text with a warning naming the missing spec. A header that differs from the main spec only in case or interior spacing found no match at all under exact lookup, or matched under a lowercase-only comparison that let a real mismatch through silently. Lookup is now exact first, then the shared foldRequirementName fallback, and a folded match prints the diff the author meant alongside a warning that archive matches names exactly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(show): name the main spec as such in collectSpecDiffs Comment and locals still called the main spec the "base" spec, and the no-main-spec comment described the old all-additions behavior. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(deps-dev): bump the development-dependencies group with 2 updates Bumps the development-dependencies group with 2 updates: [smol-toml](https://github.com/squirrelchat/smol-toml) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint). Updates `smol-toml` from 1.7.1 to 1.8.0 - [Release notes](https://github.com/squirrelchat/smol-toml/releases) - [Commits](https://github.com/squirrelchat/smol-toml/compare/v1.7.1...v1.8.0) Updates `typescript-eslint` from 8.66.0 to 8.67.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/typescript-eslint) --- updated-dependencies: - dependency-name: smol-toml dependency-version: 1.8.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies - dependency-name: typescript-eslint dependency-version: 8.67.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> * chore(nix): invalidate pnpm dependency hash * fix(nix): update pnpm dependency hash * fix(show): harden requirement diff output * build(nix): pin combined dependency hash * test(show): assert proposal precedes diffs * docs(show): clarify JSON diff diagnostics * fix(show): retain unified diff hunk headers --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Clay Good <hi@claygood.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
109f81f17d |
fix(antigravity): update skill and workflow paths from .agent to .agents (#830)
* fix(antigravity): update skill and workflow paths from .agent to .agents Antigravity has migrated from .agent (singular) to .agents (plural) for workspace skills and workflows. The old .agent path still works via backward compatibility, but the official docs now specify .agents. Changes: - config.ts: skillsDir '.agent' -> '.agents' - antigravity adapter: workflow path '.agent/workflows/' -> '.agents/workflows/' - legacy-cleanup: add patterns for old .agent/ artifacts cleanup - docs: update supported-tools.md table - tests: update expected path assertions Closes #0 (reported by BugsCreator and Minh Pham in Discord) * fix(antigravity): migrate an existing .agent install to .agents Pointing Antigravity at `.agents` leaves every existing `.agent/` install behind, so this registers the move instead of only changing the target: - `.agent` becomes Antigravity's legacy skills root and legacy tool root, so update relocates managed skills and commands after generating their replacement, keeping a file the user customized. - Detection keys off `.agent` and `.agents/workflows`. The bare `.agents` root is shared with Codex, Zed, and the vendor-neutral target, so it cannot stand in for "Antigravity is set up here". - Delivery inference reads a tool's legacy roots for command files too. Without it the first update after the move saw skills but no commands, wrote `delivery: skills`, and the next update deleted every slash command. - Legacy slash-command cleanup stays scoped to the pre-opsx `openspec-*` names under `.agent`; the migration owns the `opsx-*` files, and a shared root is never glob-swept. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: record the Antigravity root move Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(antigravity): require a generated command before moving its legacy copy migrateCommandFiles relocated a legacy command file whether or not the current root held a replacement. Codex never reached that path — it has no command adapter — so Antigravity is the first after-generation move where it matters: under skills-only delivery, or for a deselected workflow, the move recreated a command OpenSpec had just decided not to install. Gate the move on an existing destination, the same way migrateSkillDirs already does for after-generation timing. Reported by CodeRabbit on #830. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(antigravity): arbitrate shared agent skills * test(antigravity): cover Windows migration paths * fix(antigravity): harden shared-root migration --------- Co-authored-by: Clay Good <hi@claygood.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a7353aea9a |
feat(status): add --all for batch status of every active change (#1301)
* feat(status): add --all for batch status of every active change
`openspec status --all --json` reports every active change in one
process instead of one CLI spawn (~500ms module-load) per change,
mirroring the existing `validate --all`. Emits a single
`{ changes: [ChangeStatus, ...], root }` envelope sorted by change
name; a change that fails to load contributes a per-change error entry
instead of failing the sweep. `--all` and `--change` are mutually
exclusive, honoring the --json null-shape on failure.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(status): harden --all per adversarial review findings
- Validate --schema before the no-changes early return so a bogus
schema fails consistently whether or not any change exists.
- Text mode now exits 1 when any change fails to load (mirrors
validate --all); JSON mode still exits 0 with per-change diagnostics.
- Add tests for the --all --schema interaction (unknown schema
null-shape, override propagation, broken-metadata precedence) and
text-mode failure rendering.
- Changeset heading to "### New Features" per repo convention; add
status --all to the agent quick-reference table in docs/cli.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(status): thread batch null-shape through root resolution, align sort with validate
Code-review findings on --all:
- Pass failurePayload: { changes: [] } to resolveRootForCommand so a
root-selection failure under --all --json still emits the documented
batch null-shape (siblings like list/doctor/context already do this).
- Sort with localeCompare to match validate --all's ordering for
mixed-case change names.
- Extract a shared loadStatus helper so the batch and single-change
payloads cannot drift apart.
- Changeset no longer claims exact validate --all parity (JSON exit
semantics deliberately differ).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(status): give the --all failure null-shape a single owner
Simplify pass on the --all diff: hoist the { changes: [] } batch
null-shape into an exported BATCH_STATUS_FAILURE_PAYLOAD constant so
the root-resolution and CLI-wrapper failure paths cannot drift, replace
the conditional spread with the plain ternary the sibling call site
already uses, drop a redundant array copy before sort, and narrow the
text-mode failure counter to the boolean it actually is.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(status): point the missing-target error at --all, correct the docs
`openspec status` with neither --change nor --all listed the available
changes and named only --change, so the batch path was discoverable
only from --help. The error now offers both.
Also corrects two stale claims in the status section of docs/cli.md
that the new row sits next to: the command never prompts for a change
(it errors), and bare `openspec status` is not an interactive check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(status): assert --all carries store context like the single-change path
The batch sweep resolves the root once and threads the store id into
every entry. Nothing pinned that: a regression would have shown up only
as a wrong path inside an agent's JSON. Assert the sweep's envelope root
and per-change payload match `status --change` in a registered store.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(status): fail incomplete batch reports
* docs(status): clarify empty and batch output
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Clay Good <hi@claygood.com>
|
||
|
|
f1b521dffa |
docs: rebuild docs site from docs-lab (#1649)
* docs: rebuild docs site from docs-lab
Replace the docs site's source tree with docs-lab, a page-by-page rebuild
of the OpenSpec docs (40 pages: Start / Guides / Customize / Multi-repo /
Reference / Help).
- Point website/docs.sync.config.mjs at ../docs-lab and restructure the
sidebar into nested groups; sync script gains nested meta.json emission,
leading-quote descriptions, idempotent writes, and diagram asset copying
- Remove the marketing landing page; / now redirects to /docs
(meta-refresh page + Cloudflare _redirects)
- Add remark plugins (faq, file-steps, gfm-alert) and the FileSteps
component backing the new page formats
- Add install.md at the repo root, curled by docs-lab/start/installation.md
as an agent-executable install prompt
- Add the docs authoring skills (.agents/skills/{write,draft,verify}-
openspec-docs); docs-lab/README.md links into write-openspec-docs
The old docs/ tree is now unused by the site and left for a follow-up.
Claude-Session: https://claude.ai/code/session_01BMMLYNJQPKXx1QHpnDn4ho
* docs: hold back unwritten pages, add worksets, drop diagram drafts
- website: comment out Overview, Guides, Architecture, Help, Legacy in
docs.sync.config.mjs until those pages are written; temporary
/docs -> /docs/installation redirect (Cloudflare _redirects + static
export meta-refresh fallback in page.tsx)
- docs-lab: new multi-repo/worksets.md page, published under Multi-repo
- docs-lab: content revisions across start/, customize/, reference/,
help/, multi-repo/; add review notes (Notes.md)
- remove docs-lab/diagrams option-* drafts and their website copies
- write-openspec-docs skill: add spoken-flow sentence rule
* docs: address review on PR #1649
- sync-docs: read the existing output directly instead of exists-then-read
(CodeQL TOCTOU alert)
- hold back the headings-only Environment variables and Stores reference
pages until written; links to them fall back to their GitHub source
- sources.md: cutover keeps docs/ in place and points at public/_redirects
- setup.md: label the workflow tree as the default set plus two optional ones
* docs: two review nits (spoken-flow rule, XDG_DATA_HOME note)
|