diff --git a/.changeset/core-profile-self-contained.md b/.changeset/core-profile-self-contained.md new file mode 100644 index 0000000000..00135b2707 --- /dev/null +++ b/.changeset/core-profile-self-contained.md @@ -0,0 +1,5 @@ +--- +"@fission-ai/openspec": patch +--- + +Keep core-profile skills self-contained by avoiding dead `new` and `continue` skill invocations and using the existing CLI fallback when those optional workflows are not installed. diff --git a/skills/openspec-apply-change/SKILL.md b/skills/openspec-apply-change/SKILL.md index 098f63fecb..f35e4de270 100644 --- a/skills/openspec-apply-change/SKILL.md +++ b/skills/openspec-apply-change/SKILL.md @@ -50,7 +50,7 @@ Implement tasks from an OpenSpec change. - Optional `operationGuidance`: current advisory guidance for apply **Handle states:** - - If `state: "blocked"` (missing artifacts): show message, suggest using `/openspec-continue-change` (if it is not installed, run `openspec status --change "" --json` to see the next artifact and `openspec instructions --change "" --json` for how to create it) + - If `state: "blocked"` (missing artifacts): show message, suggest using the continue workflow (if it is not installed, run `openspec status --change "" --json` to see the next artifact and `openspec instructions --change "" --json` for how to create it) - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation diff --git a/skills/openspec-update-change/SKILL.md b/skills/openspec-update-change/SKILL.md index 24c9f88367..027a3b0341 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 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. +the continue workflow 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** @@ -58,7 +58,7 @@ Revise a change's existing planning artifacts and keep them coherent. Never edit - Read the artifact(s) the request touches and the change's other existing artifacts. - Apply the requested edit. Then check every other existing artifact against it - in ANY direction: an edit to a later artifact may require revising an earlier one, not only the other way around. Build order is a useful reading order, not a constraint on which artifacts may be revised. - Note everything that is now inconsistent, missing, or contradictory. - - Revise only files that already exist (`existingOutputPaths`). Do NOT create artifacts that don't exist yet, and do NOT invent new files under a glob artifact - note them and point the user to `/openspec-continue-change` to create them. + - Revise only files that already exist (`existingOutputPaths`). Do NOT create artifacts that don't exist yet, and do NOT invent new files under a glob artifact - note them and point the user to the continue workflow to create them. - If the change is already coherent, say so and make no edits. 5. **Confirm and apply, one artifact at a time** @@ -70,7 +70,7 @@ Revise a change's existing planning artifacts and keep them coherent. Never edit ``` 6. **Point to the next step (guidance only - NEVER act on it)** - - Artifacts still missing -> suggest `/openspec-continue-change` to create them. + - Artifacts still missing -> suggest the continue workflow to create them. - Change already implemented (tasks checked off / already applied) -> the code may no longer match the revised plan; suggest `/openspec-apply-change` to carry the delta into code. - Everything done and implemented -> suggest `/openspec-archive-change`. @@ -78,13 +78,13 @@ Revise a change's existing planning artifacts and keep them coherent. Never edit After each invocation, show: - Which artifacts were revised (and which proposed revisions were rejected) -- Anything deferred to `/openspec-continue-change` (not-yet-created artifacts or files) +- Anything deferred to the continue workflow (not-yet-created artifacts or files) - Where the change stands and the recommended next command **Guardrails** - Planning artifacts only - NEVER edit implementation code. If the revised plan implies code changes, stop and point to `/openspec-apply-change`. - Use the artifact ids and paths reported by `openspec status`; never branch on hardcoded artifact names. - 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. +- Do not advance the build frontier: no new artifacts, no new files under glob artifacts - that is the continue workflow'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 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. +- If the request changes the change's *intent* rather than refining it, first verify whether the optional new workflow is available. If it is, recommend starting fresh with that workflow (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/core/templates/workflows/apply-change.ts b/src/core/templates/workflows/apply-change.ts index d7ebe2f4eb..cbfaacbab3 100644 --- a/src/core/templates/workflows/apply-change.ts +++ b/src/core/templates/workflows/apply-change.ts @@ -58,7 +58,7 @@ ${STORE_SELECTION_GUIDANCE} - Optional \`operationGuidance\`: current advisory guidance for apply **Handle states:** - - If \`state: "blocked"\` (missing artifacts): show message, suggest using \`/opsx:continue\` (if it is not installed, run \`openspec status --change "" --json\` to see the next artifact and \`openspec instructions --change "" --json\` for how to create it) + - If \`state: "blocked"\` (missing artifacts): show message, suggest using the continue workflow (if it is not installed, run \`openspec status --change "" --json\` to see the next artifact and \`openspec instructions --change "" --json\` for how to create it) - If \`state: "all_done"\`: congratulate, suggest archive - Otherwise: proceed to implementation diff --git a/src/core/templates/workflows/update-change.ts b/src/core/templates/workflows/update-change.ts index 7700cd8d7e..6012692ddc 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 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. +the continue workflow 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** @@ -60,7 +60,7 @@ ${STORE_SELECTION_GUIDANCE} - Read the artifact(s) the request touches and the change's other existing artifacts. - Apply the requested edit. Then check every other existing artifact against it - in ANY direction: an edit to a later artifact may require revising an earlier one, not only the other way around. Build order is a useful reading order, not a constraint on which artifacts may be revised. - Note everything that is now inconsistent, missing, or contradictory. - - Revise only files that already exist (\`existingOutputPaths\`). Do NOT create artifacts that don't exist yet, and do NOT invent new files under a glob artifact - note them and point the user to \`/opsx:continue\` to create them. + - Revise only files that already exist (\`existingOutputPaths\`). Do NOT create artifacts that don't exist yet, and do NOT invent new files under a glob artifact - note them and point the user to the continue workflow to create them. - If the change is already coherent, say so and make no edits. 5. **Confirm and apply, one artifact at a time** @@ -72,7 +72,7 @@ ${STORE_SELECTION_GUIDANCE} \`\`\` 6. **Point to the next step (guidance only - NEVER act on it)** - - Artifacts still missing -> suggest \`/opsx:continue\` to create them. + - Artifacts still missing -> suggest the continue workflow to create them. - Change already implemented (tasks checked off / already applied) -> the code may no longer match the revised plan; suggest \`/opsx:apply\` to carry the delta into code. - Everything done and implemented -> suggest \`/opsx:archive\`. @@ -80,16 +80,16 @@ ${STORE_SELECTION_GUIDANCE} After each invocation, show: - Which artifacts were revised (and which proposed revisions were rejected) -- Anything deferred to \`/opsx:continue\` (not-yet-created artifacts or files) +- Anything deferred to the continue workflow (not-yet-created artifacts or files) - Where the change stands and the recommended next command **Guardrails** - Planning artifacts only - NEVER edit implementation code. If the revised plan implies code changes, stop and point to \`/opsx:apply\`. - Use the artifact ids and paths reported by \`openspec status\`; never branch on hardcoded artifact names. - 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. +- Do not advance the build frontier: no new artifacts, no new files under glob artifacts - that is the continue workflow'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 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.`, +- If the request changes the change's *intent* rather than refining it, first verify whether the optional new workflow is available. If it is, recommend starting fresh with that workflow (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 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. +the continue workflow 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** @@ -151,7 +151,7 @@ ${STORE_SELECTION_GUIDANCE} - Read the artifact(s) the request touches and the change's other existing artifacts. - Apply the requested edit. Then check every other existing artifact against it - in ANY direction: an edit to a later artifact may require revising an earlier one, not only the other way around. Build order is a useful reading order, not a constraint on which artifacts may be revised. - Note everything that is now inconsistent, missing, or contradictory. - - Revise only files that already exist (\`existingOutputPaths\`). Do NOT create artifacts that don't exist yet, and do NOT invent new files under a glob artifact - note them and point the user to \`/opsx:continue\` to create them. + - Revise only files that already exist (\`existingOutputPaths\`). Do NOT create artifacts that don't exist yet, and do NOT invent new files under a glob artifact - note them and point the user to the continue workflow to create them. - If the change is already coherent, say so and make no edits. 5. **Confirm and apply, one artifact at a time** @@ -163,7 +163,7 @@ ${STORE_SELECTION_GUIDANCE} \`\`\` 6. **Point to the next step (guidance only - NEVER act on it)** - - Artifacts still missing -> suggest \`/opsx:continue\` to create them. + - Artifacts still missing -> suggest the continue workflow to create them. - Change already implemented (tasks checked off / already applied) -> the code may no longer match the revised plan; suggest \`/opsx:apply\` to carry the delta into code. - Everything done and implemented -> suggest \`/opsx:archive\`. @@ -171,15 +171,15 @@ ${STORE_SELECTION_GUIDANCE} After each invocation, show: - Which artifacts were revised (and which proposed revisions were rejected) -- Anything deferred to \`/opsx:continue\` (not-yet-created artifacts or files) +- Anything deferred to the continue workflow (not-yet-created artifacts or files) - Where the change stands and the recommended next command **Guardrails** - Planning artifacts only - NEVER edit implementation code. If the revised plan implies code changes, stop and point to \`/opsx:apply\`. - Use the artifact ids and paths reported by \`openspec status\`; never branch on hardcoded artifact names. - 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. +- Do not advance the build frontier: no new artifacts, no new files under glob artifacts - that is the continue workflow'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 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.` +- If the request changes the change's *intent* rather than refining it, first verify whether the optional new workflow is available. If it is, recommend starting fresh with that workflow (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/core/templates/core-profile-self-sufficiency.test.ts b/test/core/templates/core-profile-self-sufficiency.test.ts new file mode 100644 index 0000000000..f04e2d2d3a --- /dev/null +++ b/test/core/templates/core-profile-self-sufficiency.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, it } from 'vitest'; +import { CORE_WORKFLOWS } from '../../../src/core/profiles.js'; +import { getSkillTemplates } from '../../../src/core/shared/index.js'; +import { transformToSkillReferences } from '../../../src/utils/command-references.js'; + +describe('core profile skill self-sufficiency', () => { + it('does not advertise new or continue skills that the core profile does not install', () => { + const rendered = getSkillTemplates(CORE_WORKFLOWS) + .map(({ template }) => transformToSkillReferences(template.instructions)) + .join('\n'); + + expect(rendered).not.toContain('/openspec-new-change'); + expect(rendered).not.toContain('/openspec-continue-change'); + }); +}); diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index c7a8b44b5c..dc86062724 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -41,14 +41,14 @@ const EXPECTED_FUNCTION_HASHES: Record = { getExploreSkillTemplate: 'ecaa0bea4c1cd14eee9dbfcfe4b5808fff4ff808cba0a46789b37c1df3048d9a', getNewChangeSkillTemplate: 'eabd1e895c5881dcb17dcbaa3fb26098dd59e8eacb318e400820b4dc811ef781', getContinueChangeSkillTemplate: '012136f6411a99c8fa228e2f9444cb64b0a89e0f56fdeac2fe03b2f5bee0c5d7', - getApplyChangeSkillTemplate: 'd1e7d5ceb85193c0964057dbb88e9651526754bd33f84020e2440ff0621d5dbb', + getApplyChangeSkillTemplate: '7feb89244b43cd58361f8d2d4446d41748740244729c62c09f36600513da0d72', getFfChangeSkillTemplate: '5501740e7ec36ab23ab8c3a0d6dd0655a5e2f35433c7b90e82904fef5e7a326a', getSyncSpecsSkillTemplate: 'b099e2ff31859c9b10d928066e662524f9aad9ecf2be12fceacb732d718c4146', getOnboardSkillTemplate: '3a836faae463d88c289a1c129cb7ee556a563b7e53e1a52a4711ff152a3b51f7', getOpsxExploreCommandTemplate: '1460fcb4fbdf22244e9e76608102e611db598cd4cca8c5dbd001292854bcba6e', getOpsxNewCommandTemplate: 'f2d30e569798a4c92ba932859d6ba4e0ad10e18feccbade1cfee0957597b3463', getOpsxContinueCommandTemplate: 'e50e50266efa1b8e64ff9b6274ee8254f0a240d6adc1b862d126e2f1c9d3a559', - getOpsxApplyCommandTemplate: 'e3579ac78f2e2c75fa3d3a7ac7dc3e49c395e96f7323398f0f041d94f8de9bb0', + getOpsxApplyCommandTemplate: 'b9ee71ed1bb0b77966fcc3a22d91b150ec68b7ed57623cd6a4f19f484a648805', getOpsxFfCommandTemplate: 'e603bc0996604e6c17a3140943ea642a32d0fc65565e25424bf956e124c55772', getArchiveChangeSkillTemplate: '56bfada1a5f35a127791b70de9d428a75b5aedd1584d6c9803a1ecb1fd1b4a23', getBulkArchiveChangeSkillTemplate: '93875998cade5322d95b43299fba794bc1da754e917dd63a770406386a6d295d', @@ -61,15 +61,15 @@ const EXPECTED_FUNCTION_HASHES: Record = { getOpsxProposeSkillTemplate: '24623c066f97e34b957d448d1f9a9e8b8a13da3dfce45d45671f6226a2534848', getOpsxProposeCommandTemplate: 'e67ba591efb0fecacb2229d06dfa84af18b825fab8a7b01377279e4f09a06ce4', getFeedbackSkillTemplate: 'dabeb5e825b9349abc8156c3e7b8608f27987912a6d9bf47ef29addde6138133', - getUpdateChangeSkillTemplate: '7dc8abc6f64c58bf34d7581ed4ab095a3b7a53cb372349bee2d840db58622819', - getOpsxUpdateCommandTemplate: 'e2388521b22f92f74561df9a0c2f98e1fa4d265af93b5ba26f42fb47a6c5bfed', + getUpdateChangeSkillTemplate: '06209ab45b7fe3c130c0c2b9c6a4ac4ee5449ef207d4625970a0ba302993314c', + getOpsxUpdateCommandTemplate: 'edc9d8e8f6cdf1ff44df1ca939ae3a93d0ee603e1066602131cd58bbed719d96', }; const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { 'openspec-explore': '886680e71f2900378bd12bb9ff25c888a41a8f851e0bb3ec056affcc18d07ca8', 'openspec-new-change': 'ec4529beef978e34634a6f7286fab55d68fad8fb374dceb45691d52caab33fbb', 'openspec-continue-change': 'bb6194a16c54891cdb253678e8f70ce53b2af86735243980f366ce551d37e42e', - 'openspec-apply-change': '81ea96d9fa6ec8536cd23c1fe561ed28e1cc1cad0a8ceb700588e08974cc0e49', + 'openspec-apply-change': 'ecd12411d70cb805a058fbd6d3a2f4afae989c55eec9f1b16c50f5ebcb02d524', 'openspec-ff-change': '217c78da2b6e8358f609ac57dcd02266aaec3354ce26dc6ec2fc9c2174673ab4', 'openspec-sync-specs': 'd933d8856584d6c1253de91e652e7aee9e85c77ad4d3531f6476f79d84e6e5e8', 'openspec-archive-change': '7c65053d674ba4e1e20e2bf73ba7e5a7f94baef2eaa9b33cee48d4cadea51b7a', @@ -77,7 +77,7 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { 'openspec-verify-change': 'af9be013dcbe8c6d8f6d9ab10c893fbd03f4c62933c384d82f63894dd0ceb84f', 'openspec-onboard': 'f6f59476acaf5e4d65dbb180da4cef62432612f3cecf207d471a951295e2003a', 'openspec-propose': '25d08ed4f031770cea219604167d76bca9f3e89fe0c2f545263674482c6f13f0', - 'openspec-update-change': '586547406aca94422dfeb3ffedce6c01049429b743f57ce829baa79ebc714d51', + 'openspec-update-change': 'da8d4c2af9252b91b3c8a3b1723513b0eb89c5d350b0b99aeb8597e345337a09', }; // Intentionally excludes getFeedbackSkillTemplate: this list only models templates diff --git a/test/utils/command-references.test.ts b/test/utils/command-references.test.ts index d5886f2dfe..9e8105845b 100644 --- a/test/utils/command-references.test.ts +++ b/test/utils/command-references.test.ts @@ -333,35 +333,32 @@ describe('getTransformerForTool', () => { }); }); -// Regression for #1153/#1514: the apply skill template must author its -// continue/apply/archive references as canonical /opsx:* tokens so the -// generator can rewrite them per target. Bare "openspec-continue-change" -// prose is invisible to the transformers, which left skills.sh, Codex, and -// Kimi with dead text and no archive/input invocation after a naive revert. +// Regression for #1153/#1514/#1734: apply/archive references stay +// transformable for every target, while optional workflows that the core +// profile does not install must not be advertised as invocable commands. describe('apply skill template generates valid per-target invocations', () => { const skill = getApplyChangeSkillTemplate().instructions; - it('authors invocation references as transformable /opsx:* tokens', () => { + it('authors installed workflow references as transformable /opsx:* tokens', () => { expect(skill).toContain('/opsx:apply add-auth'); - expect(skill).toContain('suggest using `/opsx:continue`'); + expect(skill).toContain('the continue workflow'); + expect(skill).not.toContain('/opsx:continue'); expect(skill).toContain('archive this change with `/opsx:archive`'); - // No bare, non-transformable skill-name prose remains. - expect(skill).not.toContain('suggest using openspec-continue-change'); }); const cases = [ - { tool: 'default (skills.sh)', transform: transformToSkillReferences, cont: '/openspec-continue-change', arch: '/openspec-archive-change', apply: '/openspec-apply-change' }, - { tool: 'codex', transform: getSkillReferenceTransformer('codex'), cont: '$openspec-continue-change', arch: '$openspec-archive-change', apply: '$openspec-apply-change' }, - { tool: 'kimi', transform: getSkillReferenceTransformer('kimi'), cont: '/skill:openspec-continue-change', arch: '/skill:openspec-archive-change', apply: '/skill:openspec-apply-change' }, + { tool: 'default (skills.sh)', transform: transformToSkillReferences, arch: '/openspec-archive-change', apply: '/openspec-apply-change' }, + { tool: 'codex', transform: getSkillReferenceTransformer('codex'), arch: '$openspec-archive-change', apply: '$openspec-apply-change' }, + { tool: 'kimi', transform: getSkillReferenceTransformer('kimi'), arch: '/skill:openspec-archive-change', apply: '/skill:openspec-apply-change' }, ]; - for (const { tool, transform, cont, arch, apply } of cases) { - it(`emits ${tool} skill invocations for continue, apply, and archive`, () => { + for (const { tool, transform, arch, apply } of cases) { + it(`emits ${tool} skill invocations for installed apply and archive workflows`, () => { const out = transform(skill); - expect(out).toContain(cont); expect(out).toContain(arch); expect(out).toContain(`${apply} add-auth`); - // No canonical token survives the rewrite. + expect(out).not.toContain('openspec-continue-change'); + // No canonical token survives the rewrite for installed invocations. expect(out).not.toMatch(/\/opsx:(continue|apply|archive)/); }); }