From 6e624afdf4548063fe807d4b32c5915fe3f3f8b4 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Mon, 3 Aug 2026 18:12:48 -0500 Subject: [PATCH 1/7] fix(propose): honor explicit schema selection --- skills/openspec-propose/SKILL.md | 19 ++++++++-- src/core/templates/workflows/propose.ts | 38 +++++++++++++++---- test/core/templates/propose.test.ts | 22 ++++++++++- .../templates/skill-templates-parity.test.ts | 6 +-- 4 files changed, 68 insertions(+), 17 deletions(-) diff --git a/skills/openspec-propose/SKILL.md b/skills/openspec-propose/SKILL.md index 3b852b58d3..7efb95941f 100644 --- a/skills/openspec-propose/SKILL.md +++ b/skills/openspec-propose/SKILL.md @@ -40,13 +40,24 @@ When the user is ready to implement, they must start the apply workflow explicit If the request contains ambiguity that would materially affect scope, externally observable behavior, compatibility, or acceptance criteria, ask the user before creating the change. For minor details, make a reasonable assumption and record it in the planning artifacts. -2. **Create the change directory** +2. **Determine the workflow schema** + + Use the configured default schema unless the user explicitly requests a different workflow. + + **Use a different schema only if the user mentions:** + - A specific schema name → use `--schema ` + - "show workflows" or "what workflows" → run `openspec schemas --json` and let them choose + + Otherwise, omit `--schema` to preserve the configured default. + +3. **Create the change directory** ```bash openspec new change "" ``` + Add `--schema ` only if the user requested a specific workflow. This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. -3. **Get the artifact build order** +4. **Get the artifact build order** ```bash openspec status --change "" --json ``` @@ -55,7 +66,7 @@ When the user is ready to implement, they must start the apply workflow explicit - `artifacts`: list of all artifacts, each with its `status` and its `requires` edges (the artifact IDs it directly depends on) - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. -4. **Create every artifact in the required set** +5. **Create every artifact in the required set** Use a todo list to track progress through the artifacts. @@ -94,7 +105,7 @@ When the user is ready to implement, they must start the apply workflow explicit - Ask the user to clarify - Then continue with creation -5. **Show final status** +6. **Show final status** ```bash openspec status --change "" ``` diff --git a/src/core/templates/workflows/propose.ts b/src/core/templates/workflows/propose.ts index c8f54c14a1..742b2c3965 100644 --- a/src/core/templates/workflows/propose.ts +++ b/src/core/templates/workflows/propose.ts @@ -42,13 +42,24 @@ ${STORE_SELECTION_GUIDANCE} If the request contains ambiguity that would materially affect scope, externally observable behavior, compatibility, or acceptance criteria, ask the user before creating the change. For minor details, make a reasonable assumption and record it in the planning artifacts. -2. **Create the change directory** +2. **Determine the workflow schema** + + Use the configured default schema unless the user explicitly requests a different workflow. + + **Use a different schema only if the user mentions:** + - A specific schema name → use \`--schema \` + - "show workflows" or "what workflows" → run \`openspec schemas --json\` and let them choose + + Otherwise, omit \`--schema\` to preserve the configured default. + +3. **Create the change directory** \`\`\`bash openspec new change "" \`\`\` + Add \`--schema \` only if the user requested a specific workflow. This creates a scaffolded change in the planning home resolved by the CLI with \`.openspec.yaml\`. -3. **Get the artifact build order** +4. **Get the artifact build order** \`\`\`bash openspec status --change "" --json \`\`\` @@ -57,7 +68,7 @@ ${STORE_SELECTION_GUIDANCE} - \`artifacts\`: list of all artifacts, each with its \`status\` and its \`requires\` edges (the artifact IDs it directly depends on) - \`planningHome\`, \`changeRoot\`, \`artifactPaths\`, and \`actionContext\`: path and scope context. Use these instead of assuming repo-local paths. -4. **Create every artifact in the required set** +5. **Create every artifact in the required set** Use a todo list to track progress through the artifacts. @@ -96,7 +107,7 @@ ${STORE_SELECTION_GUIDANCE} - Ask the user to clarify - Then continue with creation -5. **Show final status** +6. **Show final status** \`\`\`bash openspec status --change "" \`\`\` @@ -170,13 +181,24 @@ ${STORE_SELECTION_GUIDANCE} If the request contains ambiguity that would materially affect scope, externally observable behavior, compatibility, or acceptance criteria, ask the user before creating the change. For minor details, make a reasonable assumption and record it in the planning artifacts. -2. **Create the change directory** +2. **Determine the workflow schema** + + Use the configured default schema unless the user explicitly requests a different workflow. + + **Use a different schema only if the user mentions:** + - A specific schema name → use \`--schema \` + - "show workflows" or "what workflows" → run \`openspec schemas --json\` and let them choose + + Otherwise, omit \`--schema\` to preserve the configured default. + +3. **Create the change directory** \`\`\`bash openspec new change "" \`\`\` + Add \`--schema \` only if the user requested a specific workflow. This creates a scaffolded change in the planning home resolved by the CLI with \`.openspec.yaml\`. -3. **Get the artifact build order** +4. **Get the artifact build order** \`\`\`bash openspec status --change "" --json \`\`\` @@ -185,7 +207,7 @@ ${STORE_SELECTION_GUIDANCE} - \`artifacts\`: list of all artifacts, each with its \`status\` and its \`requires\` edges (the artifact IDs it directly depends on) - \`planningHome\`, \`changeRoot\`, \`artifactPaths\`, and \`actionContext\`: path and scope context. Use these instead of assuming repo-local paths. -4. **Create every artifact in the required set** +5. **Create every artifact in the required set** Use a todo list to track progress through the artifacts. @@ -224,7 +246,7 @@ ${STORE_SELECTION_GUIDANCE} - Ask the user to clarify - Then continue with creation -5. **Show final status** +6. **Show final status** \`\`\`bash openspec status --change "" \`\`\` diff --git a/test/core/templates/propose.test.ts b/test/core/templates/propose.test.ts index 429c6e89d2..84fbe6be74 100644 --- a/test/core/templates/propose.test.ts +++ b/test/core/templates/propose.test.ts @@ -153,6 +153,23 @@ describe('propose implementation boundary', () => { }); }); +describe('propose schema selection', () => { + // #770: the CLI and new workflow already accept an explicit schema, but + // propose used to discard that request and always create with the default. + it('honors an explicitly requested workflow schema before creating the change (#770)', () => { + for (const [label, body] of proposeBodies) { + const schemaStep = body.indexOf('**Determine the workflow schema**'); + const createStep = body.indexOf('**Create the change directory**'); + + expect(schemaStep, `${label} is missing schema selection`).toBeGreaterThanOrEqual(0); + expect(createStep, `${label} is missing change creation`).toBeGreaterThan(schemaStep); + expect(body, label).toContain('openspec schemas --json'); + expect(body, label).toContain('Add `--schema ` only if the user requested a specific workflow'); + expect(body, label).toContain('Otherwise, omit `--schema` to preserve the configured default'); + } + }); +}); + describe('artifact loop guards (propose and ff)', () => { // `status` is file-existence based (detectCompleted), so writing tasks.md before // specs flips tasks to done and satisfies a bare applyRequires stop condition @@ -258,8 +275,9 @@ describe('artifact loop guards (propose and ff)', () => { } }); - // The step-4 TITLE must not use "apply-ready" either: in the prewritten-tasks - // case the change is already apply-ready when step 4 begins, so a title of + // The artifact-creation TITLE must not use "apply-ready" either: in the + // prewritten-tasks case the change is already apply-ready when this step + // begins, so a title of // "create ... until apply-ready" invites the exact early-stop this PR kills. it('titles the create step around the required set, not "apply-ready"', () => { for (const [label, body] of loopBodies) { diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 7a17b6ed55..981aa3a76b 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -57,8 +57,8 @@ const EXPECTED_FUNCTION_HASHES: Record = { getOpsxOnboardCommandTemplate: 'e04e4ab6c2f25122e6840212b4c22708812c36ceff9ec529c2bb1d1d035429e3', getOpsxBulkArchiveCommandTemplate: 'fbb4de58ed00861badd93cde9bdd3d7c52f966158a18a660152060076ea9723e', getOpsxVerifyCommandTemplate: 'ce0ee05b7a6b332e29db2298b9d5a928a1932caf516e35fd88f163154ffd43f4', - getOpsxProposeSkillTemplate: 'e175316cc654f78fea4195ee3f5173e544cc3bae35585e200833f26abbb09bd7', - getOpsxProposeCommandTemplate: '1085c01d9ce9ca576eab43887a6700007f30001978b624f7e004df7beb577028', + getOpsxProposeSkillTemplate: 'd1750192a80446d08df7e77e5498ed861ab2d903d1b0686291b038eb6bd61bfa', + getOpsxProposeCommandTemplate: 'aedb70a43558e69b1439a7698a3e42ec14712b889edf81341f6d403302ef4d4b', getFeedbackSkillTemplate: 'd7d83c5f7fc2b92fe8f4588a5bf2d9cb315e4c73ec19bcd5ef28270906319a0d', getUpdateChangeSkillTemplate: 'f85fbfb3a175e949becbef08be0eccfab97de5e7ad45105e999d2900dfafbaba', getOpsxUpdateCommandTemplate: '461edf06e92c0da3dab4f11d91d59d44b48ed30a0881c1f34a714b1813435af6', @@ -75,7 +75,7 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { 'openspec-bulk-archive-change': 'c58e1d319a6587b52202434d5d769c94718aafc0f019276cef04cf8be473b6ce', 'openspec-verify-change': '7cd65897d126f7c948620c0672ca62418620dbcb82ee73d890f758fb666a4ff8', 'openspec-onboard': '80f39cf33a138aac8e508db25d7af2c9e9bd482f90e414770e806f966dd58c9c', - 'openspec-propose': '37818ab54ffc8e60a51ec8cd9913eec8735645ea0c6c46a19e89de9b573dcf2c', + 'openspec-propose': '6f72fb88fd2287c4f3b0b0920e29b02fa249d118ac65480686fe2197e3ff8bcd', 'openspec-update-change': '95bb533105e49aee06c9ea164b63092de77644cf8f94fa38d3ee3c11b0ccb893', }; From a1208a0a0ca560edb56519215b7d5b69864859f8 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Mon, 3 Aug 2026 18:33:27 -0500 Subject: [PATCH 2/7] fix(propose): harden schema selection guidance --- skills/openspec-propose/SKILL.md | 16 +++++++--- src/core/templates/workflows/propose.ts | 32 ++++++++++++++----- test/core/templates/propose.test.ts | 24 ++++++++++++-- .../templates/skill-templates-parity.test.ts | 1 - 4 files changed, 57 insertions(+), 16 deletions(-) diff --git a/skills/openspec-propose/SKILL.md b/skills/openspec-propose/SKILL.md index 7efb95941f..3556d77546 100644 --- a/skills/openspec-propose/SKILL.md +++ b/skills/openspec-propose/SKILL.md @@ -44,17 +44,25 @@ When the user is ready to implement, they must start the apply workflow explicit Use the configured default schema unless the user explicitly requests a different workflow. - **Use a different schema only if the user mentions:** - - A specific schema name → use `--schema ` - - "show workflows" or "what workflows" → run `openspec schemas --json` and let them choose + **Use a different schema only if the user:** + - Explicitly requests a specific schema by name → use `--schema ` + - Asks to "show workflows" or asks "what workflows" exist → identify the selected project or store root, then run `openspec schemas --json` with its working directory set to that root (the directory containing `openspec/`) and let them choose. For a registered store, use the store `root` returned by `openspec store list --json`; `schemas` does not accept `--store` Otherwise, omit `--schema` to preserve the configured default. 3. **Create the change directory** + + Run exactly one of these commands. + + Using the configured default: ```bash openspec new change "" ``` - Add `--schema ` only if the user requested a specific workflow. + + Using an explicitly requested schema: + ```bash + openspec new change "" --schema "" + ``` This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 4. **Get the artifact build order** diff --git a/src/core/templates/workflows/propose.ts b/src/core/templates/workflows/propose.ts index 742b2c3965..032c0d306f 100644 --- a/src/core/templates/workflows/propose.ts +++ b/src/core/templates/workflows/propose.ts @@ -46,17 +46,25 @@ ${STORE_SELECTION_GUIDANCE} Use the configured default schema unless the user explicitly requests a different workflow. - **Use a different schema only if the user mentions:** - - A specific schema name → use \`--schema \` - - "show workflows" or "what workflows" → run \`openspec schemas --json\` and let them choose + **Use a different schema only if the user:** + - Explicitly requests a specific schema by name → use \`--schema \` + - Asks to "show workflows" or asks "what workflows" exist → identify the selected project or store root, then run \`openspec schemas --json\` with its working directory set to that root (the directory containing \`openspec/\`) and let them choose. For a registered store, use the store \`root\` returned by \`openspec store list --json\`; \`schemas\` does not accept \`--store\` Otherwise, omit \`--schema\` to preserve the configured default. 3. **Create the change directory** + + Run exactly one of these commands. + + Using the configured default: \`\`\`bash openspec new change "" \`\`\` - Add \`--schema \` only if the user requested a specific workflow. + + Using an explicitly requested schema: + \`\`\`bash + openspec new change "" --schema "" + \`\`\` This creates a scaffolded change in the planning home resolved by the CLI with \`.openspec.yaml\`. 4. **Get the artifact build order** @@ -185,17 +193,25 @@ ${STORE_SELECTION_GUIDANCE} Use the configured default schema unless the user explicitly requests a different workflow. - **Use a different schema only if the user mentions:** - - A specific schema name → use \`--schema \` - - "show workflows" or "what workflows" → run \`openspec schemas --json\` and let them choose + **Use a different schema only if the user:** + - Explicitly requests a specific schema by name → use \`--schema \` + - Asks to "show workflows" or asks "what workflows" exist → identify the selected project or store root, then run \`openspec schemas --json\` with its working directory set to that root (the directory containing \`openspec/\`) and let them choose. For a registered store, use the store \`root\` returned by \`openspec store list --json\`; \`schemas\` does not accept \`--store\` Otherwise, omit \`--schema\` to preserve the configured default. 3. **Create the change directory** + + Run exactly one of these commands. + + Using the configured default: \`\`\`bash openspec new change "" \`\`\` - Add \`--schema \` only if the user requested a specific workflow. + + Using an explicitly requested schema: + \`\`\`bash + openspec new change "" --schema "" + \`\`\` This creates a scaffolded change in the planning home resolved by the CLI with \`.openspec.yaml\`. 4. **Get the artifact build order** diff --git a/test/core/templates/propose.test.ts b/test/core/templates/propose.test.ts index 84fbe6be74..10a8d680c7 100644 --- a/test/core/templates/propose.test.ts +++ b/test/core/templates/propose.test.ts @@ -156,18 +156,36 @@ describe('propose implementation boundary', () => { describe('propose schema selection', () => { // #770: the CLI and new workflow already accept an explicit schema, but // propose used to discard that request and always create with the default. - it('honors an explicitly requested workflow schema before creating the change (#770)', () => { + it('shows both concrete creation forms after an explicit schema choice (#770)', () => { for (const [label, body] of proposeBodies) { const schemaStep = body.indexOf('**Determine the workflow schema**'); const createStep = body.indexOf('**Create the change directory**'); + const statusStep = body.indexOf('**Get the artifact build order**'); expect(schemaStep, `${label} is missing schema selection`).toBeGreaterThanOrEqual(0); expect(createStep, `${label} is missing change creation`).toBeGreaterThan(schemaStep); - expect(body, label).toContain('openspec schemas --json'); - expect(body, label).toContain('Add `--schema ` only if the user requested a specific workflow'); + expect(statusStep, `${label} is missing status lookup`).toBeGreaterThan(createStep); + + const createSection = body.slice(createStep, statusStep); + expect(createSection, label).toContain('openspec new change ""'); + expect(createSection, label).toContain( + 'openspec new change "" --schema ""' + ); + expect(createSection, label).toContain('Run exactly one of these commands'); + expect(body, label).toContain('Explicitly requests a specific schema by name'); expect(body, label).toContain('Otherwise, omit `--schema` to preserve the configured default'); } }); + + it('discovers schemas from the selected project or store root', () => { + for (const [label, body] of proposeBodies) { + expect(body, label).toContain('run `openspec schemas --json` with its working directory'); + expect(body, label).toContain( + 'use the store `root` returned by `openspec store list --json`' + ); + expect(body, label).toContain('`schemas` does not accept `--store`'); + } + }); }); describe('artifact loop guards (propose and ff)', () => { diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 981aa3a76b..7a169fbc42 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -76,7 +76,6 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { 'openspec-verify-change': '7cd65897d126f7c948620c0672ca62418620dbcb82ee73d890f758fb666a4ff8', 'openspec-onboard': '80f39cf33a138aac8e508db25d7af2c9e9bd482f90e414770e806f966dd58c9c', 'openspec-propose': '6f72fb88fd2287c4f3b0b0920e29b02fa249d118ac65480686fe2197e3ff8bcd', - 'openspec-update-change': '95bb533105e49aee06c9ea164b63092de77644cf8f94fa38d3ee3c11b0ccb893', }; // Intentionally excludes getFeedbackSkillTemplate: this list only models templates From 0fb5beabb35143ade123e7c024039edcc7b3078f Mon Sep 17 00:00:00 2001 From: Clay Good Date: Mon, 3 Aug 2026 18:54:06 -0500 Subject: [PATCH 3/7] fix(propose): preserve selected store --- skills/openspec-propose/SKILL.md | 2 +- src/core/templates/workflows/propose.ts | 4 ++-- test/core/templates/propose.test.ts | 8 +++++--- test/core/templates/skill-templates-parity.test.ts | 1 + 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/skills/openspec-propose/SKILL.md b/skills/openspec-propose/SKILL.md index 3556d77546..c0e89dd717 100644 --- a/skills/openspec-propose/SKILL.md +++ b/skills/openspec-propose/SKILL.md @@ -52,7 +52,7 @@ When the user is ready to implement, they must start the apply workflow explicit 3. **Create the change directory** - Run exactly one of these commands. + Choose one schema form below. If a registered store is selected, append `--store ""` to that command and every follow-up command. Using the configured default: ```bash diff --git a/src/core/templates/workflows/propose.ts b/src/core/templates/workflows/propose.ts index 032c0d306f..cc4b1a712a 100644 --- a/src/core/templates/workflows/propose.ts +++ b/src/core/templates/workflows/propose.ts @@ -54,7 +54,7 @@ ${STORE_SELECTION_GUIDANCE} 3. **Create the change directory** - Run exactly one of these commands. + Choose one schema form below. If a registered store is selected, append \`--store ""\` to that command and every follow-up command. Using the configured default: \`\`\`bash @@ -201,7 +201,7 @@ ${STORE_SELECTION_GUIDANCE} 3. **Create the change directory** - Run exactly one of these commands. + Choose one schema form below. If a registered store is selected, append \`--store ""\` to that command and every follow-up command. Using the configured default: \`\`\`bash diff --git a/test/core/templates/propose.test.ts b/test/core/templates/propose.test.ts index 10a8d680c7..84530340aa 100644 --- a/test/core/templates/propose.test.ts +++ b/test/core/templates/propose.test.ts @@ -167,11 +167,13 @@ describe('propose schema selection', () => { expect(statusStep, `${label} is missing status lookup`).toBeGreaterThan(createStep); const createSection = body.slice(createStep, statusStep); - expect(createSection, label).toContain('openspec new change ""'); + expect(createSection, label).toMatch(/^\s*openspec new change ""\s*$/m); + expect(createSection, label).toMatch( + /^\s*openspec new change "" --schema ""\s*$/m + ); expect(createSection, label).toContain( - 'openspec new change "" --schema ""' + 'If a registered store is selected, append `--store ""`' ); - expect(createSection, label).toContain('Run exactly one of these commands'); expect(body, label).toContain('Explicitly requests a specific schema by name'); expect(body, label).toContain('Otherwise, omit `--schema` to preserve the configured default'); } diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 7a169fbc42..981aa3a76b 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -76,6 +76,7 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { 'openspec-verify-change': '7cd65897d126f7c948620c0672ca62418620dbcb82ee73d890f758fb666a4ff8', 'openspec-onboard': '80f39cf33a138aac8e508db25d7af2c9e9bd482f90e414770e806f966dd58c9c', 'openspec-propose': '6f72fb88fd2287c4f3b0b0920e29b02fa249d118ac65480686fe2197e3ff8bcd', + 'openspec-update-change': '95bb533105e49aee06c9ea164b63092de77644cf8f94fa38d3ee3c11b0ccb893', }; // Intentionally excludes getFeedbackSkillTemplate: this list only models templates From c7894fd3c96c4b65a52a8f552526de95c35ba11c Mon Sep 17 00:00:00 2001 From: Clay Good Date: Mon, 3 Aug 2026 19:14:06 -0500 Subject: [PATCH 4/7] fix(propose): respect store flag support --- skills/openspec-propose/SKILL.md | 2 +- src/core/templates/workflows/propose.ts | 4 ++-- test/core/templates/propose.test.ts | 24 ++++++++++++++----- .../templates/skill-templates-parity.test.ts | 1 - 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/skills/openspec-propose/SKILL.md b/skills/openspec-propose/SKILL.md index c0e89dd717..00b7bdbcfc 100644 --- a/skills/openspec-propose/SKILL.md +++ b/skills/openspec-propose/SKILL.md @@ -52,7 +52,7 @@ When the user is ready to implement, they must start the apply workflow explicit 3. **Create the change directory** - Choose one schema form below. If a registered store is selected, append `--store ""` to that command and every follow-up command. + Choose one schema form below. If a registered store is selected, append `--store ""` to that command and each later OpenSpec command shown below that accepts `--store`. Using the configured default: ```bash diff --git a/src/core/templates/workflows/propose.ts b/src/core/templates/workflows/propose.ts index cc4b1a712a..cff27f2c38 100644 --- a/src/core/templates/workflows/propose.ts +++ b/src/core/templates/workflows/propose.ts @@ -54,7 +54,7 @@ ${STORE_SELECTION_GUIDANCE} 3. **Create the change directory** - Choose one schema form below. If a registered store is selected, append \`--store ""\` to that command and every follow-up command. + Choose one schema form below. If a registered store is selected, append \`--store ""\` to that command and each later OpenSpec command shown below that accepts \`--store\`. Using the configured default: \`\`\`bash @@ -201,7 +201,7 @@ ${STORE_SELECTION_GUIDANCE} 3. **Create the change directory** - Choose one schema form below. If a registered store is selected, append \`--store ""\` to that command and every follow-up command. + Choose one schema form below. If a registered store is selected, append \`--store ""\` to that command and each later OpenSpec command shown below that accepts \`--store\`. Using the configured default: \`\`\`bash diff --git a/test/core/templates/propose.test.ts b/test/core/templates/propose.test.ts index 84530340aa..521ae023dd 100644 --- a/test/core/templates/propose.test.ts +++ b/test/core/templates/propose.test.ts @@ -172,20 +172,32 @@ describe('propose schema selection', () => { /^\s*openspec new change "" --schema ""\s*$/m ); expect(createSection, label).toContain( - 'If a registered store is selected, append `--store ""`' + 'If a registered store is selected, append `--store ""` to that command and each later OpenSpec command shown below that accepts `--store`' ); - expect(body, label).toContain('Explicitly requests a specific schema by name'); - expect(body, label).toContain('Otherwise, omit `--schema` to preserve the configured default'); + expect(createSection, label).not.toContain('every follow-up command'); } }); it('discovers schemas from the selected project or store root', () => { for (const [label, body] of proposeBodies) { - expect(body, label).toContain('run `openspec schemas --json` with its working directory'); - expect(body, label).toContain( + const schemaStep = body.indexOf('**Determine the workflow schema**'); + const createStep = body.indexOf('**Create the change directory**'); + const schemaSection = body.slice(schemaStep, createStep); + + expect(schemaSection, label).toContain('Use the configured default schema'); + expect(schemaSection, label).toContain('Explicitly requests a specific schema by name'); + expect(schemaSection, label).toContain('selected project or store root'); + expect(schemaSection, label).toContain( + 'run `openspec schemas --json` with its working directory' + ); + expect(schemaSection, label).toContain('the directory containing `openspec/`'); + expect(schemaSection, label).toContain( 'use the store `root` returned by `openspec store list --json`' ); - expect(body, label).toContain('`schemas` does not accept `--store`'); + expect(schemaSection, label).toContain('`schemas` does not accept `--store`'); + expect(schemaSection, label).toContain( + 'Otherwise, omit `--schema` to preserve the configured default' + ); } }); }); diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 981aa3a76b..7a169fbc42 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -76,7 +76,6 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { 'openspec-verify-change': '7cd65897d126f7c948620c0672ca62418620dbcb82ee73d890f758fb666a4ff8', 'openspec-onboard': '80f39cf33a138aac8e508db25d7af2c9e9bd482f90e414770e806f966dd58c9c', 'openspec-propose': '6f72fb88fd2287c4f3b0b0920e29b02fa249d118ac65480686fe2197e3ff8bcd', - 'openspec-update-change': '95bb533105e49aee06c9ea164b63092de77644cf8f94fa38d3ee3c11b0ccb893', }; // Intentionally excludes getFeedbackSkillTemplate: this list only models templates From 88162d6e3190503bc85f3662bb4a84b7066b64eb Mon Sep 17 00:00:00 2001 From: Clay Good Date: Tue, 4 Aug 2026 09:35:17 -0500 Subject: [PATCH 5/7] fix(propose): resolve schema discovery root --- skills/openspec-propose/SKILL.md | 2 +- src/core/templates/workflows/propose.ts | 4 ++-- test/core/templates/propose.test.ts | 18 +++++++++++++----- .../templates/skill-templates-parity.test.ts | 1 + 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/skills/openspec-propose/SKILL.md b/skills/openspec-propose/SKILL.md index 00b7bdbcfc..2e18ae22e2 100644 --- a/skills/openspec-propose/SKILL.md +++ b/skills/openspec-propose/SKILL.md @@ -46,7 +46,7 @@ When the user is ready to implement, they must start the apply workflow explicit **Use a different schema only if the user:** - Explicitly requests a specific schema by name → use `--schema ` - - Asks to "show workflows" or asks "what workflows" exist → identify the selected project or store root, then run `openspec schemas --json` with its working directory set to that root (the directory containing `openspec/`) and let them choose. For a registered store, use the store `root` returned by `openspec store list --json`; `schemas` does not accept `--store` + - Asks to "show workflows" or asks "what workflows" exist → resolve the authoritative root by running `openspec context --json` from the current working directory. If the user explicitly selected a registered store, use `openspec context --json --store ""`. Then run `openspec schemas --json` with its working directory set to the returned `root.path` and let them choose. This preserves roots selected by a local `store:` pointer or the global `defaultStore`; `schemas` does not accept `--store` Otherwise, omit `--schema` to preserve the configured default. diff --git a/src/core/templates/workflows/propose.ts b/src/core/templates/workflows/propose.ts index cff27f2c38..9f91f61a5a 100644 --- a/src/core/templates/workflows/propose.ts +++ b/src/core/templates/workflows/propose.ts @@ -48,7 +48,7 @@ ${STORE_SELECTION_GUIDANCE} **Use a different schema only if the user:** - Explicitly requests a specific schema by name → use \`--schema \` - - Asks to "show workflows" or asks "what workflows" exist → identify the selected project or store root, then run \`openspec schemas --json\` with its working directory set to that root (the directory containing \`openspec/\`) and let them choose. For a registered store, use the store \`root\` returned by \`openspec store list --json\`; \`schemas\` does not accept \`--store\` + - Asks to "show workflows" or asks "what workflows" exist → resolve the authoritative root by running \`openspec context --json\` from the current working directory. If the user explicitly selected a registered store, use \`openspec context --json --store ""\`. Then run \`openspec schemas --json\` with its working directory set to the returned \`root.path\` and let them choose. This preserves roots selected by a local \`store:\` pointer or the global \`defaultStore\`; \`schemas\` does not accept \`--store\` Otherwise, omit \`--schema\` to preserve the configured default. @@ -195,7 +195,7 @@ ${STORE_SELECTION_GUIDANCE} **Use a different schema only if the user:** - Explicitly requests a specific schema by name → use \`--schema \` - - Asks to "show workflows" or asks "what workflows" exist → identify the selected project or store root, then run \`openspec schemas --json\` with its working directory set to that root (the directory containing \`openspec/\`) and let them choose. For a registered store, use the store \`root\` returned by \`openspec store list --json\`; \`schemas\` does not accept \`--store\` + - Asks to "show workflows" or asks "what workflows" exist → resolve the authoritative root by running \`openspec context --json\` from the current working directory. If the user explicitly selected a registered store, use \`openspec context --json --store ""\`. Then run \`openspec schemas --json\` with its working directory set to the returned \`root.path\` and let them choose. This preserves roots selected by a local \`store:\` pointer or the global \`defaultStore\`; \`schemas\` does not accept \`--store\` Otherwise, omit \`--schema\` to preserve the configured default. diff --git a/test/core/templates/propose.test.ts b/test/core/templates/propose.test.ts index 521ae023dd..0f983ed2d2 100644 --- a/test/core/templates/propose.test.ts +++ b/test/core/templates/propose.test.ts @@ -178,7 +178,7 @@ describe('propose schema selection', () => { } }); - it('discovers schemas from the selected project or store root', () => { + it('discovers schemas from the authoritative project or store root', () => { for (const [label, body] of proposeBodies) { const schemaStep = body.indexOf('**Determine the workflow schema**'); const createStep = body.indexOf('**Create the change directory**'); @@ -186,14 +186,22 @@ describe('propose schema selection', () => { expect(schemaSection, label).toContain('Use the configured default schema'); expect(schemaSection, label).toContain('Explicitly requests a specific schema by name'); - expect(schemaSection, label).toContain('selected project or store root'); + const contextCommand = schemaSection.indexOf('`openspec context --json`'); + const schemasCommand = schemaSection.indexOf('`openspec schemas --json`'); + expect(contextCommand, `${label} is missing root resolution`).toBeGreaterThanOrEqual(0); + expect(schemasCommand, `${label} lists schemas before resolving the root`).toBeGreaterThan( + contextCommand + ); + expect(schemaSection, label).toContain('from the current working directory'); expect(schemaSection, label).toContain( - 'run `openspec schemas --json` with its working directory' + '`openspec context --json --store ""`' ); - expect(schemaSection, label).toContain('the directory containing `openspec/`'); expect(schemaSection, label).toContain( - 'use the store `root` returned by `openspec store list --json`' + 'run `openspec schemas --json` with its working directory' ); + expect(schemaSection, label).toContain('returned `root.path`'); + expect(schemaSection, label).toContain('local `store:` pointer'); + expect(schemaSection, label).toContain('global `defaultStore`'); expect(schemaSection, label).toContain('`schemas` does not accept `--store`'); expect(schemaSection, label).toContain( 'Otherwise, omit `--schema` to preserve the configured default' diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 7a169fbc42..981aa3a76b 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -76,6 +76,7 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { 'openspec-verify-change': '7cd65897d126f7c948620c0672ca62418620dbcb82ee73d890f758fb666a4ff8', 'openspec-onboard': '80f39cf33a138aac8e508db25d7af2c9e9bd482f90e414770e806f966dd58c9c', 'openspec-propose': '6f72fb88fd2287c4f3b0b0920e29b02fa249d118ac65480686fe2197e3ff8bcd', + 'openspec-update-change': '95bb533105e49aee06c9ea164b63092de77644cf8f94fa38d3ee3c11b0ccb893', }; // Intentionally excludes getFeedbackSkillTemplate: this list only models templates From 71562feacc4073d407de0f966e9704d026b76698 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Tue, 4 Aug 2026 09:51:05 -0500 Subject: [PATCH 6/7] fix(propose): preserve rootless schema discovery --- skills/openspec-propose/SKILL.md | 2 +- src/core/templates/workflows/propose.ts | 4 ++-- test/commands/context.test.ts | 2 ++ test/core/templates/propose.test.ts | 12 ++++++++++-- test/core/templates/skill-templates-parity.test.ts | 1 - 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/skills/openspec-propose/SKILL.md b/skills/openspec-propose/SKILL.md index 2e18ae22e2..29677ac457 100644 --- a/skills/openspec-propose/SKILL.md +++ b/skills/openspec-propose/SKILL.md @@ -46,7 +46,7 @@ When the user is ready to implement, they must start the apply workflow explicit **Use a different schema only if the user:** - Explicitly requests a specific schema by name → use `--schema ` - - Asks to "show workflows" or asks "what workflows" exist → resolve the authoritative root by running `openspec context --json` from the current working directory. If the user explicitly selected a registered store, use `openspec context --json --store ""`. Then run `openspec schemas --json` with its working directory set to the returned `root.path` and let them choose. This preserves roots selected by a local `store:` pointer or the global `defaultStore`; `schemas` does not accept `--store` + - Asks to "show workflows" or asks "what workflows" exist → resolve the authoritative root by running `openspec context --json` from the current working directory. If the user explicitly selected a registered store, use `openspec context --json --store ""`. Then run `openspec schemas --json` with its working directory set to the returned `root.path` and let them choose. This preserves roots selected by a local `store:` pointer or the global `defaultStore`; `schemas` does not accept `--store`. If context reports only `no_openspec_root`, run `openspec schemas --json` from the current working directory instead. Do not use this fallback for invalid or unavailable stores. Otherwise, omit `--schema` to preserve the configured default. diff --git a/src/core/templates/workflows/propose.ts b/src/core/templates/workflows/propose.ts index 9f91f61a5a..e82594906a 100644 --- a/src/core/templates/workflows/propose.ts +++ b/src/core/templates/workflows/propose.ts @@ -48,7 +48,7 @@ ${STORE_SELECTION_GUIDANCE} **Use a different schema only if the user:** - Explicitly requests a specific schema by name → use \`--schema \` - - Asks to "show workflows" or asks "what workflows" exist → resolve the authoritative root by running \`openspec context --json\` from the current working directory. If the user explicitly selected a registered store, use \`openspec context --json --store ""\`. Then run \`openspec schemas --json\` with its working directory set to the returned \`root.path\` and let them choose. This preserves roots selected by a local \`store:\` pointer or the global \`defaultStore\`; \`schemas\` does not accept \`--store\` + - Asks to "show workflows" or asks "what workflows" exist → resolve the authoritative root by running \`openspec context --json\` from the current working directory. If the user explicitly selected a registered store, use \`openspec context --json --store ""\`. Then run \`openspec schemas --json\` with its working directory set to the returned \`root.path\` and let them choose. This preserves roots selected by a local \`store:\` pointer or the global \`defaultStore\`; \`schemas\` does not accept \`--store\`. If context reports only \`no_openspec_root\`, run \`openspec schemas --json\` from the current working directory instead. Do not use this fallback for invalid or unavailable stores. Otherwise, omit \`--schema\` to preserve the configured default. @@ -195,7 +195,7 @@ ${STORE_SELECTION_GUIDANCE} **Use a different schema only if the user:** - Explicitly requests a specific schema by name → use \`--schema \` - - Asks to "show workflows" or asks "what workflows" exist → resolve the authoritative root by running \`openspec context --json\` from the current working directory. If the user explicitly selected a registered store, use \`openspec context --json --store ""\`. Then run \`openspec schemas --json\` with its working directory set to the returned \`root.path\` and let them choose. This preserves roots selected by a local \`store:\` pointer or the global \`defaultStore\`; \`schemas\` does not accept \`--store\` + - Asks to "show workflows" or asks "what workflows" exist → resolve the authoritative root by running \`openspec context --json\` from the current working directory. If the user explicitly selected a registered store, use \`openspec context --json --store ""\`. Then run \`openspec schemas --json\` with its working directory set to the returned \`root.path\` and let them choose. This preserves roots selected by a local \`store:\` pointer or the global \`defaultStore\`; \`schemas\` does not accept \`--store\`. If context reports only \`no_openspec_root\`, run \`openspec schemas --json\` from the current working directory instead. Do not use this fallback for invalid or unavailable stores. Otherwise, omit \`--schema\` to preserve the configured default. diff --git a/test/commands/context.test.ts b/test/commands/context.test.ts index ed1551fd13..ccb7064d58 100644 --- a/test/commands/context.test.ts +++ b/test/commands/context.test.ts @@ -103,6 +103,7 @@ describe('openspec context (4.1)', () => { fs.writeFileSync(path.join(pointerRepo, 'openspec', 'config.yaml'), 'store: team-context\n'); const declared = await runCLI(['context', '--json'], { cwd: pointerRepo, env }); expect(parseJson(declared).root.source).toBe('declared'); + expect(parseJson(declared).root.path).toBe(storeRoot); expect(parseJson(declared).members).toHaveLength(2); // Global-default session: no root, no pointer — provenance must name @@ -116,6 +117,7 @@ describe('openspec context (4.1)', () => { fs.mkdirSync(scratch, { recursive: true }); const fallback = await runCLI(['context', '--json'], { cwd: scratch, env }); expect(parseJson(fallback).root.source).toBe('global_default'); + expect(parseJson(fallback).root.path).toBe(storeRoot); expect(parseJson(fallback).root.store_id).toBe('team-context'); expect(parseJson(fallback).members).toHaveLength(2); }, CONTEXT_MATRIX_TIMEOUT_MS); diff --git a/test/core/templates/propose.test.ts b/test/core/templates/propose.test.ts index 0f983ed2d2..e88c8d7786 100644 --- a/test/core/templates/propose.test.ts +++ b/test/core/templates/propose.test.ts @@ -8,6 +8,7 @@ import { getFfChangeSkillTemplate, getOpsxFfCommandTemplate, } from '../../../src/core/templates/skill-templates.js'; +import { generateSkillContent } from '../../../src/core/shared/skill-generation.js'; import { loadSchema } from '../../../src/core/artifact-graph/schema.js'; import { CommandAdapterRegistry } from '../../../src/core/command-generation/registry.js'; import { generateCommand } from '../../../src/core/command-generation/generator.js'; @@ -20,8 +21,8 @@ import { getCommandContents } from '../../../src/core/shared/skill-generation.js const proposeSkillBody = getOpsxProposeSkillTemplate().instructions; const proposeCommandBody = getOpsxProposeCommandTemplate().content; const proposeBodies: Array<[string, string]> = [ - ['propose skill', proposeSkillBody], - ['propose command', proposeCommandBody], + ['propose skill', generateSkillContent(getOpsxProposeSkillTemplate(), 'TEST')], + ['propose command', getOpsxProposeCommandTemplate().content], ]; // ff runs the byte-identical artifact loop, so it carries the identical guards. @@ -203,6 +204,13 @@ describe('propose schema selection', () => { expect(schemaSection, label).toContain('local `store:` pointer'); expect(schemaSection, label).toContain('global `defaultStore`'); expect(schemaSection, label).toContain('`schemas` does not accept `--store`'); + expect(schemaSection, label).toContain('context reports only `no_openspec_root`'); + expect(schemaSection, label).toContain( + 'run `openspec schemas --json` from the current working directory instead' + ); + expect(schemaSection, label).toContain( + 'Do not use this fallback for invalid or unavailable stores' + ); expect(schemaSection, label).toContain( 'Otherwise, omit `--schema` to preserve the configured default' ); diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 981aa3a76b..7a169fbc42 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -76,7 +76,6 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { 'openspec-verify-change': '7cd65897d126f7c948620c0672ca62418620dbcb82ee73d890f758fb666a4ff8', 'openspec-onboard': '80f39cf33a138aac8e508db25d7af2c9e9bd482f90e414770e806f966dd58c9c', 'openspec-propose': '6f72fb88fd2287c4f3b0b0920e29b02fa249d118ac65480686fe2197e3ff8bcd', - 'openspec-update-change': '95bb533105e49aee06c9ea164b63092de77644cf8f94fa38d3ee3c11b0ccb893', }; // Intentionally excludes getFeedbackSkillTemplate: this list only models templates From fe3bfc2a3695dd27f03a5a2fa6ca60101199f7ed Mon Sep 17 00:00:00 2001 From: Clay Good Date: Tue, 4 Aug 2026 15:32:07 -0500 Subject: [PATCH 7/7] test(propose): align schema parity after rebase --- test/core/templates/skill-templates-parity.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 7a169fbc42..30b12eb662 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -57,8 +57,8 @@ const EXPECTED_FUNCTION_HASHES: Record = { getOpsxOnboardCommandTemplate: 'e04e4ab6c2f25122e6840212b4c22708812c36ceff9ec529c2bb1d1d035429e3', getOpsxBulkArchiveCommandTemplate: 'fbb4de58ed00861badd93cde9bdd3d7c52f966158a18a660152060076ea9723e', getOpsxVerifyCommandTemplate: 'ce0ee05b7a6b332e29db2298b9d5a928a1932caf516e35fd88f163154ffd43f4', - getOpsxProposeSkillTemplate: 'd1750192a80446d08df7e77e5498ed861ab2d903d1b0686291b038eb6bd61bfa', - getOpsxProposeCommandTemplate: 'aedb70a43558e69b1439a7698a3e42ec14712b889edf81341f6d403302ef4d4b', + getOpsxProposeSkillTemplate: '416200ae0277061405d17d5506243657ee26f7b883abe063844126c497d88f94', + getOpsxProposeCommandTemplate: '8de5ce5fe15c0b13ee1801b6b18cb86dc16ddea66c34223fafb4360232d8424d', getFeedbackSkillTemplate: 'd7d83c5f7fc2b92fe8f4588a5bf2d9cb315e4c73ec19bcd5ef28270906319a0d', getUpdateChangeSkillTemplate: 'f85fbfb3a175e949becbef08be0eccfab97de5e7ad45105e999d2900dfafbaba', getOpsxUpdateCommandTemplate: '461edf06e92c0da3dab4f11d91d59d44b48ed30a0881c1f34a714b1813435af6', @@ -75,7 +75,8 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { 'openspec-bulk-archive-change': 'c58e1d319a6587b52202434d5d769c94718aafc0f019276cef04cf8be473b6ce', 'openspec-verify-change': '7cd65897d126f7c948620c0672ca62418620dbcb82ee73d890f758fb666a4ff8', 'openspec-onboard': '80f39cf33a138aac8e508db25d7af2c9e9bd482f90e414770e806f966dd58c9c', - 'openspec-propose': '6f72fb88fd2287c4f3b0b0920e29b02fa249d118ac65480686fe2197e3ff8bcd', + 'openspec-propose': '48b06cf0fa53be06c84fc3e79729fb16b7b9d8549cbed6d89616eb6ba1f7e325', + 'openspec-update-change': '95bb533105e49aee06c9ea164b63092de77644cf8f94fa38d3ee3c11b0ccb893', }; // Intentionally excludes getFeedbackSkillTemplate: this list only models templates