fix(config): label the update workflow in the picker; drop "expanded-profile" wording (#1632)

* 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 <noreply@anthropic.com>

* chore: regenerate skills and parity hashes after rebase onto main

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Clay Good
2026-08-11 22:41:44 +00:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 4b114aade9
commit 207f3cc515
7 changed files with 38 additions and 13 deletions
+2 -2
View File
@@ -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 "<name>" --json` shows the next artifact and `openspec instructions "<artifact-id>" --change "<name>" --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 "<name>" --json` shows the next artifact and `openspec instructions "<artifact-id>" --change "<name>" --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 "<new-change-name>"` 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 "<new-change-name>"` instead.
+5 -1
View File
@@ -44,7 +44,7 @@ interface WorkflowPromptMeta {
description: string;
}
const WORKFLOW_PROMPT_META: Record<string, WorkflowPromptMeta> = {
export const WORKFLOW_PROMPT_META: Record<string, WorkflowPromptMeta> = {
propose: {
name: 'Propose change',
description: 'Create proposal, design, and tasks from a request',
@@ -65,6 +65,10 @@ const WORKFLOW_PROMPT_META: Record<string, WorkflowPromptMeta> = {
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',
@@ -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 "<name>" --json\` shows the next artifact and \`openspec instructions "<artifact-id>" --change "<name>" --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 "<name>" --json\` shows the next artifact and \`openspec instructions "<artifact-id>" --change "<name>" --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 "<new-change-name>"\` 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 "<new-change-name>"\` 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 "<name>" --json\` shows the next artifact and \`openspec instructions "<artifact-id>" --change "<name>" --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 "<name>" --json\` shows the next artifact and \`openspec instructions "<artifact-id>" --change "<name>" --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 "<new-change-name>"\` 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 "<new-change-name>"\` instead.`
};
}
+21
View File
@@ -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);
}
});
});
+1 -1
View File
@@ -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,
@@ -61,8 +61,8 @@ const EXPECTED_FUNCTION_HASHES: Record<string, string> = {
getOpsxProposeSkillTemplate: '24623c066f97e34b957d448d1f9a9e8b8a13da3dfce45d45671f6226a2534848',
getOpsxProposeCommandTemplate: 'e67ba591efb0fecacb2229d06dfa84af18b825fab8a7b01377279e4f09a06ce4',
getFeedbackSkillTemplate: 'd7d83c5f7fc2b92fe8f4588a5bf2d9cb315e4c73ec19bcd5ef28270906319a0d',
getUpdateChangeSkillTemplate: '7aa7351aca25fe2d8d29df4339ca2c768e32c6a59596316bb451831447fa15fc',
getOpsxUpdateCommandTemplate: 'fdc8ba0502910f74e85044e450906f189e939ffd70d5e85b7307a6ec33805148',
getUpdateChangeSkillTemplate: '7dc8abc6f64c58bf34d7581ed4ab095a3b7a53cb372349bee2d840db58622819',
getOpsxUpdateCommandTemplate: 'e2388521b22f92f74561df9a0c2f98e1fa4d265af93b5ba26f42fb47a6c5bfed',
};
const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record<string, string> = {
@@ -77,7 +77,7 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record<string, string> = {
'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
+2 -2
View File
@@ -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 "<name>"');
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);