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..c7611faf8e 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 called + add-jwt-auth? Just the change folder, nothing else yet. + +You: Yes. + +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:** - 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..ef69c4c220 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, 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. 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 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). ## 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 diff --git a/test/explore-docs-claims.test.ts b/test/explore-docs-claims.test.ts new file mode 100644 index 0000000000..287a4b083b --- /dev/null +++ b/test/explore-docs-claims.test.ts @@ -0,0 +1,164 @@ +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([]); + }); + + // 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; + 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 lines = content.split(/\r?\n/); + const captureLines = lines.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 = lines.filter(claimsUnpromptedWrite); + 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 + ); + }); + } +});