From 922aa1ab1bce05c5966e7a72f8b430c086bc78c0 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Thu, 10 Sep 2026 13:29:40 -0500 Subject: [PATCH 1/4] docs(explore): stop claiming explore never writes files Sixteen lines across both documentation trees told users that `/opsx:explore` creates no artifacts and writes no files, full stop. That has been false since explore shipped (#467): its capture branch writes the planning artifacts the user asked for, and can edit an existing change's artifacts. #1503 later made it scaffold with `openspec new change` first, closing #668 and #720. The claim appeared in two shapes. Six lines denied the capability outright ("Explore creates no artifacts and writes no code"). Ten more said the same thing as a timing claim ("before any artifact exists"), which reads as ordinary pitch copy and is what escaped the first pass. Every site now carries one guarantee, worded the same way: explore never writes code, and writes nothing else unless you ask, or say yes when it offers. Four sites described only the user-initiated trigger, which left the offer path - the one a reader actually hits - looking like it did not exist. docs/explore.md and docs/commands.md also gain a positive description of capture where the denial used to sit, including what scaffolding creates beyond the artifacts you named, and how capture differs from handing off to propose (propose writes the set your schema requires; capture writes only what you named). Closes #1833 Co-Authored-By: Claude Opus 5 --- README.md | 2 +- docs-lab/start/quickstart.md | 2 +- docs/README.md | 2 +- docs/commands.md | 15 +++++++++++---- docs/examples.md | 2 +- docs/explore.md | 18 ++++++++++++------ docs/faq.md | 2 +- docs/getting-started.md | 2 +- docs/glossary.md | 2 +- docs/overview.md | 2 +- docs/workflows.md | 4 ++-- 11 files changed, 33 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index ddf36a476e..c8b71a475d 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ openspec init Now talk to your AI: -- **Not sure what to build yet?** Start with `/opsx:explore`, a no-stakes thinking partner that reads your code, weighs options, and shapes a plan before anything is written. ([Explore guide](docs/explore.md)) +- **Not sure what to build yet?** Start with `/opsx:explore`, a no-stakes thinking partner that reads your code, weighs options, and shapes a plan before any code gets written. ([Explore guide](docs/explore.md)) - **Already know what you want?** Go straight to `/opsx:propose `. Both are in the default profile. If you want the expanded workflow (`/opsx:new`, `/opsx:continue`, `/opsx:ff`, `/opsx:verify`, `/opsx:bulk-archive`, `/opsx:onboard`), select it with `openspec config profile` and apply with `openspec update`. diff --git a/docs-lab/start/quickstart.md b/docs-lab/start/quickstart.md index a822f3d12a..fa7deae19b 100644 --- a/docs-lab/start/quickstart.md +++ b/docs-lab/start/quickstart.md @@ -27,7 +27,7 @@ Think the idea through with your agent before you ask for a plan. In your AI cha /openspec-explore how rate limiting should work in this app ``` -Explore is a thinking mode. The agent investigates your codebase, asks the questions that matter, sketches options, and challenges assumptions. It writes no code and no files. The output is a sharper idea. +Explore is a thinking mode. The agent investigates your codebase, asks the questions that matter, sketches options, and challenges assumptions. It never writes code. It writes nothing else unless you ask it to capture what you decided, or say yes when it offers. The output is a sharper idea. Stay here as long as the problem needs. When the shape feels right, hand it off: diff --git a/docs/README.md b/docs/README.md index a9c2d23659..feab8287fd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,7 +11,7 @@ If you read nothing else, read these two pages: That second one matters more than it looks. OpenSpec has two halves: a command line tool you run in your terminal, and slash commands you give to your AI assistant. Knowing which is which saves you the most common moment of confusion. -> **The best habit to build first: when you're not sure what to build, start with `/opsx:explore`.** It's a no-stakes thinking partner that reads your code, weighs options, and sharpens a fuzzy idea into a concrete plan before any artifact or code exists. The [Explore First](explore.md) guide makes the case. +> **The best habit to build first: when you're not sure what to build, start with `/opsx:explore`.** It's a no-stakes thinking partner that reads your code, weighs options, and sharpens a fuzzy idea into a concrete plan before any code gets written. The [Explore First](explore.md) guide makes the case. ## Pick your path diff --git a/docs/commands.md b/docs/commands.md index 7546dae82d..f766379c90 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -78,7 +78,7 @@ AI: Created openspec/changes/add-dark-mode/ ### `/opsx:explore` -> **Start here when you're unsure.** Explore is a no-stakes thinking partner: it reads your codebase, compares options, and sharpens a fuzzy idea into a concrete plan before any change exists. It ships in the default profile. For the full case and more examples, see the [Explore First](explore.md) guide. +> **Start here when you're unsure.** Explore is a no-stakes thinking partner: it reads your codebase, compares options, and sharpens a fuzzy idea into a concrete plan before any code gets written. It ships in the default profile. For the full case and more examples, see the [Explore First](explore.md) guide. Think through ideas, investigate problems, and clarify requirements before committing to a change. @@ -97,6 +97,7 @@ Think through ideas, investigate problems, and clarify requirements before commi - Investigates the codebase to answer questions - Compares options and approaches - Creates visual diagrams to clarify thinking +- Captures the exploration when you ask, or when you say yes to its offer: scaffolds a change with `openspec new change` and writes the planning artifacts you name, or updates an existing change's artifacts - Can transition to `/opsx:propose` (default) or `/opsx:new` (expanded workflow) when insights crystallize **Example:** @@ -119,14 +120,20 @@ AI: Let me investigate your current auth setup... Your API already has CORS configured. Which direction interests you? -You: Let's go with JWT. Can we start a change for that? +You: Let's go with JWT. -AI: Ready when you are. Run /opsx:propose add-jwt-auth to begin. +AI: That's a decision worth keeping. Want me to start a change for it? + +You: Yes. + +AI: Started openspec/changes/add-jwt-auth/ - change metadata only, no + artifacts yet. Say the word and I'll write the proposal, specs, + and tasks from what we just worked out. ``` **Tips:** - Use when requirements are unclear or you need to investigate -- No artifacts are created during exploration +- It never writes code, and writes nothing else unless you ask, or say yes when it offers - Good for comparing multiple approaches before deciding - Can read files and search the codebase diff --git a/docs/examples.md b/docs/examples.md index 80d85611af..f49dabbd94 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -68,7 +68,7 @@ Tip: for a fix, a good scenario is the regression test in prose. "GIVEN a logged **When to use it:** you have a problem but not yet a plan. You're not sure what to build, or which approach is right. -Start with `/opsx:explore`. It's a thinking partner with no structure and no artifacts created. It reads your codebase and helps you decide. +Start with `/opsx:explore`. It's a thinking partner with no structure. It never writes code, and writes nothing else unless you ask it to capture what you decided, or say yes when it offers. It reads your codebase and helps you decide. ```text You: /opsx:explore diff --git a/docs/explore.md b/docs/explore.md index 432b0c6272..7c667d5440 100644 --- a/docs/explore.md +++ b/docs/explore.md @@ -1,6 +1,6 @@ # Explore First -**`/opsx:explore` is your thinking partner. Reach for it whenever you have a problem but not yet a plan.** It investigates your codebase, weighs options with you, and clarifies what you actually want, all before a single artifact or line of code is created. When the picture is clear, it hands off to `/opsx:propose`. +**`/opsx:explore` is your thinking partner. Reach for it whenever you have a problem but not yet a plan.** It investigates your codebase, weighs options with you, and clarifies what you actually want, all before a line of code is written. When the picture is clear, it hands off to `/opsx:propose`. If you take one habit from these docs, take this one: **when you're not sure, explore before you propose.** @@ -27,14 +27,16 @@ Explore is a **conversation**, not a generator. - Compare options and name the tradeoffs of each. - Draw diagrams to make a design legible. - Help you narrow a vague idea into a concrete, buildable scope. +- Capture the exploration when you ask, or when you accept its offer: it scaffolds the change with `openspec new change` and writes the planning artifacts you named, or updates an existing change's artifacts. - Transition to `/opsx:propose` when you're ready. **It does not:** -- Create a change folder. -- Write any artifacts (no proposal, specs, design, or tasks). -- Write or modify code. +- Write or modify code. Explore never writes code, on any path, capture included. +- Design or edit your schemas or templates. Shaping those is a change, not thinking. +- Start a change or write an artifact on its own. It writes nothing unless you ask, or say yes when it offers, and then only what you agreed to. +- Push you toward capturing. It offers when the thinking crystallizes; you decide. -That's the point. Exploring costs you nothing and commits you to nothing. You can explore three dead ends, learn something from each, and only then propose the path that survived. +That's the point. Exploring costs you nothing and commits you to nothing until you say so. You can explore three dead ends, learn something from each, and only then propose the path that survived. ## It's already installed @@ -95,6 +97,10 @@ explore ──► propose ──► apply ──► archive You can say it in plain language ("let's turn this into a change") or run `/opsx:propose ` directly. Either way, the exploration you just did becomes the foundation of the proposal, not throwaway chat. +You can also ask explore to capture the change itself, without leaving the conversation: "start a change for this" scaffolds the folder, and "write the proposal too" writes exactly the artifacts you named. Scaffolding also lays down the change's own metadata, and fills in anything your project is missing at the top level (`openspec/specs/`, `openspec/changes/archive/`, a `config.yaml`). + +That's the same destination as handing off, with one difference: propose writes the whole set your schema requires to reach implementation, while capture writes only what you named. + If you use the expanded command set, explore can hand off to `/opsx:new` instead, for step-by-step artifact creation. See [Workflows](workflows.md). ## Tips for a good exploration @@ -107,7 +113,7 @@ If you use the expanded command set, explore can hand off to `/opsx:new` instead ## The honest tradeoffs -**What you gain:** explore catches wrong turns at the cheapest possible moment, before any artifact exists. It's especially powerful in unfamiliar code, where the AI's ability to read and summarize the system saves you an afternoon of spelunking. +**What you gain:** explore catches wrong turns at the cheapest possible moment, before you've committed to anything. It's especially powerful in unfamiliar code, where the AI's ability to read and summarize the system saves you an afternoon of spelunking. **What it costs:** a little patience. Explore is a conversation, so it's slower than firing off `/opsx:propose` and hoping. For work you genuinely understand already, that extra step is pure overhead, and you should skip it. diff --git a/docs/faq.md b/docs/faq.md index 770479aa3e..b77cf9e929 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -50,7 +50,7 @@ Both are files OpenSpec writes so your assistant can run the workflow. Skills (` ### Where should I start if I'm not sure what to build? -With `/opsx:explore`. It's a no-stakes thinking partner that reads your codebase, lays out options, and turns a fuzzy problem into a concrete plan, all before any change or code exists. It's in the default profile, so it's always available. When the plan is clear, it hands off to `/opsx:propose`. This is the single best habit to form, because it stops an eager AI from confidently building the wrong thing. See [Explore First](explore.md). +With `/opsx:explore`. It's a no-stakes thinking partner that reads your codebase, lays out options, and turns a fuzzy problem into a concrete plan, all before any code gets written. It's in the default profile, so it's always available. When the plan is clear, it hands off to `/opsx:propose`. This is the single best habit to form, because it stops an eager AI from confidently building the wrong thing. See [Explore First](explore.md). ### What's the simplest possible flow? diff --git a/docs/getting-started.md b/docs/getting-started.md index 36cd97cbb6..373efa186d 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -26,7 +26,7 @@ Two terminal steps to set up, then you live in chat. The rest of this guide unpa **Don't want to do the terminal part yourself?** Paste the [setup prompt](installation.md#install-with-your-ai-assistant) into your assistant and it handles both lines, then reports what it created. -> **Not sure what to build yet? Start with `/opsx:explore`.** It's a no-stakes thinking partner that reads your codebase, weighs options, and sharpens a fuzzy idea into a concrete plan, all before any artifact or code exists. When the picture is clear, it hands off to `/opsx:propose`. This is the single best habit for working with an AI that will otherwise confidently build the wrong thing. See the [Explore guide](explore.md). +> **Not sure what to build yet? Start with `/opsx:explore`.** It's a no-stakes thinking partner that reads your codebase, weighs options, and sharpens a fuzzy idea into a concrete plan, all before any code gets written. When the picture is clear, it hands off to `/opsx:propose`. This is the single best habit for working with an AI that will otherwise confidently build the wrong thing. See the [Explore guide](explore.md). ## How It Works diff --git a/docs/glossary.md b/docs/glossary.md index 345125f38a..0c459fa06e 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -46,7 +46,7 @@ Terms are grouped by topic, then alphabetized within each group. **Slash command.** A command you type into your AI assistant's chat, like `/opsx:propose`. Slash commands drive the workflow. They are not terminal commands. See [How Commands Work](how-commands-work.md). -**Explore (`/opsx:explore`).** The thinking-partner command. It reads your codebase, compares options, and clarifies a fuzzy idea into a concrete plan, creating no artifacts and writing no code. The recommended starting point whenever you have a problem but not yet a plan. See [Explore First](explore.md). +**Explore (`/opsx:explore`).** The thinking-partner command. It reads your codebase, compares options, and clarifies a fuzzy idea into a concrete plan. It never writes code, and writes nothing else unless you ask it to capture the exploration as a change, or say yes when it offers. The recommended starting point whenever you have a problem but not yet a plan. See [Explore First](explore.md). **CLI.** The `openspec` program you run in your terminal. It sets up projects, lists and validates changes, opens the dashboard, and archives. The terminal half of OpenSpec. See [CLI](cli.md). diff --git a/docs/overview.md b/docs/overview.md index 6321a3439a..6cda79036b 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -56,7 +56,7 @@ In the default setup, your day looks like this. Optionally think it through firs /opsx:archive → specs updated, change archived ``` -**When in doubt, start by exploring.** `/opsx:explore` is a no-stakes thinking partner: it reads your code, lays out options, and turns a fuzzy idea into a concrete plan before any artifact exists. It's the best antidote to an AI that will otherwise build *something* from a vague prompt. Already know exactly what you want? Skip straight to `/opsx:propose`. Either way, explore ships in the default profile, so it's always there. See the [Explore guide](explore.md). +**When in doubt, start by exploring.** `/opsx:explore` is a no-stakes thinking partner: it reads your code, lays out options, and turns a fuzzy idea into a concrete plan before any code gets written. It's the best antidote to an AI that will otherwise build *something* from a vague prompt. Already know exactly what you want? Skip straight to `/opsx:propose`. Either way, explore ships in the default profile, so it's always there. See the [Explore guide](explore.md). Those are slash commands, typed in your AI assistant's chat. Setup (`openspec init`) happens in your terminal. If that split is new to you, read [How Commands Work](how-commands-work.md) first; it's the most common point of confusion. diff --git a/docs/workflows.md b/docs/workflows.md index 99bf36dbb0..cbdc5d230b 100644 --- a/docs/workflows.md +++ b/docs/workflows.md @@ -140,7 +140,7 @@ You: Yes. You: /opsx:propose rebuild-search-index-on-write ``` -Explore creates no artifacts and writes no code. It's a free, no-stakes conversation that turns a vague worry into a precise change, so the proposal that follows is sharp. Already know exactly what you want? Skip it and go straight to `/opsx:propose`. Full guide: [Explore First](explore.md). +Explore never writes code, and writes nothing else unless you ask, or say yes when it offers. It's a free, no-stakes conversation that turns a vague worry into a precise change, so the proposal that follows is sharp. Already know exactly what you want? Skip it and go straight to `/opsx:propose`. Full guide: [Explore First](explore.md). ### Expanded/Full Workflow (custom selection) @@ -493,7 +493,7 @@ AI: Let me investigate your current setup and options... Your current stack suggests #1 or #2. What's your scale? ``` -Exploration clarifies thinking before you create artifacts. +Exploration clarifies thinking before any code gets written. ### Verify Before Archiving From 3bb77f4b75101787a4b18d8171a35c8504e1a15e Mon Sep 17 00:00:00 2001 From: Clay Good Date: Thu, 10 Sep 2026 13:29:40 -0500 Subject: [PATCH 2/4] test(docs): keep the retired explore wording retired A flat list of the phrasings that actually carried the claim, swept over the eleven pages that pitch explore. Fails on main with all sixteen offenders; clean on this branch. Modeled on test/vocabulary-sweep.test.ts, and deliberately a list rather than a grammar. An earlier draft built the grammar - section splitting, code-fence tracking, a conditional-marker exemption so "creates no artifacts unless you ask" would pass - and measured against realistic prose it was imprecise in both directions while returning the same verdict on the real input. The list has no exemption logic to get wrong, and any maintainer can extend it. Phrasings that are only wrong in the absolute ("writes nothing", "creates nothing") are left to review, since the conditional form of each is the wording the failure message recommends. Co-Authored-By: Claude Opus 5 --- test/explore-docs-claims.test.ts | 118 +++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 test/explore-docs-claims.test.ts diff --git a/test/explore-docs-claims.test.ts b/test/explore-docs-claims.test.ts new file mode 100644 index 0000000000..1047f00391 --- /dev/null +++ b/test/explore-docs-claims.test.ts @@ -0,0 +1,118 @@ +import { describe, expect, it } from 'vitest'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +// Sixteen lines across both documentation trees used to state that `explore` +// creates no artifacts and writes no files, full stop (#1833). That has been +// false since explore shipped (#467): the capture branch of +// `src/core/templates/workflows/explore.ts` writes the planning artifacts the +// user asked for, and can edit an existing change's artifacts. #1503 later +// made it scaffold with `openspec new change` first. What stays true is narrower: explore never writes code, +// and it writes nothing you did not ask for or agree to. +// +// This sweep keeps the absolute wording retired, the same way +// test/vocabulary-sweep.test.ts keeps the pre-rename store vocabulary retired. +// It is deliberately a flat list of phrasings over a named page list rather +// than a grammar for the claim. An earlier draft tried the grammar - section +// splitting, fence tracking, a conditional-marker exemption - and it was +// imprecise in both directions on realistic prose while returning the same +// verdict here. Phrasings that are only wrong in the absolute ("writes +// nothing", "creates nothing") are left to review, because "writes nothing +// unless you ask" is the wording this very test recommends. +const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); + +// The pages that pitch explore to a user. Both trees ship: `docs/` is what +// README links today, `docs-lab/` is what the documentation site publishes +// (`website/content/docs` is generated from it and is not committed). +const PAGES = [ + 'README.md', + 'docs/README.md', + 'docs/commands.md', + 'docs/examples.md', + 'docs/explore.md', + 'docs/faq.md', + 'docs/getting-started.md', + 'docs/glossary.md', + 'docs/overview.md', + 'docs/workflows.md', + 'docs-lab/start/quickstart.md', +]; + +/** + * Every phrasing that actually carried the claim, as a pattern. This list is + * grounded, not speculative: it is what the sixteen offending lines said. When + * a new phrasing appears, add it then. A guard does not have to be exhaustive + * to be worth having, and a literal list is the one thing every maintainer can + * extend correctly without reading a grammar. + */ +const FORBIDDEN: readonly RegExp[] = [ + // The capability form. + /\bno artifacts\b/i, + /\bno files\b/i, + /\bwrite any artifacts\b/i, + // Bullet-anchored: "It does not: - Create a change folder." The same words + // in a sentence ("it does not create a change folder on its own") are the + // correct wording, so only the unconditional bullet form is forbidden. + /^\s*[-*]\s+creates? a change folder\b/i, + // The timing form: true of the default path, false as a description of what + // explore can do. This is the shape that survived the first pass on #1833. + /\bbefore any artifact\b/i, + /\bbefore any change\b/i, + /\bbefore a single artifact\b/i, + /\bbefore anything is written\b/i, + /\bbefore you create artifacts\b/i, +]; + +/** Lines that contain a forbidden phrase without making the claim. */ +const ALLOW = [ + // docs/commands.md and docs/troubleshooting.md quote this CLI message. + /"No artifacts ready"/, +]; + +const TEST_FILE = 'test/explore-docs-claims.test.ts'; + +function findOffenders(page: string, content: string): string[] { + const offenders: string[] = []; + content.split(/\r?\n/).forEach((line, index) => { + if (ALLOW.some((allowed) => allowed.test(line))) return; + const match = FORBIDDEN.map((f) => f.exec(line)).find(Boolean); + if (match) { + offenders.push(`${page}:${index + 1}: "${match[0]}" in: ${line.trim()}`); + } + }); + return offenders; +} + +describe('explore documentation', () => { + it('never claims explore writes nothing at all (#1833)', () => { + const offenders = PAGES.flatMap((page) => { + const file = path.join(REPO_ROOT, ...page.split('/')); + if (!fs.existsSync(file)) return []; + return findOffenders(page, fs.readFileSync(file, 'utf-8')); + }); + + expect( + offenders, + 'These pages assert that explore writes nothing. It captures on ' + + 'request: `openspec new change` plus the planning artifacts you ' + + 'name. Keep "never writes code" and "nothing unless you ask, or say ' + + 'yes when it offers"; drop the absolute denial. If a line below is ' + + `not a claim about explore, add it to ALLOW in ${TEST_FILE}.` + + `\n\n${offenders.join('\n')}` + ).toEqual([]); + }); + + it('still documents the capture path on the explore guide (#1833)', () => { + // A forbidden-phrase list cannot tell "the correction was deleted" from + // "this page never mentioned capture". Pin the concept on the one page + // whose whole job is explaining what explore does. Concept, not phrasing: + // how the page words it is review's call, not this test's. + const guide = fs.readFileSync(path.join(REPO_ROOT, 'docs', 'explore.md'), 'utf-8'); + expect( + guide, + 'docs/explore.md should still describe capture - the change explore ' + + 'writes when you ask it to. See #1833.' + ).toMatch(/captur\w*/i); + }); +}); From e8cea49f0df649bd68324af131fdc1d0a726df64 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Tue, 15 Sep 2026 07:55:04 -0500 Subject: [PATCH 3/4] test(docs): pin the explore capture contract, not the word The guide check matched any "capture", so "explore automatically captures every artifact" passed. Both explore.md and commands.md now must name the user trigger, `openspec new change`, and the named-artifacts scope, with no capture line claiming it happens unprompted, and keep "never writes code". Also scope the explore.md guarantee to the setup files a new change needs, and make the commands.md offer name the change and its scope, which the template asks for on main and after #1832. Co-Authored-By: Claude Opus 5 --- docs/commands.md | 8 ++--- docs/explore.md | 4 +-- test/explore-docs-claims.test.ts | 52 ++++++++++++++++++++++++-------- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index f766379c90..c7611faf8e 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -122,13 +122,13 @@ AI: Let me investigate your current auth setup... You: Let's go with JWT. -AI: That's a decision worth keeping. Want me to start a change for it? +AI: That's a decision worth keeping. Want me to start a change called + add-jwt-auth? Just the change folder, nothing else yet. You: Yes. -AI: Started openspec/changes/add-jwt-auth/ - change metadata only, no - artifacts yet. Say the word and I'll write the proposal, specs, - and tasks from what we just worked out. +AI: Started openspec/changes/add-jwt-auth/. Say the word and I'll + write the proposal, specs, and tasks from what we just worked out. ``` **Tips:** diff --git a/docs/explore.md b/docs/explore.md index 7c667d5440..ef69c4c220 100644 --- a/docs/explore.md +++ b/docs/explore.md @@ -33,7 +33,7 @@ Explore is a **conversation**, not a generator. **It does not:** - Write or modify code. Explore never writes code, on any path, capture included. - Design or edit your schemas or templates. Shaping those is a change, not thinking. -- Start a change or write an artifact on its own. It writes nothing unless you ask, or say yes when it offers, and then only what you agreed to. +- Start a change or write an artifact on its own. It writes nothing unless you ask, or say yes when it offers, and then only what you agreed to, plus the setup files starting a change needs (see below). - Push you toward capturing. It offers when the thinking crystallizes; you decide. That's the point. Exploring costs you nothing and commits you to nothing until you say so. You can explore three dead ends, learn something from each, and only then propose the path that survived. @@ -99,7 +99,7 @@ You can say it in plain language ("let's turn this into a change") or run `/opsx You can also ask explore to capture the change itself, without leaving the conversation: "start a change for this" scaffolds the folder, and "write the proposal too" writes exactly the artifacts you named. Scaffolding also lays down the change's own metadata, and fills in anything your project is missing at the top level (`openspec/specs/`, `openspec/changes/archive/`, a `config.yaml`). -That's the same destination as handing off, with one difference: propose writes the whole set your schema requires to reach implementation, while capture writes only what you named. +That's the same destination as handing off, with one difference: propose writes the whole set your schema requires to reach implementation, while capture writes only the artifacts you named. If you use the expanded command set, explore can hand off to `/opsx:new` instead, for step-by-step artifact creation. See [Workflows](workflows.md). diff --git a/test/explore-docs-claims.test.ts b/test/explore-docs-claims.test.ts index 1047f00391..c1bdb55bc6 100644 --- a/test/explore-docs-claims.test.ts +++ b/test/explore-docs-claims.test.ts @@ -103,16 +103,44 @@ describe('explore documentation', () => { ).toEqual([]); }); - it('still documents the capture path on the explore guide (#1833)', () => { - // A forbidden-phrase list cannot tell "the correction was deleted" from - // "this page never mentioned capture". Pin the concept on the one page - // whose whole job is explaining what explore does. Concept, not phrasing: - // how the page words it is review's call, not this test's. - const guide = fs.readFileSync(path.join(REPO_ROOT, 'docs', 'explore.md'), 'utf-8'); - expect( - guide, - 'docs/explore.md should still describe capture - the change explore ' + - 'writes when you ask it to. See #1833.' - ).toMatch(/captur\w*/i); - }); + // A forbidden-phrase list cannot tell "the correction was deleted" from + // "this page never mentioned capture", and a bare /captur/ match passes on + // "explore automatically captures every artifact". So pin the contract, not + // the word, on the two pages that describe capture in full: some capture + // line names the user's trigger, the `openspec new change` path, and the + // named-artifacts scope; no capture line claims explore does it unprompted + // or wholesale; and the page keeps the never-writes-code guarantee. + // Wording stays review's call: the user trigger covers both "when you ask" + // and a yes to explore's offer, which is true before and after #1832. + const USER_TRIGGER = /\b(you ask|accept its offer|say yes)\b/i; + const NAMED_SCOPE = /\bartifacts you name(d)?\b/i; + const UNPROMPTED = /\b(automatically|on its own|without (asking|confirmation)|(every|all) (planning )?artifacts)\b/i; + + for (const page of ['docs/explore.md', 'docs/commands.md']) { + it(`${page} still documents the user-requested capture contract (#1833)`, () => { + const content = fs.readFileSync(path.join(REPO_ROOT, ...page.split('/')), 'utf-8'); + const captureLines = content.split(/\r?\n/).filter((line) => /\bcaptur/i.test(line)); + + expect( + captureLines.some( + (line) => + USER_TRIGGER.test(line) && /openspec new change/.test(line) && NAMED_SCOPE.test(line) + ), + `${page} should describe capture as something explore does when you ask ` + + 'or say yes to its offer: `openspec new change` plus the artifacts you ' + + 'name. See #1833.' + ).toBe(true); + + const unprompted = captureLines.filter((line) => UNPROMPTED.test(line)); + expect( + unprompted, + `${page} describes capture as happening unprompted or wholesale. It ` + + 'writes only what the user asked for or agreed to.' + ).toEqual([]); + + expect(content, `${page} should keep the never-writes-code guarantee.`).toMatch( + /never writes code/i + ); + }); + } }); From 879279b6aaf9b17a0be952d8d652826f1ca44cd4 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Tue, 15 Sep 2026 08:16:02 -0500 Subject: [PATCH 4/4] test(docs): accept negated unprompted-capture wording, catch non-capture verbs The unprompted check matched "automatically" on any capture line, so the correct "Explore does not automatically capture artifacts" failed, while "Explore automatically writes planning artifacts" was never scanned because it lacks the word capture. Check each clause of lines naming explore or capture for an unprompted write verb with no preceding negation. Co-Authored-By: Claude Opus 5 --- test/explore-docs-claims.test.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/test/explore-docs-claims.test.ts b/test/explore-docs-claims.test.ts index c1bdb55bc6..287a4b083b 100644 --- a/test/explore-docs-claims.test.ts +++ b/test/explore-docs-claims.test.ts @@ -115,11 +115,29 @@ describe('explore documentation', () => { const USER_TRIGGER = /\b(you ask|accept its offer|say yes)\b/i; const NAMED_SCOPE = /\bartifacts you name(d)?\b/i; const UNPROMPTED = /\b(automatically|on its own|without (asking|confirmation)|(every|all) (planning )?artifacts)\b/i; + const NEGATION = /\b(not|never|no|doesn't|don't|won't)\b/i; + + // An unprompted claim about explore writing, checked clause by clause so + // "does not automatically capture" passes and "automatically captures every + // artifact, but never writes code" still fails. Only lines naming explore or + // capture count: /opsx:ff legitimately "creates all planning artifacts". + function claimsUnpromptedWrite(line: string): boolean { + if (!/\b(explore|captur)/i.test(line)) return false; + return line.split(/[.,;:!?]|\bbut\b/i).some((clause) => { + const match = UNPROMPTED.exec(clause); + return ( + match !== null && + /\b(captur|creat|edit|writ|scaffold)/i.test(clause) && + !NEGATION.test(clause.slice(0, match.index)) + ); + }); + } for (const page of ['docs/explore.md', 'docs/commands.md']) { it(`${page} still documents the user-requested capture contract (#1833)`, () => { const content = fs.readFileSync(path.join(REPO_ROOT, ...page.split('/')), 'utf-8'); - const captureLines = content.split(/\r?\n/).filter((line) => /\bcaptur/i.test(line)); + const lines = content.split(/\r?\n/); + const captureLines = lines.filter((line) => /\bcaptur/i.test(line)); expect( captureLines.some( @@ -131,7 +149,7 @@ describe('explore documentation', () => { 'name. See #1833.' ).toBe(true); - const unprompted = captureLines.filter((line) => UNPROMPTED.test(line)); + const unprompted = lines.filter(claimsUnpromptedWrite); expect( unprompted, `${page} describes capture as happening unprompted or wholesale. It ` +