From 157c49d6d012a0e9aa24a5a0419cb51ccbc2ddcc Mon Sep 17 00:00:00 2001 From: Clay Good Date: Wed, 19 Aug 2026 10:34:02 -0500 Subject: [PATCH 01/15] fix(guidance): teach the spec-inventory verb to generated guidance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `openspec list --specs` appeared in no generated skill, command, or artifact instruction, while `openspec list --json` — the in-flight CHANGE list — appeared throughout. An agent asked to read the existing specs first reached for the one enumeration verb it had been taught, got the change list, found it plausible, and reported the step complete against the wrong object. Explore now lists the spec inventory alongside the change list and says which is which. The spec-driven `proposal` and `specs` instructions name the command at the two points that need it: researching existing capabilities before filling in the Capabilities section, and confirming a delta's path matches an existing capability. Guidance text only — no CLI, parser, or archive behavior changes. Closes #1689 Co-Authored-By: Claude Opus 5 --- .changeset/teach-spec-inventory-verb.md | 5 ++ schemas/spec-driven/schema.yaml | 11 ++- skills/openspec-explore/SKILL.md | 6 ++ src/core/templates/workflows/explore.ts | 12 +++ .../templates/skill-templates-parity.test.ts | 6 +- test/core/templates/spec-inventory.test.ts | 79 +++++++++++++++++++ 6 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 .changeset/teach-spec-inventory-verb.md create mode 100644 test/core/templates/spec-inventory.test.ts diff --git a/.changeset/teach-spec-inventory-verb.md b/.changeset/teach-spec-inventory-verb.md new file mode 100644 index 0000000000..0f215b693a --- /dev/null +++ b/.changeset/teach-spec-inventory-verb.md @@ -0,0 +1,5 @@ +--- +"@fission-ai/openspec": patch +--- + +Teach the generated guidance how to list a project's specs. `openspec list --specs` appeared in no generated skill, command, or artifact instruction, while `openspec list --json` (the in-flight *change* list) appeared throughout, so an agent asked to read the existing specs first enumerated changes instead and reported the step complete against the wrong object. The explore skill and command now list the spec inventory alongside the change list and say which is which, and the spec-driven `proposal` and `specs` instructions name the command where they ask for existing capabilities to be researched and for a delta's path to match an existing one. Fixes #1689. diff --git a/schemas/spec-driven/schema.yaml b/schemas/spec-driven/schema.yaml index ae4d9eb336..c7eebbbbf1 100644 --- a/schemas/spec-driven/schema.yaml +++ b/schemas/spec-driven/schema.yaml @@ -18,7 +18,14 @@ artifacts: - **Impact**: Affected code, APIs, dependencies, or systems. IMPORTANT: The Capabilities section is critical. It creates the contract between - proposal and specs phases. Research existing specs before filling this in. + proposal and specs phases. Research existing specs before filling this in: + run `openspec list --specs` for the project's capability inventory (add + `--json` for ids and requirement counts, and `--store ""` when a + registered store is selected), then read + `openspec/specs//spec.md` for any that look related. + `openspec list` without `--specs` lists in-flight changes, not specs - it + never shows what the project already covers. Reuse an existing + capability's exact path instead of introducing a near-duplicate name. Each capability listed here will need a corresponding spec file. Every change must either declare at least one capability (new or @@ -63,7 +70,7 @@ artifacts: `` is the spec directory relative to `specs/` (for example, `user-auth` or `identity/user-auth`). Preserve the full path: - New capabilities: use the exact path from the proposal at `specs//spec.md`. Any path segment newly introduced in the proposal must be kebab-case. Follow the project's existing organization; do not add a new domain level when the project uses a flat layout. - - Modified capabilities: use the exact existing path from `openspec/specs//` when creating the delta at `specs//spec.md`. Do not move or rename the capability. + - Modified capabilities: use the exact existing path from `openspec/specs//` when creating the delta at `specs//spec.md`. Run `openspec list --specs` to confirm that path before writing the delta - a mistyped or invented path targets a capability that does not exist rather than the one you meant. Do not move or rename the capability. There must be at least one spec file unless the change's `.openspec.yaml` sets `skip_specs: true` (no spec-level behavior change) - `openspec validate` diff --git a/skills/openspec-explore/SKILL.md b/skills/openspec-explore/SKILL.md index 327c261489..d5493e5a37 100644 --- a/skills/openspec-explore/SKILL.md +++ b/skills/openspec-explore/SKILL.md @@ -93,6 +93,12 @@ This tells you: - Their names, schemas, and status - What the user might be working on +That is the *change* list - work in flight. It does not include the project's durable capabilities, so list those too: +```bash +openspec list --specs +``` +Add `--json` for ids and requirement counts, and `--store ""` when a registered store is selected. This is the inventory of what the project already claims to do, and `openspec list` on its own never shows it. Read `/openspec/specs//spec.md` for any capability that looks related before proposing new behavior. + Then read the project's own context from the resolved root - `/openspec/config.yaml` (or `config.yml`). Use the `root.path` returned above, and skip this if neither file exists: - `context`: project background - tech stack, conventions, constraints - `rules`: keyed by artifact id - the entries for an artifact apply only when you write that artifact diff --git a/src/core/templates/workflows/explore.ts b/src/core/templates/workflows/explore.ts index 211de65646..ccfd8c8cfd 100644 --- a/src/core/templates/workflows/explore.ts +++ b/src/core/templates/workflows/explore.ts @@ -95,6 +95,12 @@ This tells you: - Their names, schemas, and status - What the user might be working on +That is the *change* list - work in flight. It does not include the project's durable capabilities, so list those too: +\`\`\`bash +openspec list --specs +\`\`\` +Add \`--json\` for ids and requirement counts, and \`--store ""\` when a registered store is selected. This is the inventory of what the project already claims to do, and \`openspec list\` on its own never shows it. Read \`/openspec/specs//spec.md\` for any capability that looks related before proposing new behavior. + Then read the project's own context from the resolved root - \`/openspec/config.yaml\` (or \`config.yml\`). Use the \`root.path\` returned above, and skip this if neither file exists: - \`context\`: project background - tech stack, conventions, constraints - \`rules\`: keyed by artifact id - the entries for an artifact apply only when you write that artifact @@ -410,6 +416,12 @@ This tells you: - Their names, schemas, and status - What the user might be working on +That is the *change* list - work in flight. It does not include the project's durable capabilities, so list those too: +\`\`\`bash +openspec list --specs +\`\`\` +Add \`--json\` for ids and requirement counts, and \`--store ""\` when a registered store is selected. This is the inventory of what the project already claims to do, and \`openspec list\` on its own never shows it. Read \`/openspec/specs//spec.md\` for any capability that looks related before proposing new behavior. + Then read the project's own context from the resolved root - \`/openspec/config.yaml\` (or \`config.yml\`). Use the \`root.path\` returned above, and skip this if neither file exists: - \`context\`: project background - tech stack, conventions, constraints - \`rules\`: keyed by artifact id - the entries for an artifact apply only when you write that artifact diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index decad7b0b5..57495ec2a4 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -38,14 +38,14 @@ import { import { STORE_SELECTION_GUIDANCE } from '../../../src/core/templates/workflows/store-selection.js'; const EXPECTED_FUNCTION_HASHES: Record = { - getExploreSkillTemplate: '3efc37cddf342318ac37be7bb4ff5915f454b4c5bb127294ebdc7534ee21aa23', + getExploreSkillTemplate: '64e2f121985877941f6a1e4164810c338af5cde1fec2a7c28e58012738a2ad25', getNewChangeSkillTemplate: 'eabd1e895c5881dcb17dcbaa3fb26098dd59e8eacb318e400820b4dc811ef781', getContinueChangeSkillTemplate: '012136f6411a99c8fa228e2f9444cb64b0a89e0f56fdeac2fe03b2f5bee0c5d7', getApplyChangeSkillTemplate: 'd1e7d5ceb85193c0964057dbb88e9651526754bd33f84020e2440ff0621d5dbb', getFfChangeSkillTemplate: '5501740e7ec36ab23ab8c3a0d6dd0655a5e2f35433c7b90e82904fef5e7a326a', getSyncSpecsSkillTemplate: 'b099e2ff31859c9b10d928066e662524f9aad9ecf2be12fceacb732d718c4146', getOnboardSkillTemplate: '29b1d825179cff92fbc7b790694c1baef138575ea3de56848715e27d7e367946', - getOpsxExploreCommandTemplate: 'd2f70d11588f902c15c1e5ce9908cc4124c6b82fe78dc766ac5c3599c9e2a6f1', + getOpsxExploreCommandTemplate: '056249b459e124f4e347592489a3adac6b22b4b3a544b7158f0147306e9527b0', getOpsxNewCommandTemplate: 'f2d30e569798a4c92ba932859d6ba4e0ad10e18feccbade1cfee0957597b3463', getOpsxContinueCommandTemplate: 'e50e50266efa1b8e64ff9b6274ee8254f0a240d6adc1b862d126e2f1c9d3a559', getOpsxApplyCommandTemplate: 'e3579ac78f2e2c75fa3d3a7ac7dc3e49c395e96f7323398f0f041d94f8de9bb0', @@ -66,7 +66,7 @@ const EXPECTED_FUNCTION_HASHES: Record = { }; const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { - 'openspec-explore': '4d9736372cc1faf8a5d8a66395a95bf77b9f3fcd2cda40411ad1db6927e8066a', + 'openspec-explore': '5c848086e19d6b31e217dc7b4d821af0f52e78964cf9d25946e0091541362edc', 'openspec-new-change': 'ec4529beef978e34634a6f7286fab55d68fad8fb374dceb45691d52caab33fbb', 'openspec-continue-change': 'bb6194a16c54891cdb253678e8f70ce53b2af86735243980f366ce551d37e42e', 'openspec-apply-change': '81ea96d9fa6ec8536cd23c1fe561ed28e1cc1cad0a8ceb700588e08974cc0e49', diff --git a/test/core/templates/spec-inventory.test.ts b/test/core/templates/spec-inventory.test.ts new file mode 100644 index 0000000000..7c595d6e0a --- /dev/null +++ b/test/core/templates/spec-inventory.test.ts @@ -0,0 +1,79 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; +import { describe, expect, it } from 'vitest'; + +import { + getSkillTemplates, + getCommandTemplates, +} from '../../../src/core/shared/skill-generation.js'; +import { + getExploreSkillTemplate, + getOpsxExploreCommandTemplate, +} from '../../../src/core/templates/skill-templates.js'; +import { loadSchema } from '../../../src/core/artifact-graph/schema.js'; + +// #1689: 1.9.0 removed openspec/AGENTS.md, which carried the spec index, and +// nothing that replaced it ever named the verb that lists specs. Measured +// across one repo's generated surfaces: `openspec list --json` (the CHANGE +// list) appeared 10 times, `openspec list --specs` zero times. An agent told +// to "read the existing specs first" reaches for the one enumeration verb it +// was taught, gets the in-flight change list, and reports the step complete +// against the wrong object. +const SPEC_INVENTORY = 'openspec list --specs'; + +const repoRoot = path.resolve(fileURLToPath(new URL('.', import.meta.url)), '../../..'); +const defaultSchema = loadSchema(path.join(repoRoot, 'schemas', 'spec-driven', 'schema.yaml')); + +function instructionFor(artifactId: string): string { + const artifact = defaultSchema.artifacts.find(entry => entry.id === artifactId); + expect(artifact, `spec-driven has no "${artifactId}" artifact`).toBeDefined(); + const instruction = artifact?.instruction; + expect(instruction, `spec-driven "${artifactId}" has no instruction`).toBeDefined(); + return instruction as string; +} + +const exploreBodies: Array<[string, string]> = [ + ['explore skill', getExploreSkillTemplate().instructions], + ['explore command', getOpsxExploreCommandTemplate().content], +]; + +describe('spec inventory vocabulary (#1689)', () => { + it('teaches the spec-inventory verb somewhere in the generated surfaces', () => { + const bodies = [ + ...getSkillTemplates().map(entry => entry.template.instructions), + ...getCommandTemplates().map(entry => entry.template.content), + ]; + + const carriers = bodies.filter(body => body.includes(SPEC_INVENTORY)); + expect( + carriers.length, + `no generated skill or command names "${SPEC_INVENTORY}", so the spec inventory is unreachable by any path the tool teaches` + ).toBeGreaterThan(0); + }); + + it('names the spec inventory in explore, where the agent orients', () => { + for (const [label, body] of exploreBodies) { + expect(body, label).toContain(SPEC_INVENTORY); + } + }); + + it('distinguishes the change list from the spec inventory in explore', () => { + // Naming the command is not enough on its own: `openspec list` defaults to + // changes, so the two enumerations have to be told apart explicitly. + for (const [label, body] of exploreBodies) { + expect(body, label).toContain('openspec list --json'); + expect(body, label).toContain('`openspec list` on its own never shows it'); + } + }); + + it('names the spec inventory where the proposal picks capabilities', () => { + // "Research existing specs before filling this in" named no command, which + // is how the Capabilities section ends up inventing a near-duplicate + // capability instead of reusing the existing one. + expect(instructionFor('proposal')).toContain(SPEC_INVENTORY); + }); + + it('names the spec inventory where a delta must match an existing path', () => { + expect(instructionFor('specs')).toContain(SPEC_INVENTORY); + }); +}); From 68fb239abcb91d520204b0b2b2ba3c00fc99909b Mon Sep 17 00:00:00 2001 From: Clay Good Date: Wed, 19 Aug 2026 10:44:01 -0500 Subject: [PATCH 02/15] fix(guidance): carry the store qualifier wherever the command is named MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A bare `openspec list --specs` reads the local inventory, so under a selected store it confirms a capability path against the wrong root. The proposal instruction carried the qualifier; the modified-capability instruction did not. All four sites now use the same wording, and the guard is scoped to the passage that names the command — every explore body already carries the qualifier in its unrelated capture steps, so a whole-body assertion would pass with it dropped here. Addresses CodeRabbit review on #1700. Co-Authored-By: Claude Opus 5 --- schemas/spec-driven/schema.yaml | 6 ++--- skills/openspec-explore/SKILL.md | 2 +- src/core/templates/workflows/explore.ts | 4 +-- .../templates/skill-templates-parity.test.ts | 6 ++--- test/core/templates/spec-inventory.test.ts | 25 +++++++++++++++++++ 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/schemas/spec-driven/schema.yaml b/schemas/spec-driven/schema.yaml index c7eebbbbf1..f992113e8a 100644 --- a/schemas/spec-driven/schema.yaml +++ b/schemas/spec-driven/schema.yaml @@ -20,8 +20,8 @@ artifacts: IMPORTANT: The Capabilities section is critical. It creates the contract between proposal and specs phases. Research existing specs before filling this in: run `openspec list --specs` for the project's capability inventory (add - `--json` for ids and requirement counts, and `--store ""` when a - registered store is selected), then read + `--json` for ids and requirement counts, and `--store ""` only for a + registered standalone store), then read `openspec/specs//spec.md` for any that look related. `openspec list` without `--specs` lists in-flight changes, not specs - it never shows what the project already covers. Reuse an existing @@ -70,7 +70,7 @@ artifacts: `` is the spec directory relative to `specs/` (for example, `user-auth` or `identity/user-auth`). Preserve the full path: - New capabilities: use the exact path from the proposal at `specs//spec.md`. Any path segment newly introduced in the proposal must be kebab-case. Follow the project's existing organization; do not add a new domain level when the project uses a flat layout. - - Modified capabilities: use the exact existing path from `openspec/specs//` when creating the delta at `specs//spec.md`. Run `openspec list --specs` to confirm that path before writing the delta - a mistyped or invented path targets a capability that does not exist rather than the one you meant. Do not move or rename the capability. + - Modified capabilities: use the exact existing path from `openspec/specs//` when creating the delta at `specs//spec.md`. Run `openspec list --specs` to confirm that path before writing the delta, appending `--store ""` only for a registered standalone store - a mistyped or invented path targets a capability that does not exist rather than the one you meant. Do not move or rename the capability. There must be at least one spec file unless the change's `.openspec.yaml` sets `skip_specs: true` (no spec-level behavior change) - `openspec validate` diff --git a/skills/openspec-explore/SKILL.md b/skills/openspec-explore/SKILL.md index d5493e5a37..4001e04038 100644 --- a/skills/openspec-explore/SKILL.md +++ b/skills/openspec-explore/SKILL.md @@ -97,7 +97,7 @@ That is the *change* list - work in flight. It does not include the project's du ```bash openspec list --specs ``` -Add `--json` for ids and requirement counts, and `--store ""` when a registered store is selected. This is the inventory of what the project already claims to do, and `openspec list` on its own never shows it. Read `/openspec/specs//spec.md` for any capability that looks related before proposing new behavior. +Add `--json` for ids and requirement counts, and append `--store ""` only for a registered standalone store. This is the inventory of what the project already claims to do, and `openspec list` on its own never shows it. Read `/openspec/specs//spec.md` for any capability that looks related before proposing new behavior. Then read the project's own context from the resolved root - `/openspec/config.yaml` (or `config.yml`). Use the `root.path` returned above, and skip this if neither file exists: - `context`: project background - tech stack, conventions, constraints diff --git a/src/core/templates/workflows/explore.ts b/src/core/templates/workflows/explore.ts index ccfd8c8cfd..3f319f0eda 100644 --- a/src/core/templates/workflows/explore.ts +++ b/src/core/templates/workflows/explore.ts @@ -99,7 +99,7 @@ That is the *change* list - work in flight. It does not include the project's du \`\`\`bash openspec list --specs \`\`\` -Add \`--json\` for ids and requirement counts, and \`--store ""\` when a registered store is selected. This is the inventory of what the project already claims to do, and \`openspec list\` on its own never shows it. Read \`/openspec/specs//spec.md\` for any capability that looks related before proposing new behavior. +Add \`--json\` for ids and requirement counts, and append \`--store ""\` only for a registered standalone store. This is the inventory of what the project already claims to do, and \`openspec list\` on its own never shows it. Read \`/openspec/specs//spec.md\` for any capability that looks related before proposing new behavior. Then read the project's own context from the resolved root - \`/openspec/config.yaml\` (or \`config.yml\`). Use the \`root.path\` returned above, and skip this if neither file exists: - \`context\`: project background - tech stack, conventions, constraints @@ -420,7 +420,7 @@ That is the *change* list - work in flight. It does not include the project's du \`\`\`bash openspec list --specs \`\`\` -Add \`--json\` for ids and requirement counts, and \`--store ""\` when a registered store is selected. This is the inventory of what the project already claims to do, and \`openspec list\` on its own never shows it. Read \`/openspec/specs//spec.md\` for any capability that looks related before proposing new behavior. +Add \`--json\` for ids and requirement counts, and append \`--store ""\` only for a registered standalone store. This is the inventory of what the project already claims to do, and \`openspec list\` on its own never shows it. Read \`/openspec/specs//spec.md\` for any capability that looks related before proposing new behavior. Then read the project's own context from the resolved root - \`/openspec/config.yaml\` (or \`config.yml\`). Use the \`root.path\` returned above, and skip this if neither file exists: - \`context\`: project background - tech stack, conventions, constraints diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 57495ec2a4..83d902b19d 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -38,14 +38,14 @@ import { import { STORE_SELECTION_GUIDANCE } from '../../../src/core/templates/workflows/store-selection.js'; const EXPECTED_FUNCTION_HASHES: Record = { - getExploreSkillTemplate: '64e2f121985877941f6a1e4164810c338af5cde1fec2a7c28e58012738a2ad25', + getExploreSkillTemplate: 'e3940891ca8334f94be4f33f3d680005728108e4eb8e1ed02cf5da42ab24500b', getNewChangeSkillTemplate: 'eabd1e895c5881dcb17dcbaa3fb26098dd59e8eacb318e400820b4dc811ef781', getContinueChangeSkillTemplate: '012136f6411a99c8fa228e2f9444cb64b0a89e0f56fdeac2fe03b2f5bee0c5d7', getApplyChangeSkillTemplate: 'd1e7d5ceb85193c0964057dbb88e9651526754bd33f84020e2440ff0621d5dbb', getFfChangeSkillTemplate: '5501740e7ec36ab23ab8c3a0d6dd0655a5e2f35433c7b90e82904fef5e7a326a', getSyncSpecsSkillTemplate: 'b099e2ff31859c9b10d928066e662524f9aad9ecf2be12fceacb732d718c4146', getOnboardSkillTemplate: '29b1d825179cff92fbc7b790694c1baef138575ea3de56848715e27d7e367946', - getOpsxExploreCommandTemplate: '056249b459e124f4e347592489a3adac6b22b4b3a544b7158f0147306e9527b0', + getOpsxExploreCommandTemplate: 'b4284eca8d95411c08b542f54c9be2b8451b53ab3c45ead74b95be2030f88a4e', getOpsxNewCommandTemplate: 'f2d30e569798a4c92ba932859d6ba4e0ad10e18feccbade1cfee0957597b3463', getOpsxContinueCommandTemplate: 'e50e50266efa1b8e64ff9b6274ee8254f0a240d6adc1b862d126e2f1c9d3a559', getOpsxApplyCommandTemplate: 'e3579ac78f2e2c75fa3d3a7ac7dc3e49c395e96f7323398f0f041d94f8de9bb0', @@ -66,7 +66,7 @@ const EXPECTED_FUNCTION_HASHES: Record = { }; const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { - 'openspec-explore': '5c848086e19d6b31e217dc7b4d821af0f52e78964cf9d25946e0091541362edc', + 'openspec-explore': 'db652b920755f2a3a4466050087b55dfe60c57052eef09afda49ddf32905c77b', 'openspec-new-change': 'ec4529beef978e34634a6f7286fab55d68fad8fb374dceb45691d52caab33fbb', 'openspec-continue-change': 'bb6194a16c54891cdb253678e8f70ce53b2af86735243980f366ce551d37e42e', 'openspec-apply-change': '81ea96d9fa6ec8536cd23c1fe561ed28e1cc1cad0a8ceb700588e08974cc0e49', diff --git a/test/core/templates/spec-inventory.test.ts b/test/core/templates/spec-inventory.test.ts index 7c595d6e0a..b0539a889e 100644 --- a/test/core/templates/spec-inventory.test.ts +++ b/test/core/templates/spec-inventory.test.ts @@ -76,4 +76,29 @@ describe('spec inventory vocabulary (#1689)', () => { it('names the spec inventory where a delta must match an existing path', () => { expect(instructionFor('specs')).toContain(SPEC_INVENTORY); }); + + // A bare `openspec list --specs` reads the local inventory, so under a + // selected store it confirms a capability path against the wrong root. + // Every site that names the command must carry the store qualifier with it. + it('carries the store qualifier everywhere it names the command', () => { + const sites: Array<[string, string]> = [ + ...exploreBodies, + ['proposal instruction', instructionFor('proposal')], + ['specs instruction', instructionFor('specs')], + ]; + + for (const [label, body] of sites) { + const start = body.indexOf(SPEC_INVENTORY); + expect(start, label).toBeGreaterThanOrEqual(0); + + // Scoped to the passage that names the command: every explore body + // already carries the store qualifier in its unrelated capture steps, + // so a whole-body match would pass even with the qualifier dropped here. + const passage = body.slice(start, start + 400); + expect(passage, `${label} names the command without its store qualifier`).toContain( + 'registered standalone store' + ); + expect(passage, label).toContain('--store ""'); + } + }); }); From 0e0b894cadf39479db367645fdee02de7dadca05 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Wed, 19 Aug 2026 11:00:44 -0500 Subject: [PATCH 03/15] fix(guidance): read a listed capability with the store-aware command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The read step I added defeated the fix under a store. It told the agent to list the inventory with `--store ""`, then read the result back from `openspec/specs//spec.md` — a local path. Verified against a registered store: `list --specs --store mystore` returns `store-only-capability`, and the corresponding local read fails outright (or, when a local capability happens to share the name, silently returns a different one). That is the same wrong-object failure #1689 is about, reintroduced one line later. Capabilities are now read with `openspec show "" --type spec --json --no-scenarios`, which resolves against the same root the listing came from and returns purpose plus requirement texts without pulling whole spec files into context. `--type spec` is load-bearing: a change and a spec sharing a name is an ambiguous_item error, and change names routinely mirror capability names. Also documents `--store` on `list` and `show` in docs/cli.md. Both already accepted the flag — the prose at line 228 says so — but neither options table listed it. Co-Authored-By: Claude Opus 5 --- .changeset/teach-spec-inventory-verb.md | 2 +- docs/cli.md | 2 ++ schemas/spec-driven/schema.yaml | 14 ++++++---- skills/openspec-explore/SKILL.md | 2 +- src/core/templates/workflows/explore.ts | 4 +-- .../templates/skill-templates-parity.test.ts | 6 ++-- test/core/templates/spec-inventory.test.ts | 28 ++++++++++++++++++- 7 files changed, 44 insertions(+), 14 deletions(-) diff --git a/.changeset/teach-spec-inventory-verb.md b/.changeset/teach-spec-inventory-verb.md index 0f215b693a..787a046261 100644 --- a/.changeset/teach-spec-inventory-verb.md +++ b/.changeset/teach-spec-inventory-verb.md @@ -2,4 +2,4 @@ "@fission-ai/openspec": patch --- -Teach the generated guidance how to list a project's specs. `openspec list --specs` appeared in no generated skill, command, or artifact instruction, while `openspec list --json` (the in-flight *change* list) appeared throughout, so an agent asked to read the existing specs first enumerated changes instead and reported the step complete against the wrong object. The explore skill and command now list the spec inventory alongside the change list and say which is which, and the spec-driven `proposal` and `specs` instructions name the command where they ask for existing capabilities to be researched and for a delta's path to match an existing one. Fixes #1689. +Teach the generated guidance how to find and read a project's specs. `openspec list --specs` appeared in no generated skill, command, or artifact instruction, while `openspec list --json` (the in-flight *change* list) appeared throughout, so an agent asked to read the existing specs first enumerated changes instead and reported the step complete against the wrong object. The explore skill and command now list the spec inventory alongside the change list and say which is which, and the spec-driven `proposal` and `specs` instructions name the command where they ask for existing capabilities to be researched and for a delta's path to match an existing one. Both steps carry `--store ""`, and capabilities are read with `openspec show "" --type spec --json --no-scenarios` so the read resolves against the same root the listing came from. `docs/cli.md` now documents the `--store` option on `list` and `show`, which both already accepted it. Fixes #1689. diff --git a/docs/cli.md b/docs/cli.md index d17c6d662f..27396c7e39 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -439,6 +439,7 @@ openspec list [options] | `--changes` | List changes (default) | | `--sort ` | Sort by `recent` (default) or `name` | | `--json` | Output as JSON | +| `--store ` | Use a registered store as the OpenSpec root | **Examples:** @@ -495,6 +496,7 @@ openspec show [item-name] [options] | `--type ` | Specify type: `change` or `spec` (auto-detected if unambiguous) | | `--json` | Output as JSON | | `--no-interactive` | Disable prompts | +| `--store ` | Use a registered store as the OpenSpec root | **Change-specific options:** diff --git a/schemas/spec-driven/schema.yaml b/schemas/spec-driven/schema.yaml index f992113e8a..37fec8bb90 100644 --- a/schemas/spec-driven/schema.yaml +++ b/schemas/spec-driven/schema.yaml @@ -19,12 +19,14 @@ artifacts: IMPORTANT: The Capabilities section is critical. It creates the contract between proposal and specs phases. Research existing specs before filling this in: - run `openspec list --specs` for the project's capability inventory (add - `--json` for ids and requirement counts, and `--store ""` only for a - registered standalone store), then read - `openspec/specs//spec.md` for any that look related. - `openspec list` without `--specs` lists in-flight changes, not specs - it - never shows what the project already covers. Reuse an existing + run `openspec list --specs` for the project's capability inventory, then + `openspec show "" --type spec --json --no-scenarios` for any that + look related - that returns a capability's purpose and requirement texts + without pulling whole spec files into context. Append `--store ""` to + both commands only for a registered standalone store, and keep `--type + spec`: a change and a spec sharing a name is otherwise an ambiguous-item + error. `openspec list` without `--specs` lists in-flight changes, not + specs - it never shows what the project already covers. Reuse an existing capability's exact path instead of introducing a near-duplicate name. Each capability listed here will need a corresponding spec file. diff --git a/skills/openspec-explore/SKILL.md b/skills/openspec-explore/SKILL.md index 4001e04038..d3ae07b9ff 100644 --- a/skills/openspec-explore/SKILL.md +++ b/skills/openspec-explore/SKILL.md @@ -97,7 +97,7 @@ That is the *change* list - work in flight. It does not include the project's du ```bash openspec list --specs ``` -Add `--json` for ids and requirement counts, and append `--store ""` only for a registered standalone store. This is the inventory of what the project already claims to do, and `openspec list` on its own never shows it. Read `/openspec/specs//spec.md` for any capability that looks related before proposing new behavior. +Add `--json` for ids and requirement counts, and append `--store ""` only for a registered standalone store. This is the inventory of what the project already claims to do, and `openspec list` on its own never shows it. To look at one, run `openspec show "" --type spec --json --no-scenarios` (same `--store` rule) - it returns that capability's purpose and requirement texts without pulling the whole spec file into context, and `--type spec` stops a change of the same name from making it ambiguous. Then read the project's own context from the resolved root - `/openspec/config.yaml` (or `config.yml`). Use the `root.path` returned above, and skip this if neither file exists: - `context`: project background - tech stack, conventions, constraints diff --git a/src/core/templates/workflows/explore.ts b/src/core/templates/workflows/explore.ts index 3f319f0eda..2ea55de258 100644 --- a/src/core/templates/workflows/explore.ts +++ b/src/core/templates/workflows/explore.ts @@ -99,7 +99,7 @@ That is the *change* list - work in flight. It does not include the project's du \`\`\`bash openspec list --specs \`\`\` -Add \`--json\` for ids and requirement counts, and append \`--store ""\` only for a registered standalone store. This is the inventory of what the project already claims to do, and \`openspec list\` on its own never shows it. Read \`/openspec/specs//spec.md\` for any capability that looks related before proposing new behavior. +Add \`--json\` for ids and requirement counts, and append \`--store ""\` only for a registered standalone store. This is the inventory of what the project already claims to do, and \`openspec list\` on its own never shows it. To look at one, run \`openspec show "" --type spec --json --no-scenarios\` (same \`--store\` rule) - it returns that capability's purpose and requirement texts without pulling the whole spec file into context, and \`--type spec\` stops a change of the same name from making it ambiguous. Then read the project's own context from the resolved root - \`/openspec/config.yaml\` (or \`config.yml\`). Use the \`root.path\` returned above, and skip this if neither file exists: - \`context\`: project background - tech stack, conventions, constraints @@ -420,7 +420,7 @@ That is the *change* list - work in flight. It does not include the project's du \`\`\`bash openspec list --specs \`\`\` -Add \`--json\` for ids and requirement counts, and append \`--store ""\` only for a registered standalone store. This is the inventory of what the project already claims to do, and \`openspec list\` on its own never shows it. Read \`/openspec/specs//spec.md\` for any capability that looks related before proposing new behavior. +Add \`--json\` for ids and requirement counts, and append \`--store ""\` only for a registered standalone store. This is the inventory of what the project already claims to do, and \`openspec list\` on its own never shows it. To look at one, run \`openspec show "" --type spec --json --no-scenarios\` (same \`--store\` rule) - it returns that capability's purpose and requirement texts without pulling the whole spec file into context, and \`--type spec\` stops a change of the same name from making it ambiguous. Then read the project's own context from the resolved root - \`/openspec/config.yaml\` (or \`config.yml\`). Use the \`root.path\` returned above, and skip this if neither file exists: - \`context\`: project background - tech stack, conventions, constraints diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 83d902b19d..da684e35f8 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -38,14 +38,14 @@ import { import { STORE_SELECTION_GUIDANCE } from '../../../src/core/templates/workflows/store-selection.js'; const EXPECTED_FUNCTION_HASHES: Record = { - getExploreSkillTemplate: 'e3940891ca8334f94be4f33f3d680005728108e4eb8e1ed02cf5da42ab24500b', + getExploreSkillTemplate: '8dcf8890d5e7e44c379b7c019fd5e496cf469f8350121cce1d5583d4d54ce047', getNewChangeSkillTemplate: 'eabd1e895c5881dcb17dcbaa3fb26098dd59e8eacb318e400820b4dc811ef781', getContinueChangeSkillTemplate: '012136f6411a99c8fa228e2f9444cb64b0a89e0f56fdeac2fe03b2f5bee0c5d7', getApplyChangeSkillTemplate: 'd1e7d5ceb85193c0964057dbb88e9651526754bd33f84020e2440ff0621d5dbb', getFfChangeSkillTemplate: '5501740e7ec36ab23ab8c3a0d6dd0655a5e2f35433c7b90e82904fef5e7a326a', getSyncSpecsSkillTemplate: 'b099e2ff31859c9b10d928066e662524f9aad9ecf2be12fceacb732d718c4146', getOnboardSkillTemplate: '29b1d825179cff92fbc7b790694c1baef138575ea3de56848715e27d7e367946', - getOpsxExploreCommandTemplate: 'b4284eca8d95411c08b542f54c9be2b8451b53ab3c45ead74b95be2030f88a4e', + getOpsxExploreCommandTemplate: '3bf4bf57784166ed04be68946a4c45e8df629f69d4d52f57f4a863fa69a5ce1d', getOpsxNewCommandTemplate: 'f2d30e569798a4c92ba932859d6ba4e0ad10e18feccbade1cfee0957597b3463', getOpsxContinueCommandTemplate: 'e50e50266efa1b8e64ff9b6274ee8254f0a240d6adc1b862d126e2f1c9d3a559', getOpsxApplyCommandTemplate: 'e3579ac78f2e2c75fa3d3a7ac7dc3e49c395e96f7323398f0f041d94f8de9bb0', @@ -66,7 +66,7 @@ const EXPECTED_FUNCTION_HASHES: Record = { }; const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { - 'openspec-explore': 'db652b920755f2a3a4466050087b55dfe60c57052eef09afda49ddf32905c77b', + 'openspec-explore': 'd5b0a30e36edaf353d30562ca304ee8145c26e0d8b935221bc233755e9da91b7', 'openspec-new-change': 'ec4529beef978e34634a6f7286fab55d68fad8fb374dceb45691d52caab33fbb', 'openspec-continue-change': 'bb6194a16c54891cdb253678e8f70ce53b2af86735243980f366ce551d37e42e', 'openspec-apply-change': '81ea96d9fa6ec8536cd23c1fe561ed28e1cc1cad0a8ceb700588e08974cc0e49', diff --git a/test/core/templates/spec-inventory.test.ts b/test/core/templates/spec-inventory.test.ts index b0539a889e..aeed205d0c 100644 --- a/test/core/templates/spec-inventory.test.ts +++ b/test/core/templates/spec-inventory.test.ts @@ -21,6 +21,11 @@ import { loadSchema } from '../../../src/core/artifact-graph/schema.js'; // against the wrong object. const SPEC_INVENTORY = 'openspec list --specs'; +// Assertions about the guidance attached to the command are scoped to a window +// after it rather than to the whole body, so an unrelated occurrence elsewhere +// in a long template cannot stand in for the passage under test. +const PASSAGE_WINDOW = 700; + const repoRoot = path.resolve(fileURLToPath(new URL('.', import.meta.url)), '../../..'); const defaultSchema = loadSchema(path.join(repoRoot, 'schemas', 'spec-driven', 'schema.yaml')); @@ -94,11 +99,32 @@ describe('spec inventory vocabulary (#1689)', () => { // Scoped to the passage that names the command: every explore body // already carries the store qualifier in its unrelated capture steps, // so a whole-body match would pass even with the qualifier dropped here. - const passage = body.slice(start, start + 400); + const passage = body.slice(start, start + PASSAGE_WINDOW); expect(passage, `${label} names the command without its store qualifier`).toContain( 'registered standalone store' ); expect(passage, label).toContain('--store ""'); } }); + + // Reading the inventory back by raw path defeats the fix under a store: the + // ids `list --specs --store ` returns are not present under the local + // `openspec/specs/`, so the read either fails or silently lands on a + // same-named local capability - the wrong-object failure #1689 is about. + // `openspec show` resolves against the same root the listing came from. + it('reads a listed capability with the store-aware command', () => { + const sites: Array<[string, string]> = [ + ...exploreBodies, + ['proposal instruction', instructionFor('proposal')], + ]; + + for (const [label, body] of sites) { + const start = body.indexOf(SPEC_INVENTORY); + const passage = body.slice(start, start + PASSAGE_WINDOW); + expect(passage, `${label} does not name a store-aware read`).toContain('openspec show'); + // A change and a spec may share a name; without --type that is an + // ambiguous-item error rather than the spec the agent asked for. + expect(passage, `${label} omits the --type spec disambiguator`).toContain('--type spec'); + } + }); }); From dc3d14fa60ae24a8db11fb27eb976ef8018e3a84 Mon Sep 17 00:00:00 2001 From: Shooks Date: Sat, 25 Apr 2026 10:54:19 +0800 Subject: [PATCH 04/15] fix: Use ASCII arrows instead of unicode This fixes the issue of ambiguous unicode character width when visualizing on terminals --- src/core/templates/workflows/explore.ts | 70 +++++++++++++++---------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/src/core/templates/workflows/explore.ts b/src/core/templates/workflows/explore.ts index 211de65646..5712563b67 100644 --- a/src/core/templates/workflows/explore.ts +++ b/src/core/templates/workflows/explore.ts @@ -56,22 +56,29 @@ Depending on what the user brings, you might: **Visualize** \`\`\` -┌─────────────────────────────────────────┐ -│ Use ASCII diagrams liberally │ -├─────────────────────────────────────────┤ -│ │ -│ ┌────────┐ ┌────────┐ │ -│ │ State │────────▶│ State │ │ -│ │ A │ │ B │ │ -│ └────────┘ └────────┘ │ -│ │ -│ System diagrams, state machines, │ -│ data flows, architecture sketches, │ -│ dependency graphs, comparison tables │ -│ │ -└─────────────────────────────────────────┘ ++------------------------------------------+ +| Use ASCII diagrams liberally | ++------------------------------------------+ +| | +| [State A] -------> [State B] | +| | | +| v | +| [State C] | +| | +| System diagrams, state machines, | +| data flows, architecture sketches, | +| dependency graphs, comparison tables | +| | ++------------------------------------------+ \`\`\` +**Important — use plain ASCII for all diagram characters:** +- Box borders: \`+\`, \`-\`, \`|\` instead of \`┌\`, \`─\`, \`│\` +- Arrows: \`-->\`, \`<--\`, \`^\`, \`v\` instead of \`→\`, \`←\`, \`↑\`, \`↓\` +- Connections: \`|\` instead of \`│\` + +Unicode box-drawing and arrow characters (→, ←, ─, │, ┌, etc.) are "Ambiguous Width" in the Unicode standard — they render as double-width columns in many terminal configurations. Inside fixed-width padded boxes this causes the right border to appear misaligned or detached, since lines with different numbers of these characters end up at different visual widths. + **Surface risks and unknowns** - Identify what could go wrong - Find gaps in understanding @@ -371,22 +378,29 @@ Depending on what the user brings, you might: **Visualize** \`\`\` -┌─────────────────────────────────────────┐ -│ Use ASCII diagrams liberally │ -├─────────────────────────────────────────┤ -│ │ -│ ┌────────┐ ┌────────┐ │ -│ │ State │────────▶│ State │ │ -│ │ A │ │ B │ │ -│ └────────┘ └────────┘ │ -│ │ -│ System diagrams, state machines, │ -│ data flows, architecture sketches, │ -│ dependency graphs, comparison tables │ -│ │ -└─────────────────────────────────────────┘ ++------------------------------------------+ +| Use ASCII diagrams liberally | ++------------------------------------------+ +| | +| [State A] -------> [State B] | +| | | +| v | +| [State C] | +| | +| System diagrams, state machines, | +| data flows, architecture sketches, | +| dependency graphs, comparison tables | +| | ++------------------------------------------+ \`\`\` +**Important — use plain ASCII for all diagram characters:** +- Box borders: \`+\`, \`-\`, \`|\` instead of \`┌\`, \`─\`, \`│\` +- Arrows: \`-->\`, \`<--\`, \`^\`, \`v\` instead of \`→\`, \`←\`, \`↑\`, \`↓\` +- Connections: \`|\` instead of \`│\` + +Unicode box-drawing and arrow characters (→, ←, ─, │, ┌, etc.) are "Ambiguous Width" in the Unicode standard — they render as double-width columns in many terminal configurations. Inside fixed-width padded boxes this causes the right border to appear misaligned or detached, since lines with different numbers of these characters end up at different visual widths. + **Surface risks and unknowns** - Identify what could go wrong - Find gaps in understanding From 8ec4e127b8119e9c4ea2a178120fefcc3f409c8a Mon Sep 17 00:00:00 2001 From: Shooks Date: Tue, 28 Apr 2026 13:19:46 +0800 Subject: [PATCH 05/15] fix: Update remaining docs within explore to use ASCII --- src/core/templates/workflows/explore.ts | 66 ++++++++++++------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/core/templates/workflows/explore.ts b/src/core/templates/workflows/explore.ts index 5712563b67..4959f5539a 100644 --- a/src/core/templates/workflows/explore.ts +++ b/src/core/templates/workflows/explore.ts @@ -179,17 +179,17 @@ User: I'm thinking about adding real-time collaboration You: Real-time collab is a big space. Let me think about this... COLLABORATION SPECTRUM - ════════════════════════════════════════════ + ============================================ Awareness Coordination Sync - │ │ │ - ▼ ▼ ▼ - ┌────────┐ ┌────────┐ ┌────────┐ - │Presence│ │Cursors │ │ CRDT │ - │ "3 │ │ Multi │ │Conflict│ - │online" │ │ select │ │ free │ - └────────┘ └────────┘ └────────┘ - │ │ │ + | | | + v v v + +--------+ +--------+ +--------+ + |Presence| |Cursors | | CRDT | + | "3 | | Multi | |Conflict| + |online" | | select | | free | + +--------+ +--------+ +--------+ + | | | trivial moderate complex Where's your head at? @@ -201,27 +201,27 @@ User: The auth system is a mess You: [reads codebase] - ┌─────────────────────────────────────────────┐ - │ CURRENT AUTH FLOW │ - └─────────────────────────────────────────────┘ - │ - ┌─────────────┼─────────────┐ - ▼ ▼ ▼ - ┌─────────┐ ┌─────────┐ ┌─────────┐ - │ Google │ │ GitHub │ │ Email │ - │ OAuth │ │ OAuth │ │ Magic │ - └────┬────┘ └────┬────┘ └────┬────┘ - │ │ │ - └─────────────┼─────────────┘ - ▼ - ┌───────────┐ - │ Session │ - └─────┬─────┘ - │ - ▼ - ┌───────────┐ - │ Perms │ - └───────────┘ + +---------------------------------------------+ + | CURRENT AUTH FLOW | + +---------------------------------------------+ + | + +-------------+-------------+ + v v v + +---------+ +---------+ +---------+ + | Google | | GitHub | | Email | + | OAuth | | OAuth | | Magic | + +----+----+ +----+----+ +----+----+ + | | | + +-------------+-------------+ + v + +-----------+ + | Session | + +-----+-----+ + | + v + +-----------+ + | Perms | + +-----------+ I see three tangles. Which one's burning? \`\`\` @@ -253,9 +253,9 @@ User: A CLI tool that tracks local dev environments You: That changes everything. - ┌─────────────────────────────────────────────────┐ - │ CLI TOOL DATA STORAGE │ - └─────────────────────────────────────────────────┘ + +-------------------------------------------------+ + | CLI TOOL DATA STORAGE | + +-------------------------------------------------+ Key constraints: • No daemon running From 2ecc90e5fbf1d8e6664226f01720f9fbffbcbb49 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Thu, 20 Aug 2026 14:36:45 -0500 Subject: [PATCH 06/15] fix(explore): finish the ASCII conversion and guard it Rebase onto main and close the gaps in the original fix: - Regenerate skills/openspec-explore/SKILL.md. The static skills/ mirror landed after this branch was cut, so the parity test would have failed with the template and the mirror out of sync. - Regenerate the three parity hashes through scripts/regen-parity-hashes.mjs. - Convert the ambiguous-width glyphs the first pass missed: the bullets in the CLI-storage example, and the check/cross marks in its comparison table, which sat in the column-aligned block the bug is about. - Tighten the ASCII guidance to two lines. It ships into every user project on both delivery surfaces, so the paragraph was pure overhead. - Add regression tests (#983): every fenced example in both the skill and the command body must be free of box-drawing, arrow, bullet, and check/cross glyphs, and the guidance must state the rule and the reason. - Add a patch changeset. Co-Authored-By: Claude Opus 5 --- .changeset/plain-ascii-explore-diagrams.md | 5 + skills/openspec-explore/SKILL.md | 109 +++++++++--------- src/core/templates/workflows/explore.ts | 28 ++--- test/core/templates/explore.test.ts | 44 +++++++ .../templates/skill-templates-parity.test.ts | 6 +- 5 files changed, 118 insertions(+), 74 deletions(-) create mode 100644 .changeset/plain-ascii-explore-diagrams.md diff --git a/.changeset/plain-ascii-explore-diagrams.md b/.changeset/plain-ascii-explore-diagrams.md new file mode 100644 index 0000000000..6603434cae --- /dev/null +++ b/.changeset/plain-ascii-explore-diagrams.md @@ -0,0 +1,5 @@ +--- +'@fission-ai/openspec': patch +--- + +Draw explore-mode diagrams with plain ASCII. The worked examples in the explore skill and `/opsx:explore` command used Unicode box-drawing, arrow, and marker glyphs, which many terminals render two columns wide — agents copied the style and their padded boxes and aligned tables came out with the right border detached. diff --git a/skills/openspec-explore/SKILL.md b/skills/openspec-explore/SKILL.md index 327c261489..9ae85035a8 100644 --- a/skills/openspec-explore/SKILL.md +++ b/skills/openspec-explore/SKILL.md @@ -54,22 +54,25 @@ Depending on what the user brings, you might: **Visualize** ``` -┌─────────────────────────────────────────┐ -│ Use ASCII diagrams liberally │ -├─────────────────────────────────────────┤ -│ │ -│ ┌────────┐ ┌────────┐ │ -│ │ State │────────▶│ State │ │ -│ │ A │ │ B │ │ -│ └────────┘ └────────┘ │ -│ │ -│ System diagrams, state machines, │ -│ data flows, architecture sketches, │ -│ dependency graphs, comparison tables │ -│ │ -└─────────────────────────────────────────┘ ++------------------------------------------+ +| Use ASCII diagrams liberally | ++------------------------------------------+ +| | +| [State A] -------> [State B] | +| | | +| v | +| [State C] | +| | +| System diagrams, state machines, | +| data flows, architecture sketches, | +| dependency graphs, comparison tables | +| | ++------------------------------------------+ ``` +**Draw with plain ASCII only** — borders `+` `-` `|`, arrows `-->` `<--` `^` `v`, markers `*` `x`. +Unicode box-drawing, arrow, and bullet glyphs (─ │ ┌ → ← • ✓) are East Asian "Ambiguous Width": many terminals render them two columns wide, so a padded box or aligned table drifts and its right edge detaches. + **Surface risks and unknowns** - Identify what could go wrong - Find gaps in understanding @@ -170,17 +173,17 @@ User: I'm thinking about adding real-time collaboration You: Real-time collab is a big space. Let me think about this... COLLABORATION SPECTRUM - ════════════════════════════════════════════ + ============================================ Awareness Coordination Sync - │ │ │ - ▼ ▼ ▼ - ┌────────┐ ┌────────┐ ┌────────┐ - │Presence│ │Cursors │ │ CRDT │ - │ "3 │ │ Multi │ │Conflict│ - │online" │ │ select │ │ free │ - └────────┘ └────────┘ └────────┘ - │ │ │ + | | | + v v v + +--------+ +--------+ +--------+ + |Presence| |Cursors | | CRDT | + | "3 | | Multi | |Conflict| + |online" | | select | | free | + +--------+ +--------+ +--------+ + | | | trivial moderate complex Where's your head at? @@ -192,27 +195,27 @@ User: The auth system is a mess You: [reads codebase] - ┌─────────────────────────────────────────────┐ - │ CURRENT AUTH FLOW │ - └─────────────────────────────────────────────┘ - │ - ┌─────────────┼─────────────┐ - ▼ ▼ ▼ - ┌─────────┐ ┌─────────┐ ┌─────────┐ - │ Google │ │ GitHub │ │ Email │ - │ OAuth │ │ OAuth │ │ Magic │ - └────┬────┘ └────┬────┘ └────┬────┘ - │ │ │ - └─────────────┼─────────────┘ - ▼ - ┌───────────┐ - │ Session │ - └─────┬─────┘ - │ - ▼ - ┌───────────┐ - │ Perms │ - └───────────┘ + +---------------------------------------------+ + | CURRENT AUTH FLOW | + +---------------------------------------------+ + | + +-------------+-------------+ + v v v + +---------+ +---------+ +---------+ + | Google | | GitHub | | Email | + | OAuth | | OAuth | | Magic | + +----+----+ +----+----+ +----+----+ + | | | + +-------------+-------------+ + v + +-----------+ + | Session | + +-----+-----+ + | + v + +-----------+ + | Perms | + +-----------+ I see three tangles. Which one's burning? ``` @@ -244,19 +247,19 @@ User: A CLI tool that tracks local dev environments You: That changes everything. - ┌─────────────────────────────────────────────────┐ - │ CLI TOOL DATA STORAGE │ - └─────────────────────────────────────────────────┘ + +-------------------------------------------------+ + | CLI TOOL DATA STORAGE | + +-------------------------------------------------+ Key constraints: - • No daemon running - • Must work offline - • Single user + - No daemon running + - Must work offline + - Single user SQLite Postgres - Deployment embedded ✓ needs server ✗ - Offline yes ✓ no ✗ - Single file yes ✓ no ✗ + Deployment embedded needs server + Offline yes no + Single file yes no SQLite. Not even close. diff --git a/src/core/templates/workflows/explore.ts b/src/core/templates/workflows/explore.ts index 4959f5539a..0a3b833a88 100644 --- a/src/core/templates/workflows/explore.ts +++ b/src/core/templates/workflows/explore.ts @@ -72,12 +72,8 @@ Depending on what the user brings, you might: +------------------------------------------+ \`\`\` -**Important — use plain ASCII for all diagram characters:** -- Box borders: \`+\`, \`-\`, \`|\` instead of \`┌\`, \`─\`, \`│\` -- Arrows: \`-->\`, \`<--\`, \`^\`, \`v\` instead of \`→\`, \`←\`, \`↑\`, \`↓\` -- Connections: \`|\` instead of \`│\` - -Unicode box-drawing and arrow characters (→, ←, ─, │, ┌, etc.) are "Ambiguous Width" in the Unicode standard — they render as double-width columns in many terminal configurations. Inside fixed-width padded boxes this causes the right border to appear misaligned or detached, since lines with different numbers of these characters end up at different visual widths. +**Draw with plain ASCII only** — borders \`+\` \`-\` \`|\`, arrows \`-->\` \`<--\` \`^\` \`v\`, markers \`*\` \`x\`. +Unicode box-drawing, arrow, and bullet glyphs (─ │ ┌ → ← • ✓) are East Asian "Ambiguous Width": many terminals render them two columns wide, so a padded box or aligned table drifts and its right edge detaches. **Surface risks and unknowns** - Identify what could go wrong @@ -258,14 +254,14 @@ You: That changes everything. +-------------------------------------------------+ Key constraints: - • No daemon running - • Must work offline - • Single user + - No daemon running + - Must work offline + - Single user SQLite Postgres - Deployment embedded ✓ needs server ✗ - Offline yes ✓ no ✗ - Single file yes ✓ no ✗ + Deployment embedded needs server + Offline yes no + Single file yes no SQLite. Not even close. @@ -394,12 +390,8 @@ Depending on what the user brings, you might: +------------------------------------------+ \`\`\` -**Important — use plain ASCII for all diagram characters:** -- Box borders: \`+\`, \`-\`, \`|\` instead of \`┌\`, \`─\`, \`│\` -- Arrows: \`-->\`, \`<--\`, \`^\`, \`v\` instead of \`→\`, \`←\`, \`↑\`, \`↓\` -- Connections: \`|\` instead of \`│\` - -Unicode box-drawing and arrow characters (→, ←, ─, │, ┌, etc.) are "Ambiguous Width" in the Unicode standard — they render as double-width columns in many terminal configurations. Inside fixed-width padded boxes this causes the right border to appear misaligned or detached, since lines with different numbers of these characters end up at different visual widths. +**Draw with plain ASCII only** — borders \`+\` \`-\` \`|\`, arrows \`-->\` \`<--\` \`^\` \`v\`, markers \`*\` \`x\`. +Unicode box-drawing, arrow, and bullet glyphs (─ │ ┌ → ← • ✓) are East Asian "Ambiguous Width": many terminals render them two columns wide, so a padded box or aligned table drifts and its right edge detaches. **Surface risks and unknowns** - Identify what could go wrong diff --git a/test/core/templates/explore.test.ts b/test/core/templates/explore.test.ts index 280077da83..ef376343e5 100644 --- a/test/core/templates/explore.test.ts +++ b/test/core/templates/explore.test.ts @@ -29,6 +29,28 @@ function occurrenceCount(body: string, value: string): number { return body.split(value).length - 1; } +// East Asian "Ambiguous Width" glyphs: box drawing, block elements, +// geometric shapes, arrows, bullets, and the check/cross dingbats. Many +// terminals render these two columns wide. +const AMBIGUOUS_WIDTH = /[\u2022\u2190-\u21FF\u2500-\u25FF\u2713\u2717]/; + +function fencedBlockLines(body: string): Array<[number, string]> { + const lines: Array<[number, string]> = []; + let inFence = false; + + body.split('\n').forEach((line, index) => { + if (line.trimStart().startsWith('```')) { + inFence = !inFence; + return; + } + if (inFence) { + lines.push([index + 1, line]); + } + }); + + return lines; +} + describe('explore templates', () => { // Regression for #696: explore never loaded the project's declared // context, so it reasoned without the tech stack, conventions, and @@ -191,6 +213,28 @@ describe('explore templates', () => { } }); + // Regression for #983: the worked examples drew boxes and tables with + // Unicode box-drawing, arrow, and marker glyphs. Agents copy those + // examples verbatim, and on terminals that render the glyphs + // double-width the right border of every padded box drifted loose. + it('draws every fenced example with plain ASCII only (#983)', () => { + for (const [label, body] of bodies) { + const offenders = fencedBlockLines(body) + .filter(([, line]) => AMBIGUOUS_WIDTH.test(line.normalize('NFC'))) + .map(([lineNumber, line]) => `${lineNumber}: ${line}`); + + expect(offenders, `${label} fenced examples must be pure ASCII`).toEqual([]); + } + }); + + it('tells the agent to draw with ASCII and says why (#983)', () => { + for (const [label, body] of bodies) { + expect(body, label).toContain('**Draw with plain ASCII only**'); + expect(body, label).toContain('Ambiguous Width'); + expect(body, label).toContain('two columns wide'); + } + }); + it('stops after scaffolding when the user requests only a new change (#668)', () => { for (const [label, body] of bodies) { const transition = newChangeTransition(body, label); diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 3860a62eda..c58391133c 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -38,14 +38,14 @@ import { import { STORE_SELECTION_GUIDANCE } from '../../../src/core/templates/workflows/store-selection.js'; const EXPECTED_FUNCTION_HASHES: Record = { - getExploreSkillTemplate: '3efc37cddf342318ac37be7bb4ff5915f454b4c5bb127294ebdc7534ee21aa23', + getExploreSkillTemplate: '34f0e5584f4975b29a90ae173992a93204b7d522424b3f1a78ff511d628c5ea0', getNewChangeSkillTemplate: 'eabd1e895c5881dcb17dcbaa3fb26098dd59e8eacb318e400820b4dc811ef781', getContinueChangeSkillTemplate: '012136f6411a99c8fa228e2f9444cb64b0a89e0f56fdeac2fe03b2f5bee0c5d7', getApplyChangeSkillTemplate: 'd1e7d5ceb85193c0964057dbb88e9651526754bd33f84020e2440ff0621d5dbb', getFfChangeSkillTemplate: '5501740e7ec36ab23ab8c3a0d6dd0655a5e2f35433c7b90e82904fef5e7a326a', getSyncSpecsSkillTemplate: 'b099e2ff31859c9b10d928066e662524f9aad9ecf2be12fceacb732d718c4146', getOnboardSkillTemplate: '3a836faae463d88c289a1c129cb7ee556a563b7e53e1a52a4711ff152a3b51f7', - getOpsxExploreCommandTemplate: 'd2f70d11588f902c15c1e5ce9908cc4124c6b82fe78dc766ac5c3599c9e2a6f1', + getOpsxExploreCommandTemplate: '7cbce8f67f1dae0c8a02f9f22112b6ecc102f1756d477237b41faa3d9eea3c82', getOpsxNewCommandTemplate: 'f2d30e569798a4c92ba932859d6ba4e0ad10e18feccbade1cfee0957597b3463', getOpsxContinueCommandTemplate: 'e50e50266efa1b8e64ff9b6274ee8254f0a240d6adc1b862d126e2f1c9d3a559', getOpsxApplyCommandTemplate: 'e3579ac78f2e2c75fa3d3a7ac7dc3e49c395e96f7323398f0f041d94f8de9bb0', @@ -66,7 +66,7 @@ const EXPECTED_FUNCTION_HASHES: Record = { }; const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { - 'openspec-explore': '4d9736372cc1faf8a5d8a66395a95bf77b9f3fcd2cda40411ad1db6927e8066a', + 'openspec-explore': 'f40a48df291bdde9452d1b115aeeee1f9244f6a213736e5db155e58f6f655329', 'openspec-new-change': 'ec4529beef978e34634a6f7286fab55d68fad8fb374dceb45691d52caab33fbb', 'openspec-continue-change': 'bb6194a16c54891cdb253678e8f70ce53b2af86735243980f366ce551d37e42e', 'openspec-apply-change': '81ea96d9fa6ec8536cd23c1fe561ed28e1cc1cad0a8ceb700588e08974cc0e49', From 583dd2c36247bf1c98a4ac7479992c53c7332dd1 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Thu, 20 Aug 2026 14:46:46 -0500 Subject: [PATCH 07/15] test(explore): cover every check/cross dingbat in the ASCII guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The matcher listed U+2713 and U+2717 only, so a fenced example could use ✕ (U+2715) or ✘ (U+2718) — same ambiguous width, same misalignment — and still pass. Widen to the U+2713-U+2718 run. Co-Authored-By: Claude Opus 5 --- test/core/templates/explore.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/core/templates/explore.test.ts b/test/core/templates/explore.test.ts index ef376343e5..9646b53815 100644 --- a/test/core/templates/explore.test.ts +++ b/test/core/templates/explore.test.ts @@ -32,7 +32,7 @@ function occurrenceCount(body: string, value: string): number { // East Asian "Ambiguous Width" glyphs: box drawing, block elements, // geometric shapes, arrows, bullets, and the check/cross dingbats. Many // terminals render these two columns wide. -const AMBIGUOUS_WIDTH = /[\u2022\u2190-\u21FF\u2500-\u25FF\u2713\u2717]/; +const AMBIGUOUS_WIDTH = /[\u2022\u2190-\u21FF\u2500-\u25FF\u2713-\u2718]/; function fencedBlockLines(body: string): Array<[number, string]> { const lines: Array<[number, string]> = []; From f5ff932af109b92856eee49296844da5b1775e91 Mon Sep 17 00:00:00 2001 From: "Ayman D." Date: Sun, 23 Aug 2026 00:57:26 +0100 Subject: [PATCH 08/15] fix(explore): require explicit confirmation before writing files --- .changeset/explore-explicit-write-confirmation.md | 5 +++++ skills/openspec-explore/SKILL.md | 6 +++--- src/core/templates/workflows/explore.ts | 12 ++++++------ test/core/templates/skill-templates-parity.test.ts | 6 +++--- 4 files changed, 17 insertions(+), 12 deletions(-) create mode 100644 .changeset/explore-explicit-write-confirmation.md diff --git a/.changeset/explore-explicit-write-confirmation.md b/.changeset/explore-explicit-write-confirmation.md new file mode 100644 index 0000000000..cf6119bbf5 --- /dev/null +++ b/.changeset/explore-explicit-write-confirmation.md @@ -0,0 +1,5 @@ +--- +'@fission-ai/openspec': patch +--- + +explore: require explicit confirmation before the skill writes any file. The explore skill's guardrails let "if the user asks" cover answers to its own clarifying questions, so an agent could treat a design discussion as a go-ahead and start creating schemas or editing `openspec/config.yaml` uninvited. The skill and the `/opsx:explore` command now instruct the agent to ask a direct yes/no question and wait for the user's confirmation in a separate message before the first write of any file, and the "Don't implement" guardrail now names workflow configuration — schemas, templates, `openspec/config.yaml` — as changes rather than thinking. diff --git a/skills/openspec-explore/SKILL.md b/skills/openspec-explore/SKILL.md index 327c261489..66df0bdd9a 100644 --- a/skills/openspec-explore/SKILL.md +++ b/skills/openspec-explore/SKILL.md @@ -11,7 +11,7 @@ metadata: Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. -**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing. For a new change, scaffold it first as described below. +**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) when the user explicitly asks you to—that's capturing thinking, not implementing. Answering design or clarifying questions is never such an ask: before your first write of any file, ask a direct yes/no question ("Want me to create it?") and wait for the user's confirmation. For a new change, scaffold it first as described below. **This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore. @@ -296,11 +296,11 @@ But this summary is optional. Sometimes the thinking IS the value. ## Guardrails -- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not. +- **Don't implement** - Never write code or implement features. Workflow configuration counts too: creating or editing schemas, templates, or `openspec/config.yaml` is a change, not thinking. Creating OpenSpec artifacts once the user confirms is fine, writing anything else is not. - **Don't fake understanding** - If something is unclear, dig deeper - **Don't rush** - Discovery is thinking time, not task time - **Don't force structure** - Let patterns emerge naturally -- **Don't auto-capture** - Offer to save insights, don't just do it +- **Don't auto-capture** - Offer to save insights, don't just do it. Before your first write of ANY file (change artifacts, specs, `openspec/config.yaml`, `openspec/schemas/**`, anything else), ask a direct yes/no question and wait for explicit confirmation in a separate user message. Answers to design or clarifying questions are never consent to write. - **Don't manually scaffold changes** - Never create a new change directory under `openspec/changes/` by hand. Always use `openspec new change ""` (with `--store ` when applicable) so required metadata such as `.openspec.yaml` is created before writing artifacts. - **Do visualize** - A good diagram is worth many paragraphs - **Do explore the codebase** - Ground discussions in reality diff --git a/src/core/templates/workflows/explore.ts b/src/core/templates/workflows/explore.ts index 211de65646..cfdde1e3d0 100644 --- a/src/core/templates/workflows/explore.ts +++ b/src/core/templates/workflows/explore.ts @@ -13,7 +13,7 @@ export function getExploreSkillTemplate(): SkillTemplate { description: 'Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change.', instructions: `Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. -**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing. For a new change, scaffold it first as described below. +**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) when the user explicitly asks you to—that's capturing thinking, not implementing. Answering design or clarifying questions is never such an ask: before your first write of any file, ask a direct yes/no question ("Want me to create it?") and wait for the user's confirmation. For a new change, scaffold it first as described below. **This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore. @@ -298,11 +298,11 @@ But this summary is optional. Sometimes the thinking IS the value. ## Guardrails -- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not. +- **Don't implement** - Never write code or implement features. Workflow configuration counts too: creating or editing schemas, templates, or \`openspec/config.yaml\` is a change, not thinking. Creating OpenSpec artifacts once the user confirms is fine, writing anything else is not. - **Don't fake understanding** - If something is unclear, dig deeper - **Don't rush** - Discovery is thinking time, not task time - **Don't force structure** - Let patterns emerge naturally -- **Don't auto-capture** - Offer to save insights, don't just do it +- **Don't auto-capture** - Offer to save insights, don't just do it. Before your first write of ANY file (change artifacts, specs, \`openspec/config.yaml\`, \`openspec/schemas/**\`, anything else), ask a direct yes/no question and wait for explicit confirmation in a separate user message. Answers to design or clarifying questions are never consent to write. - **Don't manually scaffold changes** - Never create a new change directory under \`openspec/changes/\` by hand. Always use \`openspec new change ""\` (with \`--store \` when applicable) so required metadata such as \`.openspec.yaml\` is created before writing artifacts. - **Do visualize** - A good diagram is worth many paragraphs - **Do explore the codebase** - Ground discussions in reality @@ -321,7 +321,7 @@ export function getOpsxExploreCommandTemplate(): CommandTemplate { tags: ['workflow', 'explore', 'experimental', 'thinking'], content: `Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. -**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing. For a new change, scaffold it first as described below. +**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) when the user explicitly asks you to—that's capturing thinking, not implementing. Answering design or clarifying questions is never such an ask: before your first write of any file, ask a direct yes/no question ("Want me to create it?") and wait for the user's confirmation. For a new change, scaffold it first as described below. **This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore. @@ -495,11 +495,11 @@ When things crystallize, you might offer a summary - but it's optional. Sometime ## Guardrails -- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not. +- **Don't implement** - Never write code or implement features. Workflow configuration counts too: creating or editing schemas, templates, or \`openspec/config.yaml\` is a change, not thinking. Creating OpenSpec artifacts once the user confirms is fine, writing anything else is not. - **Don't fake understanding** - If something is unclear, dig deeper - **Don't rush** - Discovery is thinking time, not task time - **Don't force structure** - Let patterns emerge naturally -- **Don't auto-capture** - Offer to save insights, don't just do it +- **Don't auto-capture** - Offer to save insights, don't just do it. Before your first write of ANY file (change artifacts, specs, \`openspec/config.yaml\`, \`openspec/schemas/**\`, anything else), ask a direct yes/no question and wait for explicit confirmation in a separate user message. Answers to design or clarifying questions are never consent to write. - **Don't manually scaffold changes** - Never create a new change directory under \`openspec/changes/\` by hand. Always use \`openspec new change ""\` (with \`--store \` when applicable) so required metadata such as \`.openspec.yaml\` is created before writing artifacts. - **Do visualize** - A good diagram is worth many paragraphs - **Do explore the codebase** - Ground discussions in reality diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 3860a62eda..f873f63203 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -38,14 +38,14 @@ import { import { STORE_SELECTION_GUIDANCE } from '../../../src/core/templates/workflows/store-selection.js'; const EXPECTED_FUNCTION_HASHES: Record = { - getExploreSkillTemplate: '3efc37cddf342318ac37be7bb4ff5915f454b4c5bb127294ebdc7534ee21aa23', + getExploreSkillTemplate: '401a3b7ba5cbeb81715db21864c4bf542ea25e51058a156ee8415f040ddcc3a7', getNewChangeSkillTemplate: 'eabd1e895c5881dcb17dcbaa3fb26098dd59e8eacb318e400820b4dc811ef781', getContinueChangeSkillTemplate: '012136f6411a99c8fa228e2f9444cb64b0a89e0f56fdeac2fe03b2f5bee0c5d7', getApplyChangeSkillTemplate: 'd1e7d5ceb85193c0964057dbb88e9651526754bd33f84020e2440ff0621d5dbb', getFfChangeSkillTemplate: '5501740e7ec36ab23ab8c3a0d6dd0655a5e2f35433c7b90e82904fef5e7a326a', getSyncSpecsSkillTemplate: 'b099e2ff31859c9b10d928066e662524f9aad9ecf2be12fceacb732d718c4146', getOnboardSkillTemplate: '3a836faae463d88c289a1c129cb7ee556a563b7e53e1a52a4711ff152a3b51f7', - getOpsxExploreCommandTemplate: 'd2f70d11588f902c15c1e5ce9908cc4124c6b82fe78dc766ac5c3599c9e2a6f1', + getOpsxExploreCommandTemplate: '92657ee1483d467dbf7db4ff5bfefbb19783a3fdde6206fe21d5c34f7e87bc59', getOpsxNewCommandTemplate: 'f2d30e569798a4c92ba932859d6ba4e0ad10e18feccbade1cfee0957597b3463', getOpsxContinueCommandTemplate: 'e50e50266efa1b8e64ff9b6274ee8254f0a240d6adc1b862d126e2f1c9d3a559', getOpsxApplyCommandTemplate: 'e3579ac78f2e2c75fa3d3a7ac7dc3e49c395e96f7323398f0f041d94f8de9bb0', @@ -66,7 +66,7 @@ const EXPECTED_FUNCTION_HASHES: Record = { }; const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { - 'openspec-explore': '4d9736372cc1faf8a5d8a66395a95bf77b9f3fcd2cda40411ad1db6927e8066a', + 'openspec-explore': '6ee737ed85cbe74bc1b35b1b86d98159cbd617b54013f6afaa4aff8be88305c3', 'openspec-new-change': 'ec4529beef978e34634a6f7286fab55d68fad8fb374dceb45691d52caab33fbb', 'openspec-continue-change': 'bb6194a16c54891cdb253678e8f70ce53b2af86735243980f366ce551d37e42e', 'openspec-apply-change': '81ea96d9fa6ec8536cd23c1fe561ed28e1cc1cad0a8ceb700588e08974cc0e49', From 4d2435e95f1de645a238ef28bdbc9c968c7c74c7 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Mon, 24 Aug 2026 08:53:23 -0500 Subject: [PATCH 09/15] test(explore): harden write confirmation guardrail --- .../explore-explicit-write-confirmation.md | 2 +- skills/openspec-explore/SKILL.md | 4 +-- src/core/templates/workflows/explore.ts | 8 +++--- test/core/templates/explore.test.ts | 28 +++++++++++++++++++ .../templates/skill-templates-parity.test.ts | 6 ++-- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/.changeset/explore-explicit-write-confirmation.md b/.changeset/explore-explicit-write-confirmation.md index cf6119bbf5..b782d55b1c 100644 --- a/.changeset/explore-explicit-write-confirmation.md +++ b/.changeset/explore-explicit-write-confirmation.md @@ -2,4 +2,4 @@ '@fission-ai/openspec': patch --- -explore: require explicit confirmation before the skill writes any file. The explore skill's guardrails let "if the user asks" cover answers to its own clarifying questions, so an agent could treat a design discussion as a go-ahead and start creating schemas or editing `openspec/config.yaml` uninvited. The skill and the `/opsx:explore` command now instruct the agent to ask a direct yes/no question and wait for the user's confirmation in a separate message before the first write of any file, and the "Don't implement" guardrail now names workflow configuration — schemas, templates, `openspec/config.yaml` — as changes rather than thinking. +explore: require explicit confirmation before the skill uses any command or tool that can create, edit, move, or delete a file. The explore skill's guardrails let "if the user asks" cover answers to its own clarifying questions, so an agent could treat a design discussion as a go-ahead and start creating schemas or editing `openspec/config.yaml` uninvited. The skill and the `/opsx:explore` command now instruct the agent to ask a direct yes/no question and wait for the user's confirmation in a separate message before the first file-writing action, and the "Don't implement" guardrail now names workflow configuration — schemas, templates, `openspec/config.yaml` — as changes rather than thinking. diff --git a/skills/openspec-explore/SKILL.md b/skills/openspec-explore/SKILL.md index 66df0bdd9a..583eace7d6 100644 --- a/skills/openspec-explore/SKILL.md +++ b/skills/openspec-explore/SKILL.md @@ -11,7 +11,7 @@ metadata: Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. -**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) when the user explicitly asks you to—that's capturing thinking, not implementing. Answering design or clarifying questions is never such an ask: before your first write of any file, ask a direct yes/no question ("Want me to create it?") and wait for the user's confirmation. For a new change, scaffold it first as described below. +**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) when the user explicitly asks you to—that's capturing thinking, not implementing. Answering design or clarifying questions is never such an ask: before the first action that could create, edit, move, or delete a file (including running an OpenSpec command), ask a direct yes/no question ("Want me to create it?") and wait for the user's confirmation in a separate message. For a new change, scaffold it first as described below. **This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore. @@ -300,7 +300,7 @@ But this summary is optional. Sometimes the thinking IS the value. - **Don't fake understanding** - If something is unclear, dig deeper - **Don't rush** - Discovery is thinking time, not task time - **Don't force structure** - Let patterns emerge naturally -- **Don't auto-capture** - Offer to save insights, don't just do it. Before your first write of ANY file (change artifacts, specs, `openspec/config.yaml`, `openspec/schemas/**`, anything else), ask a direct yes/no question and wait for explicit confirmation in a separate user message. Answers to design or clarifying questions are never consent to write. +- **Don't auto-capture** - Offer to save insights, don't just do it. Before the first action that could create, edit, move, or delete ANY file—including running `openspec new change` or another command that writes files—ask a direct yes/no question and wait for explicit confirmation in a separate user message. Answers to design or clarifying questions are never consent to write. - **Don't manually scaffold changes** - Never create a new change directory under `openspec/changes/` by hand. Always use `openspec new change ""` (with `--store ` when applicable) so required metadata such as `.openspec.yaml` is created before writing artifacts. - **Do visualize** - A good diagram is worth many paragraphs - **Do explore the codebase** - Ground discussions in reality diff --git a/src/core/templates/workflows/explore.ts b/src/core/templates/workflows/explore.ts index cfdde1e3d0..d124b61f12 100644 --- a/src/core/templates/workflows/explore.ts +++ b/src/core/templates/workflows/explore.ts @@ -13,7 +13,7 @@ export function getExploreSkillTemplate(): SkillTemplate { description: 'Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change.', instructions: `Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. -**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) when the user explicitly asks you to—that's capturing thinking, not implementing. Answering design or clarifying questions is never such an ask: before your first write of any file, ask a direct yes/no question ("Want me to create it?") and wait for the user's confirmation. For a new change, scaffold it first as described below. +**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) when the user explicitly asks you to—that's capturing thinking, not implementing. Answering design or clarifying questions is never such an ask: before the first action that could create, edit, move, or delete a file (including running an OpenSpec command), ask a direct yes/no question ("Want me to create it?") and wait for the user's confirmation in a separate message. For a new change, scaffold it first as described below. **This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore. @@ -302,7 +302,7 @@ But this summary is optional. Sometimes the thinking IS the value. - **Don't fake understanding** - If something is unclear, dig deeper - **Don't rush** - Discovery is thinking time, not task time - **Don't force structure** - Let patterns emerge naturally -- **Don't auto-capture** - Offer to save insights, don't just do it. Before your first write of ANY file (change artifacts, specs, \`openspec/config.yaml\`, \`openspec/schemas/**\`, anything else), ask a direct yes/no question and wait for explicit confirmation in a separate user message. Answers to design or clarifying questions are never consent to write. +- **Don't auto-capture** - Offer to save insights, don't just do it. Before the first action that could create, edit, move, or delete ANY file—including running \`openspec new change\` or another command that writes files—ask a direct yes/no question and wait for explicit confirmation in a separate user message. Answers to design or clarifying questions are never consent to write. - **Don't manually scaffold changes** - Never create a new change directory under \`openspec/changes/\` by hand. Always use \`openspec new change ""\` (with \`--store \` when applicable) so required metadata such as \`.openspec.yaml\` is created before writing artifacts. - **Do visualize** - A good diagram is worth many paragraphs - **Do explore the codebase** - Ground discussions in reality @@ -321,7 +321,7 @@ export function getOpsxExploreCommandTemplate(): CommandTemplate { tags: ['workflow', 'explore', 'experimental', 'thinking'], content: `Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. -**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) when the user explicitly asks you to—that's capturing thinking, not implementing. Answering design or clarifying questions is never such an ask: before your first write of any file, ask a direct yes/no question ("Want me to create it?") and wait for the user's confirmation. For a new change, scaffold it first as described below. +**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) when the user explicitly asks you to—that's capturing thinking, not implementing. Answering design or clarifying questions is never such an ask: before the first action that could create, edit, move, or delete a file (including running an OpenSpec command), ask a direct yes/no question ("Want me to create it?") and wait for the user's confirmation in a separate message. For a new change, scaffold it first as described below. **This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore. @@ -499,7 +499,7 @@ When things crystallize, you might offer a summary - but it's optional. Sometime - **Don't fake understanding** - If something is unclear, dig deeper - **Don't rush** - Discovery is thinking time, not task time - **Don't force structure** - Let patterns emerge naturally -- **Don't auto-capture** - Offer to save insights, don't just do it. Before your first write of ANY file (change artifacts, specs, \`openspec/config.yaml\`, \`openspec/schemas/**\`, anything else), ask a direct yes/no question and wait for explicit confirmation in a separate user message. Answers to design or clarifying questions are never consent to write. +- **Don't auto-capture** - Offer to save insights, don't just do it. Before the first action that could create, edit, move, or delete ANY file—including running \`openspec new change\` or another command that writes files—ask a direct yes/no question and wait for explicit confirmation in a separate user message. Answers to design or clarifying questions are never consent to write. - **Don't manually scaffold changes** - Never create a new change directory under \`openspec/changes/\` by hand. Always use \`openspec new change ""\` (with \`--store \` when applicable) so required metadata such as \`.openspec.yaml\` is created before writing artifacts. - **Do visualize** - A good diagram is worth many paragraphs - **Do explore the codebase** - Ground discussions in reality diff --git a/test/core/templates/explore.test.ts b/test/core/templates/explore.test.ts index 280077da83..333be29f38 100644 --- a/test/core/templates/explore.test.ts +++ b/test/core/templates/explore.test.ts @@ -80,6 +80,34 @@ describe('explore templates', () => { } }); + it('requires separate confirmation before any file-writing action (#1715)', () => { + for (const [label, body] of bodies) { + expect(body, label).toContain( + 'before the first action that could create, edit, move, or delete a file' + ); + expect(body, label).toContain('including running an OpenSpec command'); + expect(body, label).toContain('ask a direct yes/no question'); + expect(body, label).toContain("wait for the user's confirmation in a separate message"); + expect(body, label).toContain( + 'Answers to design or clarifying questions are never consent to write' + ); + } + }); + + it('treats workflow configuration and write-capable commands as changes (#1715)', () => { + for (const [label, body] of bodies) { + expect(body, label).toContain( + 'creating or editing schemas, templates, or `openspec/config.yaml` is a change' + ); + expect(body, label).toContain( + 'including running `openspec new change` or another command that writes files' + ); + expect(body, label).toContain( + 'Creating OpenSpec artifacts once the user confirms is fine, writing anything else is not' + ); + } + }); + it('scaffolds a new change before capturing exploration artifacts (#668, #720)', () => { for (const [label, body] of bodies) { const transition = newChangeTransition(body, label); diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index f873f63203..3c6a9fe725 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -38,14 +38,14 @@ import { import { STORE_SELECTION_GUIDANCE } from '../../../src/core/templates/workflows/store-selection.js'; const EXPECTED_FUNCTION_HASHES: Record = { - getExploreSkillTemplate: '401a3b7ba5cbeb81715db21864c4bf542ea25e51058a156ee8415f040ddcc3a7', + getExploreSkillTemplate: '38ef6b4817ff8e3583db52abb3422f864a71f42f5d76e6b1f7c67463010e51b2', getNewChangeSkillTemplate: 'eabd1e895c5881dcb17dcbaa3fb26098dd59e8eacb318e400820b4dc811ef781', getContinueChangeSkillTemplate: '012136f6411a99c8fa228e2f9444cb64b0a89e0f56fdeac2fe03b2f5bee0c5d7', getApplyChangeSkillTemplate: 'd1e7d5ceb85193c0964057dbb88e9651526754bd33f84020e2440ff0621d5dbb', getFfChangeSkillTemplate: '5501740e7ec36ab23ab8c3a0d6dd0655a5e2f35433c7b90e82904fef5e7a326a', getSyncSpecsSkillTemplate: 'b099e2ff31859c9b10d928066e662524f9aad9ecf2be12fceacb732d718c4146', getOnboardSkillTemplate: '3a836faae463d88c289a1c129cb7ee556a563b7e53e1a52a4711ff152a3b51f7', - getOpsxExploreCommandTemplate: '92657ee1483d467dbf7db4ff5bfefbb19783a3fdde6206fe21d5c34f7e87bc59', + getOpsxExploreCommandTemplate: '060150041cd075ae160abeaea426040e19b6c3bd80ae93f8cb541c33b8a532e0', getOpsxNewCommandTemplate: 'f2d30e569798a4c92ba932859d6ba4e0ad10e18feccbade1cfee0957597b3463', getOpsxContinueCommandTemplate: 'e50e50266efa1b8e64ff9b6274ee8254f0a240d6adc1b862d126e2f1c9d3a559', getOpsxApplyCommandTemplate: 'e3579ac78f2e2c75fa3d3a7ac7dc3e49c395e96f7323398f0f041d94f8de9bb0', @@ -66,7 +66,7 @@ const EXPECTED_FUNCTION_HASHES: Record = { }; const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { - 'openspec-explore': '6ee737ed85cbe74bc1b35b1b86d98159cbd617b54013f6afaa4aff8be88305c3', + 'openspec-explore': 'c9591a8ca08eaae598da619a9153e53487229f0507cccf710f5e2ac983db16b1', 'openspec-new-change': 'ec4529beef978e34634a6f7286fab55d68fad8fb374dceb45691d52caab33fbb', 'openspec-continue-change': 'bb6194a16c54891cdb253678e8f70ce53b2af86735243980f366ce551d37e42e', 'openspec-apply-change': '81ea96d9fa6ec8536cd23c1fe561ed28e1cc1cad0a8ceb700588e08974cc0e49', From 62ef54f4e4fcf4efed2082798e5162f96f7c429a Mon Sep 17 00:00:00 2001 From: Clay Good Date: Mon, 24 Aug 2026 10:22:23 -0500 Subject: [PATCH 10/15] fix(explore): scope write confirmation precisely --- .changeset/explore-explicit-write-confirmation.md | 2 +- skills/openspec-explore/SKILL.md | 6 +++--- src/core/templates/workflows/explore.ts | 12 ++++++------ test/core/templates/explore.test.ts | 14 +++++++++----- test/core/templates/skill-templates-parity.test.ts | 6 +++--- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/.changeset/explore-explicit-write-confirmation.md b/.changeset/explore-explicit-write-confirmation.md index b782d55b1c..cc07a77e23 100644 --- a/.changeset/explore-explicit-write-confirmation.md +++ b/.changeset/explore-explicit-write-confirmation.md @@ -2,4 +2,4 @@ '@fission-ai/openspec': patch --- -explore: require explicit confirmation before the skill uses any command or tool that can create, edit, move, or delete a file. The explore skill's guardrails let "if the user asks" cover answers to its own clarifying questions, so an agent could treat a design discussion as a go-ahead and start creating schemas or editing `openspec/config.yaml` uninvited. The skill and the `/opsx:explore` command now instruct the agent to ask a direct yes/no question and wait for the user's confirmation in a separate message before the first file-writing action, and the "Don't implement" guardrail now names workflow configuration — schemas, templates, `openspec/config.yaml` — as changes rather than thinking. +explore: require explicit, scope-bound confirmation before the skill uses any command or tool that can create, edit, move, or delete a file. The explore skill's guardrails let "if the user asks" cover answers to its own clarifying questions, so an agent could treat a design discussion as a go-ahead and start creating schemas or editing `openspec/config.yaml` uninvited. The skill and the `/opsx:explore` command now instruct the agent to name the proposed artifacts or files, ask a direct yes/no question, and wait for confirmation in a separate message before writing. Read-only commands and tools remain available without confirmation, and expanding the confirmed scope requires another confirmation. diff --git a/skills/openspec-explore/SKILL.md b/skills/openspec-explore/SKILL.md index 583eace7d6..706552004e 100644 --- a/skills/openspec-explore/SKILL.md +++ b/skills/openspec-explore/SKILL.md @@ -11,7 +11,7 @@ metadata: Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. -**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) when the user explicitly asks you to—that's capturing thinking, not implementing. Answering design or clarifying questions is never such an ask: before the first action that could create, edit, move, or delete a file (including running an OpenSpec command), ask a direct yes/no question ("Want me to create it?") and wait for the user's confirmation in a separate message. For a new change, scaffold it first as described below. +**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, investigate the codebase, and run read-only commands or tools without confirmation, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create or update OpenSpec change artifacts (proposals, designs, specs) within a confirmed scope—that's capturing thinking, not implementing. Answering design or clarifying questions is never consent to write. Before the first write-capable action, name the artifacts or files you would change and what you would do, ask a direct yes/no question, and wait for the user's confirmation in a separate message. Confirmation covers only the scope you described; ask again before expanding it. For a new change, scaffold it first as described below. **This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore. @@ -296,11 +296,11 @@ But this summary is optional. Sometimes the thinking IS the value. ## Guardrails -- **Don't implement** - Never write code or implement features. Workflow configuration counts too: creating or editing schemas, templates, or `openspec/config.yaml` is a change, not thinking. Creating OpenSpec artifacts once the user confirms is fine, writing anything else is not. +- **Don't implement** - Never write code or implement features. Workflow configuration counts too: creating or editing schemas, templates, or `openspec/config.yaml` is a change, not thinking. Creating or updating OpenSpec change artifacts within the confirmed scope is fine, writing anything else is not. - **Don't fake understanding** - If something is unclear, dig deeper - **Don't rush** - Discovery is thinking time, not task time - **Don't force structure** - Let patterns emerge naturally -- **Don't auto-capture** - Offer to save insights, don't just do it. Before the first action that could create, edit, move, or delete ANY file—including running `openspec new change` or another command that writes files—ask a direct yes/no question and wait for explicit confirmation in a separate user message. Answers to design or clarifying questions are never consent to write. +- **Don't auto-capture** - Offer to save insights, don't just do it. Read-only commands and tools need no confirmation. Before the first write-capable action—including `openspec new change` or another command that writes files—name the artifacts or files and proposed changes, ask a direct yes/no question, and wait for explicit confirmation in a separate user message. That confirmation covers only the described scope; ask again before expanding it. Answers to design or clarifying questions are never consent to write. - **Don't manually scaffold changes** - Never create a new change directory under `openspec/changes/` by hand. Always use `openspec new change ""` (with `--store ` when applicable) so required metadata such as `.openspec.yaml` is created before writing artifacts. - **Do visualize** - A good diagram is worth many paragraphs - **Do explore the codebase** - Ground discussions in reality diff --git a/src/core/templates/workflows/explore.ts b/src/core/templates/workflows/explore.ts index d124b61f12..e9c72e5744 100644 --- a/src/core/templates/workflows/explore.ts +++ b/src/core/templates/workflows/explore.ts @@ -13,7 +13,7 @@ export function getExploreSkillTemplate(): SkillTemplate { description: 'Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change.', instructions: `Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. -**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) when the user explicitly asks you to—that's capturing thinking, not implementing. Answering design or clarifying questions is never such an ask: before the first action that could create, edit, move, or delete a file (including running an OpenSpec command), ask a direct yes/no question ("Want me to create it?") and wait for the user's confirmation in a separate message. For a new change, scaffold it first as described below. +**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, investigate the codebase, and run read-only commands or tools without confirmation, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create or update OpenSpec change artifacts (proposals, designs, specs) within a confirmed scope—that's capturing thinking, not implementing. Answering design or clarifying questions is never consent to write. Before the first write-capable action, name the artifacts or files you would change and what you would do, ask a direct yes/no question, and wait for the user's confirmation in a separate message. Confirmation covers only the scope you described; ask again before expanding it. For a new change, scaffold it first as described below. **This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore. @@ -298,11 +298,11 @@ But this summary is optional. Sometimes the thinking IS the value. ## Guardrails -- **Don't implement** - Never write code or implement features. Workflow configuration counts too: creating or editing schemas, templates, or \`openspec/config.yaml\` is a change, not thinking. Creating OpenSpec artifacts once the user confirms is fine, writing anything else is not. +- **Don't implement** - Never write code or implement features. Workflow configuration counts too: creating or editing schemas, templates, or \`openspec/config.yaml\` is a change, not thinking. Creating or updating OpenSpec change artifacts within the confirmed scope is fine, writing anything else is not. - **Don't fake understanding** - If something is unclear, dig deeper - **Don't rush** - Discovery is thinking time, not task time - **Don't force structure** - Let patterns emerge naturally -- **Don't auto-capture** - Offer to save insights, don't just do it. Before the first action that could create, edit, move, or delete ANY file—including running \`openspec new change\` or another command that writes files—ask a direct yes/no question and wait for explicit confirmation in a separate user message. Answers to design or clarifying questions are never consent to write. +- **Don't auto-capture** - Offer to save insights, don't just do it. Read-only commands and tools need no confirmation. Before the first write-capable action—including \`openspec new change\` or another command that writes files—name the artifacts or files and proposed changes, ask a direct yes/no question, and wait for explicit confirmation in a separate user message. That confirmation covers only the described scope; ask again before expanding it. Answers to design or clarifying questions are never consent to write. - **Don't manually scaffold changes** - Never create a new change directory under \`openspec/changes/\` by hand. Always use \`openspec new change ""\` (with \`--store \` when applicable) so required metadata such as \`.openspec.yaml\` is created before writing artifacts. - **Do visualize** - A good diagram is worth many paragraphs - **Do explore the codebase** - Ground discussions in reality @@ -321,7 +321,7 @@ export function getOpsxExploreCommandTemplate(): CommandTemplate { tags: ['workflow', 'explore', 'experimental', 'thinking'], content: `Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. -**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) when the user explicitly asks you to—that's capturing thinking, not implementing. Answering design or clarifying questions is never such an ask: before the first action that could create, edit, move, or delete a file (including running an OpenSpec command), ask a direct yes/no question ("Want me to create it?") and wait for the user's confirmation in a separate message. For a new change, scaffold it first as described below. +**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, investigate the codebase, and run read-only commands or tools without confirmation, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create or update OpenSpec change artifacts (proposals, designs, specs) within a confirmed scope—that's capturing thinking, not implementing. Answering design or clarifying questions is never consent to write. Before the first write-capable action, name the artifacts or files you would change and what you would do, ask a direct yes/no question, and wait for the user's confirmation in a separate message. Confirmation covers only the scope you described; ask again before expanding it. For a new change, scaffold it first as described below. **This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore. @@ -495,11 +495,11 @@ When things crystallize, you might offer a summary - but it's optional. Sometime ## Guardrails -- **Don't implement** - Never write code or implement features. Workflow configuration counts too: creating or editing schemas, templates, or \`openspec/config.yaml\` is a change, not thinking. Creating OpenSpec artifacts once the user confirms is fine, writing anything else is not. +- **Don't implement** - Never write code or implement features. Workflow configuration counts too: creating or editing schemas, templates, or \`openspec/config.yaml\` is a change, not thinking. Creating or updating OpenSpec change artifacts within the confirmed scope is fine, writing anything else is not. - **Don't fake understanding** - If something is unclear, dig deeper - **Don't rush** - Discovery is thinking time, not task time - **Don't force structure** - Let patterns emerge naturally -- **Don't auto-capture** - Offer to save insights, don't just do it. Before the first action that could create, edit, move, or delete ANY file—including running \`openspec new change\` or another command that writes files—ask a direct yes/no question and wait for explicit confirmation in a separate user message. Answers to design or clarifying questions are never consent to write. +- **Don't auto-capture** - Offer to save insights, don't just do it. Read-only commands and tools need no confirmation. Before the first write-capable action—including \`openspec new change\` or another command that writes files—name the artifacts or files and proposed changes, ask a direct yes/no question, and wait for explicit confirmation in a separate user message. That confirmation covers only the described scope; ask again before expanding it. Answers to design or clarifying questions are never consent to write. - **Don't manually scaffold changes** - Never create a new change directory under \`openspec/changes/\` by hand. Always use \`openspec new change ""\` (with \`--store \` when applicable) so required metadata such as \`.openspec.yaml\` is created before writing artifacts. - **Do visualize** - A good diagram is worth many paragraphs - **Do explore the codebase** - Ground discussions in reality diff --git a/test/core/templates/explore.test.ts b/test/core/templates/explore.test.ts index 333be29f38..71f6a1b9dc 100644 --- a/test/core/templates/explore.test.ts +++ b/test/core/templates/explore.test.ts @@ -83,13 +83,17 @@ describe('explore templates', () => { it('requires separate confirmation before any file-writing action (#1715)', () => { for (const [label, body] of bodies) { expect(body, label).toContain( - 'before the first action that could create, edit, move, or delete a file' + 'Before the first write-capable action' ); - expect(body, label).toContain('including running an OpenSpec command'); + expect(body, label).toContain('name the artifacts or files you would change'); expect(body, label).toContain('ask a direct yes/no question'); expect(body, label).toContain("wait for the user's confirmation in a separate message"); expect(body, label).toContain( - 'Answers to design or clarifying questions are never consent to write' + 'Answering design or clarifying questions is never consent to write' + ); + expect(body, label).toContain('run read-only commands or tools without confirmation'); + expect(body, label).toContain( + 'Confirmation covers only the scope you described; ask again before expanding it' ); } }); @@ -100,10 +104,10 @@ describe('explore templates', () => { 'creating or editing schemas, templates, or `openspec/config.yaml` is a change' ); expect(body, label).toContain( - 'including running `openspec new change` or another command that writes files' + 'including `openspec new change` or another command that writes files' ); expect(body, label).toContain( - 'Creating OpenSpec artifacts once the user confirms is fine, writing anything else is not' + 'Creating or updating OpenSpec change artifacts within the confirmed scope is fine, writing anything else is not' ); } }); diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 3c6a9fe725..e7bee01f34 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -38,14 +38,14 @@ import { import { STORE_SELECTION_GUIDANCE } from '../../../src/core/templates/workflows/store-selection.js'; const EXPECTED_FUNCTION_HASHES: Record = { - getExploreSkillTemplate: '38ef6b4817ff8e3583db52abb3422f864a71f42f5d76e6b1f7c67463010e51b2', + getExploreSkillTemplate: 'cf59415faf8fe340b02a83e6d1267b8fabe78a9ee6d3856353daa5cb09c54279', getNewChangeSkillTemplate: 'eabd1e895c5881dcb17dcbaa3fb26098dd59e8eacb318e400820b4dc811ef781', getContinueChangeSkillTemplate: '012136f6411a99c8fa228e2f9444cb64b0a89e0f56fdeac2fe03b2f5bee0c5d7', getApplyChangeSkillTemplate: 'd1e7d5ceb85193c0964057dbb88e9651526754bd33f84020e2440ff0621d5dbb', getFfChangeSkillTemplate: '5501740e7ec36ab23ab8c3a0d6dd0655a5e2f35433c7b90e82904fef5e7a326a', getSyncSpecsSkillTemplate: 'b099e2ff31859c9b10d928066e662524f9aad9ecf2be12fceacb732d718c4146', getOnboardSkillTemplate: '3a836faae463d88c289a1c129cb7ee556a563b7e53e1a52a4711ff152a3b51f7', - getOpsxExploreCommandTemplate: '060150041cd075ae160abeaea426040e19b6c3bd80ae93f8cb541c33b8a532e0', + getOpsxExploreCommandTemplate: '6d7031b7801b25eb4698831b9f07d1d0ace394f56e9197a8deea3f4cb7194b22', getOpsxNewCommandTemplate: 'f2d30e569798a4c92ba932859d6ba4e0ad10e18feccbade1cfee0957597b3463', getOpsxContinueCommandTemplate: 'e50e50266efa1b8e64ff9b6274ee8254f0a240d6adc1b862d126e2f1c9d3a559', getOpsxApplyCommandTemplate: 'e3579ac78f2e2c75fa3d3a7ac7dc3e49c395e96f7323398f0f041d94f8de9bb0', @@ -66,7 +66,7 @@ const EXPECTED_FUNCTION_HASHES: Record = { }; const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { - 'openspec-explore': 'c9591a8ca08eaae598da619a9153e53487229f0507cccf710f5e2ac983db16b1', + 'openspec-explore': '39d8f9052d752f2618d5c7c11010435309775e310d2f245b4eb0f3f4830e594f', 'openspec-new-change': 'ec4529beef978e34634a6f7286fab55d68fad8fb374dceb45691d52caab33fbb', 'openspec-continue-change': 'bb6194a16c54891cdb253678e8f70ce53b2af86735243980f366ce551d37e42e', 'openspec-apply-change': '81ea96d9fa6ec8536cd23c1fe561ed28e1cc1cad0a8ceb700588e08974cc0e49', From d34bb31ffd8c6e5bf599536c52949682a7dd36a7 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Mon, 24 Aug 2026 14:38:43 -0500 Subject: [PATCH 11/15] test(guidance): pin store-aware spec reads --- test/core/templates/spec-inventory.test.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/core/templates/spec-inventory.test.ts b/test/core/templates/spec-inventory.test.ts index 97dfb8dd6d..3f74e810e9 100644 --- a/test/core/templates/spec-inventory.test.ts +++ b/test/core/templates/spec-inventory.test.ts @@ -20,6 +20,7 @@ import { loadSchema } from '../../../src/core/artifact-graph/schema.js'; // was taught, gets the in-flight change list, and reports the step complete // against the wrong object. const SPEC_INVENTORY = 'openspec list --specs'; +const SPEC_READ = 'openspec show "" --type spec --json --no-scenarios'; // Assertions about the guidance attached to the command are scoped to a window // after it rather than to the whole body, so an unrelated occurrence elsewhere @@ -125,7 +126,16 @@ describe('spec inventory vocabulary (#1689)', () => { // disambiguates a same-named change, JSON makes the result structured, // and --no-scenarios avoids pulling every scenario into context. expect(passage, `${label} does not name the complete store-aware read`).toContain( - 'openspec show "" --type spec --json --no-scenarios' + SPEC_READ + ); + + // Tie the conditional store qualifier to the read itself. A separate + // --store mention for the inventory list must not let a local-root read + // pass this guard. + const readStart = passage.indexOf(SPEC_READ); + const readContext = passage.slice(readStart, readStart + 350); + expect(readContext, `${label} does not apply the store rule to the read`).toMatch( + /(?:same `--store` rule|Append `--store ""` to\s+both commands only for a registered standalone store)/ ); } }); From 308e8df4845560598d5d95d63d5d13f501aa504d Mon Sep 17 00:00:00 2001 From: Clay Good Date: Wed, 26 Aug 2026 13:03:52 -0500 Subject: [PATCH 12/15] test(templates): regenerate explore parity hashes The explore template now carries three independent guidance edits: the spec-inventory verb, the ASCII diagram conversion, and the write confirmation contract. Each pinned its own hash constants, so the pinned values no longer describe the combined template. Regenerate them from the merged source with `regen:parity-hashes` rather than hand-editing, and confirm the committed skills mirror still matches byte-for-byte. Co-Authored-By: Claude Opus 5 --- test/core/templates/skill-templates-parity.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index eeb57e2828..a4404b22bb 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -38,14 +38,14 @@ import { import { STORE_SELECTION_GUIDANCE } from '../../../src/core/templates/workflows/store-selection.js'; const EXPECTED_FUNCTION_HASHES: Record = { - getExploreSkillTemplate: '8dcf8890d5e7e44c379b7c019fd5e496cf469f8350121cce1d5583d4d54ce047', + getExploreSkillTemplate: 'e20cc6ef9532458a62662a9fcd8834e62ea6684185bf096cbfb09b8ecddb9f03', getNewChangeSkillTemplate: 'eabd1e895c5881dcb17dcbaa3fb26098dd59e8eacb318e400820b4dc811ef781', getContinueChangeSkillTemplate: '012136f6411a99c8fa228e2f9444cb64b0a89e0f56fdeac2fe03b2f5bee0c5d7', getApplyChangeSkillTemplate: 'd1e7d5ceb85193c0964057dbb88e9651526754bd33f84020e2440ff0621d5dbb', getFfChangeSkillTemplate: '5501740e7ec36ab23ab8c3a0d6dd0655a5e2f35433c7b90e82904fef5e7a326a', getSyncSpecsSkillTemplate: 'b099e2ff31859c9b10d928066e662524f9aad9ecf2be12fceacb732d718c4146', getOnboardSkillTemplate: '3a836faae463d88c289a1c129cb7ee556a563b7e53e1a52a4711ff152a3b51f7', - getOpsxExploreCommandTemplate: '3bf4bf57784166ed04be68946a4c45e8df629f69d4d52f57f4a863fa69a5ce1d', + getOpsxExploreCommandTemplate: '391aaec5a45fcd4a55cdbfd8e1a38097a167a93896ffd507e7f94aa814d460e5', getOpsxNewCommandTemplate: 'f2d30e569798a4c92ba932859d6ba4e0ad10e18feccbade1cfee0957597b3463', getOpsxContinueCommandTemplate: 'e50e50266efa1b8e64ff9b6274ee8254f0a240d6adc1b862d126e2f1c9d3a559', getOpsxApplyCommandTemplate: 'e3579ac78f2e2c75fa3d3a7ac7dc3e49c395e96f7323398f0f041d94f8de9bb0', @@ -66,7 +66,7 @@ const EXPECTED_FUNCTION_HASHES: Record = { }; const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { - 'openspec-explore': 'd5b0a30e36edaf353d30562ca304ee8145c26e0d8b935221bc233755e9da91b7', + 'openspec-explore': 'e46f4d9925509a7209da49a9c4e07b5b1417050c77a6b4002384198977a54cca', 'openspec-new-change': 'ec4529beef978e34634a6f7286fab55d68fad8fb374dceb45691d52caab33fbb', 'openspec-continue-change': 'bb6194a16c54891cdb253678e8f70ce53b2af86735243980f366ce551d37e42e', 'openspec-apply-change': '81ea96d9fa6ec8536cd23c1fe561ed28e1cc1cad0a8ceb700588e08974cc0e49', From 762cca222d20c59e504043bb37b6297875b84726 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Fri, 28 Aug 2026 14:06:40 -0500 Subject: [PATCH 13/15] fix(guidance): read complete specs before coverage decisions --- .changeset/teach-spec-inventory-verb.md | 2 ++ schemas/spec-driven/schema.yaml | 3 +++ skills/openspec-explore/SKILL.md | 2 ++ src/core/templates/workflows/explore.ts | 4 ++++ test/commands/store-root-selection.test.ts | 5 +++++ .../core/templates/skill-templates-parity.test.ts | 6 +++--- test/core/templates/spec-inventory.test.ts | 15 +++++++++++++++ 7 files changed, 34 insertions(+), 3 deletions(-) diff --git a/.changeset/teach-spec-inventory-verb.md b/.changeset/teach-spec-inventory-verb.md index 787a046261..d87d39d66a 100644 --- a/.changeset/teach-spec-inventory-verb.md +++ b/.changeset/teach-spec-inventory-verb.md @@ -3,3 +3,5 @@ --- Teach the generated guidance how to find and read a project's specs. `openspec list --specs` appeared in no generated skill, command, or artifact instruction, while `openspec list --json` (the in-flight *change* list) appeared throughout, so an agent asked to read the existing specs first enumerated changes instead and reported the step complete against the wrong object. The explore skill and command now list the spec inventory alongside the change list and say which is which, and the spec-driven `proposal` and `specs` instructions name the command where they ask for existing capabilities to be researched and for a delta's path to match an existing one. Both steps carry `--store ""`, and capabilities are read with `openspec show "" --type spec --json --no-scenarios` so the read resolves against the same root the listing came from. `docs/cli.md` now documents the `--store` option on `list` and `show`, which both already accepted it. Fixes #1689. + +The filtered read is only an overview. Agents read relevant specs in full, including scenarios, before deciding what is already covered or what should change. diff --git a/schemas/spec-driven/schema.yaml b/schemas/spec-driven/schema.yaml index 91de8c1773..e03102ee0d 100644 --- a/schemas/spec-driven/schema.yaml +++ b/schemas/spec-driven/schema.yaml @@ -28,6 +28,9 @@ artifacts: error. `openspec list` without `--specs` lists in-flight changes, not specs - it never shows what the project already covers. Reuse an existing capability's exact path instead of introducing a near-duplicate name. + The filtered read is only an overview. Before deciding what is already + covered or what should change, read each relevant spec in full, including + scenarios, with `openspec show "" --type spec` (same `--store` rule). Each capability listed here will need a corresponding spec file. Every change must either declare at least one capability (new or diff --git a/skills/openspec-explore/SKILL.md b/skills/openspec-explore/SKILL.md index 030d7a0e44..b7a7888a71 100644 --- a/skills/openspec-explore/SKILL.md +++ b/skills/openspec-explore/SKILL.md @@ -102,6 +102,8 @@ openspec list --specs ``` Add `--json` for ids and requirement counts, and append `--store ""` only for a registered standalone store. This is the inventory of what the project already claims to do, and `openspec list` on its own never shows it. To look at one, run `openspec show "" --type spec --json --no-scenarios` (same `--store` rule) - it returns that capability's purpose and requirement texts without pulling the whole spec file into context, and `--type spec` stops a change of the same name from making it ambiguous. +The filtered read is only an overview. Before deciding what is already covered or what should change, read each relevant spec in full, including scenarios, with `openspec show "" --type spec` (same `--store` rule). + Then read the project's own context from the resolved root - `/openspec/config.yaml` (or `config.yml`). Use the `root.path` returned above, and skip this if neither file exists: - `context`: project background - tech stack, conventions, constraints - `rules`: keyed by artifact id - the entries for an artifact apply only when you write that artifact diff --git a/src/core/templates/workflows/explore.ts b/src/core/templates/workflows/explore.ts index dee32f8bab..ff18076e31 100644 --- a/src/core/templates/workflows/explore.ts +++ b/src/core/templates/workflows/explore.ts @@ -104,6 +104,8 @@ openspec list --specs \`\`\` Add \`--json\` for ids and requirement counts, and append \`--store ""\` only for a registered standalone store. This is the inventory of what the project already claims to do, and \`openspec list\` on its own never shows it. To look at one, run \`openspec show "" --type spec --json --no-scenarios\` (same \`--store\` rule) - it returns that capability's purpose and requirement texts without pulling the whole spec file into context, and \`--type spec\` stops a change of the same name from making it ambiguous. +The filtered read is only an overview. Before deciding what is already covered or what should change, read each relevant spec in full, including scenarios, with \`openspec show "" --type spec\` (same \`--store\` rule). + Then read the project's own context from the resolved root - \`/openspec/config.yaml\` (or \`config.yml\`). Use the \`root.path\` returned above, and skip this if neither file exists: - \`context\`: project background - tech stack, conventions, constraints - \`rules\`: keyed by artifact id - the entries for an artifact apply only when you write that artifact @@ -428,6 +430,8 @@ openspec list --specs \`\`\` Add \`--json\` for ids and requirement counts, and append \`--store ""\` only for a registered standalone store. This is the inventory of what the project already claims to do, and \`openspec list\` on its own never shows it. To look at one, run \`openspec show "" --type spec --json --no-scenarios\` (same \`--store\` rule) - it returns that capability's purpose and requirement texts without pulling the whole spec file into context, and \`--type spec\` stops a change of the same name from making it ambiguous. +The filtered read is only an overview. Before deciding what is already covered or what should change, read each relevant spec in full, including scenarios, with \`openspec show "" --type spec\` (same \`--store\` rule). + Then read the project's own context from the resolved root - \`/openspec/config.yaml\` (or \`config.yml\`). Use the \`root.path\` returned above, and skip this if neither file exists: - \`context\`: project background - tech stack, conventions, constraints - \`rules\`: keyed by artifact id - the entries for an artifact apply only when you write that artifact diff --git a/test/commands/store-root-selection.test.ts b/test/commands/store-root-selection.test.ts index 133e0653b2..b601cc7aa5 100644 --- a/test/commands/store-root-selection.test.ts +++ b/test/commands/store-root-selection.test.ts @@ -180,6 +180,11 @@ describe('store root selection for normal commands', () => { requirements: [{ text: 'The system SHALL bill.', scenarios: [] }], root: json.root, }); + + // The overview omits scenarios; decisions use the complete spec. + const full = await runCLI(['show', id, '--type', 'spec', ...storeArgs], { cwd: appRepo, env }); + expect(full.exitCode).toBe(0); + expect(full.stdout.trim()).toBe(spec.trim()); } }, 30_000 diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index a4404b22bb..e0ecc2f123 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -38,14 +38,14 @@ import { import { STORE_SELECTION_GUIDANCE } from '../../../src/core/templates/workflows/store-selection.js'; const EXPECTED_FUNCTION_HASHES: Record = { - getExploreSkillTemplate: 'e20cc6ef9532458a62662a9fcd8834e62ea6684185bf096cbfb09b8ecddb9f03', + getExploreSkillTemplate: '6dfb17fb7a29e8bac9a15f7aa6fd677fd4ce12c99284919983efb9f54a690729', getNewChangeSkillTemplate: 'eabd1e895c5881dcb17dcbaa3fb26098dd59e8eacb318e400820b4dc811ef781', getContinueChangeSkillTemplate: '012136f6411a99c8fa228e2f9444cb64b0a89e0f56fdeac2fe03b2f5bee0c5d7', getApplyChangeSkillTemplate: 'd1e7d5ceb85193c0964057dbb88e9651526754bd33f84020e2440ff0621d5dbb', getFfChangeSkillTemplate: '5501740e7ec36ab23ab8c3a0d6dd0655a5e2f35433c7b90e82904fef5e7a326a', getSyncSpecsSkillTemplate: 'b099e2ff31859c9b10d928066e662524f9aad9ecf2be12fceacb732d718c4146', getOnboardSkillTemplate: '3a836faae463d88c289a1c129cb7ee556a563b7e53e1a52a4711ff152a3b51f7', - getOpsxExploreCommandTemplate: '391aaec5a45fcd4a55cdbfd8e1a38097a167a93896ffd507e7f94aa814d460e5', + getOpsxExploreCommandTemplate: '0010dfbbd4f8506947ec25eeb132a5f840cfe51e49f9b53c05a730b6decdd939', getOpsxNewCommandTemplate: 'f2d30e569798a4c92ba932859d6ba4e0ad10e18feccbade1cfee0957597b3463', getOpsxContinueCommandTemplate: 'e50e50266efa1b8e64ff9b6274ee8254f0a240d6adc1b862d126e2f1c9d3a559', getOpsxApplyCommandTemplate: 'e3579ac78f2e2c75fa3d3a7ac7dc3e49c395e96f7323398f0f041d94f8de9bb0', @@ -66,7 +66,7 @@ const EXPECTED_FUNCTION_HASHES: Record = { }; const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { - 'openspec-explore': 'e46f4d9925509a7209da49a9c4e07b5b1417050c77a6b4002384198977a54cca', + 'openspec-explore': '05812dcf995542f53e937de9798534bb2751ce97e587325cd4b66c6ed247e814', 'openspec-new-change': 'ec4529beef978e34634a6f7286fab55d68fad8fb374dceb45691d52caab33fbb', 'openspec-continue-change': 'bb6194a16c54891cdb253678e8f70ce53b2af86735243980f366ce551d37e42e', 'openspec-apply-change': '81ea96d9fa6ec8536cd23c1fe561ed28e1cc1cad0a8ceb700588e08974cc0e49', diff --git a/test/core/templates/spec-inventory.test.ts b/test/core/templates/spec-inventory.test.ts index 3f74e810e9..b1dcdf890a 100644 --- a/test/core/templates/spec-inventory.test.ts +++ b/test/core/templates/spec-inventory.test.ts @@ -139,4 +139,19 @@ describe('spec inventory vocabulary (#1689)', () => { ); } }); + + it('reads full relevant specs before deciding coverage or changes', () => { + const sites: Array<[string, string]> = [ + ...exploreBodies, + ['proposal instruction', instructionFor('proposal')], + ]; + + for (const [label, body] of sites) { + const normalized = body.replace(/\s+/g, ' '); + expect(normalized, label).toContain('The filtered read is only an overview.'); + expect(normalized, label).toContain( + 'Before deciding what is already covered or what should change, read each relevant spec in full, including scenarios, with `openspec show "" --type spec` (same `--store` rule).' + ); + } + }); }); From 7d722f316d909f973cbdf5f816afecf6172eb8cf Mon Sep 17 00:00:00 2001 From: Clay Good Date: Mon, 7 Sep 2026 11:07:14 -0500 Subject: [PATCH 14/15] docs: drop the redundant legacy docs/cli.md edit docs-lab/README.md makes docs-lab/ canonical and the old docs/ tree legacy. docs-lab/reference/cli.md already documents `--store ` for both `openspec list` and `openspec show`, so this branch's docs/cli.md rows added a third copy in the stale tree and nothing else. Co-Authored-By: Claude Opus 5 --- docs/cli.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index ff67716aae..e74bc60f2d 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -444,7 +444,6 @@ openspec list [options] | `--changes` | List changes (default) | | `--sort ` | Sort by `recent` (default) or `name` | | `--json` | Output as JSON | -| `--store ` | Use a registered store as the OpenSpec root | **Examples:** @@ -501,7 +500,6 @@ openspec show [item-name] [options] | `--type ` | Specify type: `change` or `spec` (auto-detected if unambiguous) | | `--json` | Output as JSON | | `--no-interactive` | Disable prompts | -| `--store ` | Use a registered store as the OpenSpec root | **Change-specific options:** From 3ddad96148e5ad80ea10efd3944f8a00c0f8860f Mon Sep 17 00:00:00 2001 From: Clay Good Date: Wed, 9 Sep 2026 09:45:27 -0500 Subject: [PATCH 15/15] chore(changeset): drop the docs claim this PR no longer makes alfred-openspec on #1700: the release note still said docs/cli.md now documents --store on list and show, but that legacy-tree edit was removed from this head and the diff does not touch docs/cli.md. The canonical docs-lab/reference/cli.md already documented the flag on both commands, which is why the edit went. Removing the sentence rather than repointing it at docs-lab: nothing in docs-lab changed either, so there is no documentation change to announce. Co-Authored-By: Claude Opus 5 --- .changeset/teach-spec-inventory-verb.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/teach-spec-inventory-verb.md b/.changeset/teach-spec-inventory-verb.md index d87d39d66a..aad490682c 100644 --- a/.changeset/teach-spec-inventory-verb.md +++ b/.changeset/teach-spec-inventory-verb.md @@ -2,6 +2,6 @@ "@fission-ai/openspec": patch --- -Teach the generated guidance how to find and read a project's specs. `openspec list --specs` appeared in no generated skill, command, or artifact instruction, while `openspec list --json` (the in-flight *change* list) appeared throughout, so an agent asked to read the existing specs first enumerated changes instead and reported the step complete against the wrong object. The explore skill and command now list the spec inventory alongside the change list and say which is which, and the spec-driven `proposal` and `specs` instructions name the command where they ask for existing capabilities to be researched and for a delta's path to match an existing one. Both steps carry `--store ""`, and capabilities are read with `openspec show "" --type spec --json --no-scenarios` so the read resolves against the same root the listing came from. `docs/cli.md` now documents the `--store` option on `list` and `show`, which both already accepted it. Fixes #1689. +Teach the generated guidance how to find and read a project's specs. `openspec list --specs` appeared in no generated skill, command, or artifact instruction, while `openspec list --json` (the in-flight *change* list) appeared throughout, so an agent asked to read the existing specs first enumerated changes instead and reported the step complete against the wrong object. The explore skill and command now list the spec inventory alongside the change list and say which is which, and the spec-driven `proposal` and `specs` instructions name the command where they ask for existing capabilities to be researched and for a delta's path to match an existing one. Both steps carry `--store ""`, and capabilities are read with `openspec show "" --type spec --json --no-scenarios` so the read resolves against the same root the listing came from. Fixes #1689. The filtered read is only an overview. Agents read relevant specs in full, including scenarios, before deciding what is already covered or what should change.