From 960c3693b2761c4985961e99cb0f08d053d9a26d Mon Sep 17 00:00:00 2001 From: Clay Good Date: Tue, 11 Aug 2026 16:48:54 -0500 Subject: [PATCH 1/2] fix(config): label the update workflow in the picker and drop "expanded-profile" wording The config workflow picker builds each row's label from WORKFLOW_PROMPT_META in src/commands/config.ts. The table had entries for 11 of the 12 workflows but not `update`, so `openspec config` rendered that row as the raw id `update` with a `Workflow: update` placeholder description. Since `update` is one of the six core workflows, every user who opens the picker saw it. Add the missing `update` entry so the row reads "Update change / Revise the planning artifacts of an existing change". Also reword the update-change workflow template, which called `/opsx:continue` and `/opsx:new` "expanded-profile" workflows. There is no "expanded" profile; the only profile values the product stores are `core` and `custom`. They are now described as "optional" workflows. Regenerated the committed skills.sh mirror and parity hashes accordingly. Harden with a regression test asserting every ALL_WORKFLOWS id has real picker metadata (no raw-id name, no "Workflow:" placeholder), so a future workflow addition can't silently reintroduce the fallback. Closes #1627 Co-Authored-By: Claude Opus 4.8 --- skills/openspec-update-change/SKILL.md | 4 ++-- src/commands/config.ts | 6 +++++- src/core/templates/workflows/update-change.ts | 8 +++---- test/commands/config.test.ts | 21 +++++++++++++++++++ test/core/init.test.ts | 2 +- test/core/templates/update-change.test.ts | 4 ++-- 6 files changed, 35 insertions(+), 10 deletions(-) diff --git a/skills/openspec-update-change/SKILL.md b/skills/openspec-update-change/SKILL.md index 10ac7dd24c..24c9f88367 100644 --- a/skills/openspec-update-change/SKILL.md +++ b/skills/openspec-update-change/SKILL.md @@ -15,7 +15,7 @@ Revise a change's existing planning artifacts and keep them coherent. Never edit **Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. -`/openspec-continue-change` is an expanded-profile workflow and may not be installed. Before suggesting it anywhere below, verify that it is available. If it is unavailable, `openspec status --change "" --json` shows the next artifact and `openspec instructions "" --change "" --json` explains how to create it. +`/openspec-continue-change` is an optional workflow and may not be installed. Before suggesting it anywhere below, verify that it is available. If it is unavailable, `openspec status --change "" --json` shows the next artifact and `openspec instructions "" --change "" --json` explains how to create it. **Steps** @@ -87,4 +87,4 @@ After each invocation, show: - Edit only the concrete files in `existingOutputPaths`; never write to a glob `resolvedOutputPath`. - Do not advance the build frontier: no new artifacts, no new files under glob artifacts - that is `/openspec-continue-change`'s job. - Confirm every edit with the user before writing. -- If the request changes the change's *intent* rather than refining it, first verify whether the expanded-profile `/openspec-new-change` workflow is available. If it is, recommend starting fresh with `/openspec-new-change` (the "Update vs. Start Fresh" heuristic). If it is unavailable, ask for a distinct unused change name and recommend `openspec new change ""` instead. +- If the request changes the change's *intent* rather than refining it, first verify whether the optional `/openspec-new-change` workflow is available. If it is, recommend starting fresh with `/openspec-new-change` (the "Update vs. Start Fresh" heuristic). If it is unavailable, ask for a distinct unused change name and recommend `openspec new change ""` instead. diff --git a/src/commands/config.ts b/src/commands/config.ts index e594583e7f..4a3382b95b 100644 --- a/src/commands/config.ts +++ b/src/commands/config.ts @@ -44,7 +44,7 @@ interface WorkflowPromptMeta { description: string; } -const WORKFLOW_PROMPT_META: Record = { +export const WORKFLOW_PROMPT_META: Record = { propose: { name: 'Propose change', description: 'Create proposal, design, and tasks from a request', @@ -65,6 +65,10 @@ const WORKFLOW_PROMPT_META: Record = { name: 'Apply tasks', description: 'Implement tasks from the current change', }, + update: { + name: 'Update change', + description: 'Revise the planning artifacts of an existing change', + }, ff: { name: 'Fast-forward', description: 'Run a faster implementation workflow', diff --git a/src/core/templates/workflows/update-change.ts b/src/core/templates/workflows/update-change.ts index a716ec47b1..7700cd8d7e 100644 --- a/src/core/templates/workflows/update-change.ts +++ b/src/core/templates/workflows/update-change.ts @@ -17,7 +17,7 @@ ${STORE_SELECTION_GUIDANCE} **Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. -\`/opsx:continue\` is an expanded-profile workflow and may not be installed. Before suggesting it anywhere below, verify that it is available. If it is unavailable, \`openspec status --change "" --json\` shows the next artifact and \`openspec instructions "" --change "" --json\` explains how to create it. +\`/opsx:continue\` is an optional workflow and may not be installed. Before suggesting it anywhere below, verify that it is available. If it is unavailable, \`openspec status --change "" --json\` shows the next artifact and \`openspec instructions "" --change "" --json\` explains how to create it. **Steps** @@ -89,7 +89,7 @@ After each invocation, show: - Edit only the concrete files in \`existingOutputPaths\`; never write to a glob \`resolvedOutputPath\`. - Do not advance the build frontier: no new artifacts, no new files under glob artifacts - that is \`/opsx:continue\`'s job. - Confirm every edit with the user before writing. -- If the request changes the change's *intent* rather than refining it, first verify whether the expanded-profile \`/opsx:new\` workflow is available. If it is, recommend starting fresh with \`/opsx:new\` (the "Update vs. Start Fresh" heuristic). If it is unavailable, ask for a distinct unused change name and recommend \`openspec new change ""\` instead.`, +- If the request changes the change's *intent* rather than refining it, first verify whether the optional \`/opsx:new\` workflow is available. If it is, recommend starting fresh with \`/opsx:new\` (the "Update vs. Start Fresh" heuristic). If it is unavailable, ask for a distinct unused change name and recommend \`openspec new change ""\` instead.`, license: 'MIT', compatibility: 'Requires openspec CLI.', metadata: { author: 'openspec', version: '1.0' }, @@ -108,7 +108,7 @@ ${STORE_SELECTION_GUIDANCE} **Input**: Optionally specify a change name after \`/opsx:update\` (e.g., \`/opsx:update add-auth\`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. -\`/opsx:continue\` is an expanded-profile workflow and may not be installed. Before suggesting it anywhere below, verify that it is available. If it is unavailable, \`openspec status --change "" --json\` shows the next artifact and \`openspec instructions "" --change "" --json\` explains how to create it. +\`/opsx:continue\` is an optional workflow and may not be installed. Before suggesting it anywhere below, verify that it is available. If it is unavailable, \`openspec status --change "" --json\` shows the next artifact and \`openspec instructions "" --change "" --json\` explains how to create it. **Steps** @@ -180,6 +180,6 @@ After each invocation, show: - Edit only the concrete files in \`existingOutputPaths\`; never write to a glob \`resolvedOutputPath\`. - Do not advance the build frontier: no new artifacts, no new files under glob artifacts - that is \`/opsx:continue\`'s job. - Confirm every edit with the user before writing. -- If the request changes the change's *intent* rather than refining it, first verify whether the expanded-profile \`/opsx:new\` workflow is available. If it is, recommend starting fresh with \`/opsx:new\` (the "Update vs. Start Fresh" heuristic). If it is unavailable, ask for a distinct unused change name and recommend \`openspec new change ""\` instead.` +- If the request changes the change's *intent* rather than refining it, first verify whether the optional \`/opsx:new\` workflow is available. If it is, recommend starting fresh with \`/opsx:new\` (the "Update vs. Start Fresh" heuristic). If it is unavailable, ask for a distinct unused change name and recommend \`openspec new change ""\` instead.` }; } diff --git a/test/commands/config.test.ts b/test/commands/config.test.ts index 92096d266e..d7dd733359 100644 --- a/test/commands/config.test.ts +++ b/test/commands/config.test.ts @@ -395,3 +395,24 @@ describe('config profile command', () => { expect(result.success).toBe(false); }); }); + +describe('workflow picker labels', () => { + it('gives every workflow a friendly label instead of the raw-id fallback', async () => { + const { WORKFLOW_PROMPT_META } = await import('../../src/commands/config.js'); + const { ALL_WORKFLOWS } = await import('../../src/core/profiles.js'); + + for (const workflow of ALL_WORKFLOWS) { + const meta = WORKFLOW_PROMPT_META[workflow]; + // A missing entry is exactly what made `update` render as its raw id + // with a `Workflow: update` placeholder in the config picker (#1627). + expect(meta, `missing picker metadata for "${workflow}"`).toBeDefined(); + expect(meta!.name, `label for "${workflow}" must not be the raw id`).not.toBe(workflow); + expect(meta!.name.length, `label for "${workflow}" must be non-empty`).toBeGreaterThan(0); + expect( + meta!.description.startsWith('Workflow:'), + `description for "${workflow}" must not be the placeholder` + ).toBe(false); + expect(meta!.description.length, `description for "${workflow}" must be non-empty`).toBeGreaterThan(0); + } + }); +}); diff --git a/test/core/init.test.ts b/test/core/init.test.ts index c73d1e7d74..42a60a79f6 100644 --- a/test/core/init.test.ts +++ b/test/core/init.test.ts @@ -302,7 +302,7 @@ describe('InitCommand', () => { for (const [content, continueReference] of updateVariants) { const availabilityGuidance = content.indexOf( - `${continueReference} is an expanded-profile workflow and may not be installed` + `${continueReference} is an optional workflow and may not be installed` ); const nextReference = content.indexOf( continueReference, diff --git a/test/core/templates/update-change.test.ts b/test/core/templates/update-change.test.ts index 94f52736bb..cf68f234f5 100644 --- a/test/core/templates/update-change.test.ts +++ b/test/core/templates/update-change.test.ts @@ -80,7 +80,7 @@ describe('update-change templates', () => { it('explains the optional continue workflow before suggesting it', () => { for (const [label, body] of bodies) { const availabilityGuidance = body.indexOf( - '`/opsx:continue` is an expanded-profile workflow and may not be installed' + '`/opsx:continue` is an optional workflow and may not be installed' ); const firstSuggestion = body.indexOf( '`/opsx:continue`', @@ -110,7 +110,7 @@ describe('update-change templates', () => { expect(body, label).not.toContain('openspec new change ""'); const newAvailabilityCheck = body.indexOf( - 'first verify whether the expanded-profile `/opsx:new` workflow is available' + 'first verify whether the optional `/opsx:new` workflow is available' ); const newRecommendation = body.indexOf('recommend starting fresh with `/opsx:new`'); expect(newAvailabilityCheck, label).toBeGreaterThanOrEqual(0); From 275f2d5660362ae905603cb0207901df71c23074 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Tue, 11 Aug 2026 17:23:36 -0500 Subject: [PATCH 2/2] chore: regenerate skills and parity hashes after rebase onto main Co-Authored-By: Claude Opus 4.8 --- 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 05c8db1345..decad7b0b5 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -61,8 +61,8 @@ const EXPECTED_FUNCTION_HASHES: Record = { getOpsxProposeSkillTemplate: '24623c066f97e34b957d448d1f9a9e8b8a13da3dfce45d45671f6226a2534848', getOpsxProposeCommandTemplate: 'e67ba591efb0fecacb2229d06dfa84af18b825fab8a7b01377279e4f09a06ce4', getFeedbackSkillTemplate: 'd7d83c5f7fc2b92fe8f4588a5bf2d9cb315e4c73ec19bcd5ef28270906319a0d', - getUpdateChangeSkillTemplate: '7aa7351aca25fe2d8d29df4339ca2c768e32c6a59596316bb451831447fa15fc', - getOpsxUpdateCommandTemplate: 'fdc8ba0502910f74e85044e450906f189e939ffd70d5e85b7307a6ec33805148', + getUpdateChangeSkillTemplate: '7dc8abc6f64c58bf34d7581ed4ab095a3b7a53cb372349bee2d840db58622819', + getOpsxUpdateCommandTemplate: 'e2388521b22f92f74561df9a0c2f98e1fa4d265af93b5ba26f42fb47a6c5bfed', }; const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { @@ -77,7 +77,7 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { 'openspec-verify-change': 'af9be013dcbe8c6d8f6d9ab10c893fbd03f4c62933c384d82f63894dd0ceb84f', 'openspec-onboard': 'd53403b4910ab64307862ccf97e70bd8f7174ee44508088fb239c880f0939331', 'openspec-propose': '25d08ed4f031770cea219604167d76bca9f3e89fe0c2f545263674482c6f13f0', - 'openspec-update-change': '5ee000a8bf5507a553fbb9ed666625d11986f9c0bf14edb495648554a4d1c53c', + 'openspec-update-change': '586547406aca94422dfeb3ffedce6c01049429b743f57ce829baa79ebc714d51', }; // Intentionally excludes getFeedbackSkillTemplate: this list only models templates