From 40c811b980634a582a284930837ee76552d359b9 Mon Sep 17 00:00:00 2001 From: Alex <45095641+runsonmypc@users.noreply.github.com> Date: Thu, 27 Aug 2026 20:04:26 -0400 Subject: [PATCH 1/9] fix(workflows): resolve the change picker's schema label from status The update and continue templates tell the agent to read a `schema` field from `openspec list --json` and fall back to "spec-driven" when it is absent. `list --json` returns only `name`, `completedTasks`, `totalTasks`, `lastModified`, and `status` (docs/agent-contract.md 4.1), so the field is never present and the fallback fires every time: a change on a custom schema is shown to the user as `spec-driven`. Make the schema line optional and, when shown, resolve it from `openspec status --change "" --json` (`schemaName`). --- .changeset/schema-label-from-status.md | 5 +++++ skills/openspec-continue-change/SKILL.md | 4 +++- skills/openspec-update-change/SKILL.md | 4 +++- src/core/templates/workflows/continue-change.ts | 8 ++++++-- src/core/templates/workflows/update-change.ts | 8 ++++++-- test/core/templates/skill-templates-parity.test.ts | 12 ++++++------ 6 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 .changeset/schema-label-from-status.md diff --git a/.changeset/schema-label-from-status.md b/.changeset/schema-label-from-status.md new file mode 100644 index 0000000000..04aa74d18c --- /dev/null +++ b/.changeset/schema-label-from-status.md @@ -0,0 +1,5 @@ +--- +"@fission-ai/openspec": patch +--- + +Stop the `/opsx:update` and `/opsx:continue` change pickers from labelling every change `spec-driven`. Both templates read a `schema` field from `openspec list --json`, which that command does not emit, so the fallback fired unconditionally and mislabelled custom-schema changes. The schema line is now optional and, when shown, resolved from `openspec status --change "" --json` (`schemaName`). diff --git a/skills/openspec-continue-change/SKILL.md b/skills/openspec-continue-change/SKILL.md index 5991b06891..da997a9680 100644 --- a/skills/openspec-continue-change/SKILL.md +++ b/skills/openspec-continue-change/SKILL.md @@ -26,7 +26,9 @@ Continue working on a change by creating the next artifact. When prompting, present the top 3-4 most recently modified changes as options, showing: - Change name - - Schema (from `schema` field if present, otherwise "spec-driven") + - Schema (optional) - `openspec list --json` does not report it. Omit the line, or resolve + each candidate with `openspec status --change "" --json` and show its `schemaName`. + Never fall back to "spec-driven": that mislabels every custom-schema change. - Status (e.g., "0/5 tasks", "complete", "no tasks") - How recently it was modified (from `lastModified` field) diff --git a/skills/openspec-update-change/SKILL.md b/skills/openspec-update-change/SKILL.md index 24c9f88367..f9a3ec9cb2 100644 --- a/skills/openspec-update-change/SKILL.md +++ b/skills/openspec-update-change/SKILL.md @@ -28,7 +28,9 @@ Revise a change's existing planning artifacts and keep them coherent. Never edit When prompting, present the top 3-4 most recently modified changes as options, showing: - Change name - - Schema (from `schema` field if present, otherwise "spec-driven") + - Schema (optional) - `openspec list --json` does not report it. Omit the line, or resolve + each candidate with `openspec status --change "" --json` and show its `schemaName`. + Never fall back to "spec-driven": that mislabels every custom-schema change. - Status (e.g., "0/5 tasks", "complete", "no tasks") - How recently it was modified (from `lastModified` field) diff --git a/src/core/templates/workflows/continue-change.ts b/src/core/templates/workflows/continue-change.ts index 14b3109e43..3b4994606e 100644 --- a/src/core/templates/workflows/continue-change.ts +++ b/src/core/templates/workflows/continue-change.ts @@ -28,7 +28,9 @@ ${STORE_SELECTION_GUIDANCE} When prompting, present the top 3-4 most recently modified changes as options, showing: - Change name - - Schema (from \`schema\` field if present, otherwise "spec-driven") + - Schema (optional) - \`openspec list --json\` does not report it. Omit the line, or resolve + each candidate with \`openspec status --change "" --json\` and show its \`schemaName\`. + Never fall back to "spec-driven": that mislabels every custom-schema change. - Status (e.g., "0/5 tasks", "complete", "no tasks") - How recently it was modified (from \`lastModified\` field) @@ -146,7 +148,9 @@ ${STORE_SELECTION_GUIDANCE} When prompting, present the top 3-4 most recently modified changes as options, showing: - Change name - - Schema (from \`schema\` field if present, otherwise "spec-driven") + - Schema (optional) - \`openspec list --json\` does not report it. Omit the line, or resolve + each candidate with \`openspec status --change "" --json\` and show its \`schemaName\`. + Never fall back to "spec-driven": that mislabels every custom-schema change. - Status (e.g., "0/5 tasks", "complete", "no tasks") - How recently it was modified (from \`lastModified\` field) diff --git a/src/core/templates/workflows/update-change.ts b/src/core/templates/workflows/update-change.ts index 7700cd8d7e..e5987961e2 100644 --- a/src/core/templates/workflows/update-change.ts +++ b/src/core/templates/workflows/update-change.ts @@ -30,7 +30,9 @@ ${STORE_SELECTION_GUIDANCE} When prompting, present the top 3-4 most recently modified changes as options, showing: - Change name - - Schema (from \`schema\` field if present, otherwise "spec-driven") + - Schema (optional) - \`openspec list --json\` does not report it. Omit the line, or resolve + each candidate with \`openspec status --change "" --json\` and show its \`schemaName\`. + Never fall back to "spec-driven": that mislabels every custom-schema change. - Status (e.g., "0/5 tasks", "complete", "no tasks") - How recently it was modified (from \`lastModified\` field) @@ -121,7 +123,9 @@ ${STORE_SELECTION_GUIDANCE} When prompting, present the top 3-4 most recently modified changes as options, showing: - Change name - - Schema (from \`schema\` field if present, otherwise "spec-driven") + - Schema (optional) - \`openspec list --json\` does not report it. Omit the line, or resolve + each candidate with \`openspec status --change "" --json\` and show its \`schemaName\`. + Never fall back to "spec-driven": that mislabels every custom-schema change. - Status (e.g., "0/5 tasks", "complete", "no tasks") - How recently it was modified (from \`lastModified\` field) diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 4d2e717cae..e788f858ae 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -40,14 +40,14 @@ import { STORE_SELECTION_GUIDANCE } from '../../../src/core/templates/workflows/ const EXPECTED_FUNCTION_HASHES: Record = { getExploreSkillTemplate: '06aba775c621e61f00995a9ebc3a02fe873ddcc9bf024e416c4adaf91ccce115', getNewChangeSkillTemplate: 'eabd1e895c5881dcb17dcbaa3fb26098dd59e8eacb318e400820b4dc811ef781', - getContinueChangeSkillTemplate: '012136f6411a99c8fa228e2f9444cb64b0a89e0f56fdeac2fe03b2f5bee0c5d7', + getContinueChangeSkillTemplate: '4cad591b0fe601da0b6201c3358ee265aaa533b51d076434afa963e7795ba613', getApplyChangeSkillTemplate: 'd1e7d5ceb85193c0964057dbb88e9651526754bd33f84020e2440ff0621d5dbb', getFfChangeSkillTemplate: 'efa6a70c111b18b61a7720250b9622afa9a212fb64edf609cf80e2182a9bdf8c', getSyncSpecsSkillTemplate: 'b099e2ff31859c9b10d928066e662524f9aad9ecf2be12fceacb732d718c4146', getOnboardSkillTemplate: '3a836faae463d88c289a1c129cb7ee556a563b7e53e1a52a4711ff152a3b51f7', getOpsxExploreCommandTemplate: '8046003e97d885a86ed392d4fb522bb78544a02872b042e51347a5021cc10523', getOpsxNewCommandTemplate: 'f2d30e569798a4c92ba932859d6ba4e0ad10e18feccbade1cfee0957597b3463', - getOpsxContinueCommandTemplate: 'e50e50266efa1b8e64ff9b6274ee8254f0a240d6adc1b862d126e2f1c9d3a559', + getOpsxContinueCommandTemplate: '24b82d1d34a38ee79b57dbbf4b2392a38acd736de2b3034027003ddad7e79415', getOpsxApplyCommandTemplate: 'e3579ac78f2e2c75fa3d3a7ac7dc3e49c395e96f7323398f0f041d94f8de9bb0', getOpsxFfCommandTemplate: '21132fc9c6d3b3ab2d2295d6bbd72d1e0052eb35ea1be0258c8b1ab3e200c4db', getArchiveChangeSkillTemplate: '56bfada1a5f35a127791b70de9d428a75b5aedd1584d6c9803a1ecb1fd1b4a23', @@ -61,14 +61,14 @@ const EXPECTED_FUNCTION_HASHES: Record = { getOpsxProposeSkillTemplate: 'b7215583fefddae0127076465de9b3de9c230f2f1ea9ae6e4fb2a46fe510e8d6', getOpsxProposeCommandTemplate: 'f016c66c2b6115b459751154c76a6270e444d6aee31973bb7cb8c0e6d505fb98', getFeedbackSkillTemplate: 'dabeb5e825b9349abc8156c3e7b8608f27987912a6d9bf47ef29addde6138133', - getUpdateChangeSkillTemplate: '7dc8abc6f64c58bf34d7581ed4ab095a3b7a53cb372349bee2d840db58622819', - getOpsxUpdateCommandTemplate: 'e2388521b22f92f74561df9a0c2f98e1fa4d265af93b5ba26f42fb47a6c5bfed', + getUpdateChangeSkillTemplate: '412c9ae111ebddd636e1db847c96efa9113d68c494d2d45accc8e90f6349df97', + getOpsxUpdateCommandTemplate: '6dad8927281da4d23c0afa3bd941b77b53b018b505ad77f2b96c2d4580a8fb92', }; const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { 'openspec-explore': '32b20cfbcc7d51ff526bb19571ff3dc3d0c616a5911b8de74cf6d9b15650cf3e', 'openspec-new-change': 'ec4529beef978e34634a6f7286fab55d68fad8fb374dceb45691d52caab33fbb', - 'openspec-continue-change': 'bb6194a16c54891cdb253678e8f70ce53b2af86735243980f366ce551d37e42e', + 'openspec-continue-change': '4d00f5615dc024bee1b68e1dc5be9bacb09e440c6b77c6b61d0cc1f9599644a3', 'openspec-apply-change': '81ea96d9fa6ec8536cd23c1fe561ed28e1cc1cad0a8ceb700588e08974cc0e49', 'openspec-ff-change': '31355250514bce51b16ff37ee2b833bc9d475cd0dbd4b1f68fe2041694575623', 'openspec-sync-specs': 'd933d8856584d6c1253de91e652e7aee9e85c77ad4d3531f6476f79d84e6e5e8', @@ -77,7 +77,7 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { 'openspec-verify-change': 'af9be013dcbe8c6d8f6d9ab10c893fbd03f4c62933c384d82f63894dd0ceb84f', 'openspec-onboard': 'f6f59476acaf5e4d65dbb180da4cef62432612f3cecf207d471a951295e2003a', 'openspec-propose': '679d0f868bed23cfb34a8ecc6b4ba4ff7b88dd7dbaef91563423e98f194f988f', - 'openspec-update-change': '586547406aca94422dfeb3ffedce6c01049429b743f57ce829baa79ebc714d51', + 'openspec-update-change': '5a5b0f6eacd44ba1e0e980da6bdb1665aa79648d46c5cbe8533d065411f73103', }; // Intentionally excludes getFeedbackSkillTemplate: this list only models templates From edc3ecf4cc7a5c3103ab16ab8a453d93875e1f4c Mon Sep 17 00:00:00 2001 From: Alex <45095641+runsonmypc@users.noreply.github.com> Date: Thu, 27 Aug 2026 22:13:16 -0400 Subject: [PATCH 2/9] fix(workflows): align list prompts with JSON fields --- .changeset/schema-label-from-status.md | 2 +- skills/openspec-bulk-archive-change/SKILL.md | 2 +- skills/openspec-continue-change/SKILL.md | 3 -- skills/openspec-explore/SKILL.md | 2 +- skills/openspec-update-change/SKILL.md | 3 -- .../workflows/bulk-archive-change.ts | 4 +- .../templates/workflows/continue-change.ts | 6 --- src/core/templates/workflows/explore.ts | 4 +- src/core/templates/workflows/update-change.ts | 6 --- .../core/templates/list-json-contract.test.ts | 54 +++++++++++++++++++ .../templates/skill-templates-parity.test.ts | 24 ++++----- 11 files changed, 73 insertions(+), 37 deletions(-) create mode 100644 test/core/templates/list-json-contract.test.ts diff --git a/.changeset/schema-label-from-status.md b/.changeset/schema-label-from-status.md index 04aa74d18c..b482256fb5 100644 --- a/.changeset/schema-label-from-status.md +++ b/.changeset/schema-label-from-status.md @@ -2,4 +2,4 @@ "@fission-ai/openspec": patch --- -Stop the `/opsx:update` and `/opsx:continue` change pickers from labelling every change `spec-driven`. Both templates read a `schema` field from `openspec list --json`, which that command does not emit, so the fallback fired unconditionally and mislabelled custom-schema changes. The schema line is now optional and, when shown, resolved from `openspec status --change "" --json` (`schemaName`). +Stop workflows from displaying schema names that `openspec list --json` does not return. Update and continue no longer fabricate a `spec-driven` picker label, while bulk archive and explore describe only the change fields the list command actually provides. diff --git a/skills/openspec-bulk-archive-change/SKILL.md b/skills/openspec-bulk-archive-change/SKILL.md index 252e1dd155..272690f4d2 100644 --- a/skills/openspec-bulk-archive-change/SKILL.md +++ b/skills/openspec-bulk-archive-change/SKILL.md @@ -30,7 +30,7 @@ This skill allows you to batch-archive changes, handling spec conflicts intellig 2. **Prompt for change selection** Ask the user to choose changes (multi-select): - - Show each change with its schema + - Show each change name and task status from the list output - Include an option for "All changes" - Allow any number of selections (1+ works, 2+ is the typical use case) diff --git a/skills/openspec-continue-change/SKILL.md b/skills/openspec-continue-change/SKILL.md index da997a9680..2ea3d18135 100644 --- a/skills/openspec-continue-change/SKILL.md +++ b/skills/openspec-continue-change/SKILL.md @@ -26,9 +26,6 @@ Continue working on a change by creating the next artifact. When prompting, present the top 3-4 most recently modified changes as options, showing: - Change name - - Schema (optional) - `openspec list --json` does not report it. Omit the line, or resolve - each candidate with `openspec status --change "" --json` and show its `schemaName`. - Never fall back to "spec-driven": that mislabels every custom-schema change. - Status (e.g., "0/5 tasks", "complete", "no tasks") - How recently it was modified (from `lastModified` field) diff --git a/skills/openspec-explore/SKILL.md b/skills/openspec-explore/SKILL.md index 2ff15bfed1..fb91c5b815 100644 --- a/skills/openspec-explore/SKILL.md +++ b/skills/openspec-explore/SKILL.md @@ -117,7 +117,7 @@ openspec list --json This tells you: - If there are active changes -- Their names, schemas, and status +- Their names and task status - What the user might be working on That is the *change* list - work in flight. It does not include the project's durable capabilities, so list those too: diff --git a/skills/openspec-update-change/SKILL.md b/skills/openspec-update-change/SKILL.md index f9a3ec9cb2..a859f6c03e 100644 --- a/skills/openspec-update-change/SKILL.md +++ b/skills/openspec-update-change/SKILL.md @@ -28,9 +28,6 @@ Revise a change's existing planning artifacts and keep them coherent. Never edit When prompting, present the top 3-4 most recently modified changes as options, showing: - Change name - - Schema (optional) - `openspec list --json` does not report it. Omit the line, or resolve - each candidate with `openspec status --change "" --json` and show its `schemaName`. - Never fall back to "spec-driven": that mislabels every custom-schema change. - Status (e.g., "0/5 tasks", "complete", "no tasks") - How recently it was modified (from `lastModified` field) diff --git a/src/core/templates/workflows/bulk-archive-change.ts b/src/core/templates/workflows/bulk-archive-change.ts index cacede2543..e65abffaf1 100644 --- a/src/core/templates/workflows/bulk-archive-change.ts +++ b/src/core/templates/workflows/bulk-archive-change.ts @@ -32,7 +32,7 @@ ${STORE_SELECTION_GUIDANCE} 2. **Prompt for change selection** Ask the user to choose changes (multi-select): - - Show each change with its schema + - Show each change name and task status from the list output - Include an option for "All changes" - Allow any number of selections (1+ works, 2+ is the typical use case) @@ -371,7 +371,7 @@ ${STORE_SELECTION_GUIDANCE} 2. **Prompt for change selection** Ask the user to choose changes (multi-select): - - Show each change with its schema + - Show each change name and task status from the list output - Include an option for "All changes" - Allow any number of selections (1+ works, 2+ is the typical use case) diff --git a/src/core/templates/workflows/continue-change.ts b/src/core/templates/workflows/continue-change.ts index 3b4994606e..097ce19ea9 100644 --- a/src/core/templates/workflows/continue-change.ts +++ b/src/core/templates/workflows/continue-change.ts @@ -28,9 +28,6 @@ ${STORE_SELECTION_GUIDANCE} When prompting, present the top 3-4 most recently modified changes as options, showing: - Change name - - Schema (optional) - \`openspec list --json\` does not report it. Omit the line, or resolve - each candidate with \`openspec status --change "" --json\` and show its \`schemaName\`. - Never fall back to "spec-driven": that mislabels every custom-schema change. - Status (e.g., "0/5 tasks", "complete", "no tasks") - How recently it was modified (from \`lastModified\` field) @@ -148,9 +145,6 @@ ${STORE_SELECTION_GUIDANCE} When prompting, present the top 3-4 most recently modified changes as options, showing: - Change name - - Schema (optional) - \`openspec list --json\` does not report it. Omit the line, or resolve - each candidate with \`openspec status --change "" --json\` and show its \`schemaName\`. - Never fall back to "spec-driven": that mislabels every custom-schema change. - Status (e.g., "0/5 tasks", "complete", "no tasks") - How recently it was modified (from \`lastModified\` field) diff --git a/src/core/templates/workflows/explore.ts b/src/core/templates/workflows/explore.ts index 20db318293..0ee67b2c63 100644 --- a/src/core/templates/workflows/explore.ts +++ b/src/core/templates/workflows/explore.ts @@ -121,7 +121,7 @@ openspec list --json This tells you: - If there are active changes -- Their names, schemas, and status +- Their names and task status - What the user might be working on That is the *change* list - work in flight. It does not include the project's durable capabilities, so list those too: @@ -451,7 +451,7 @@ openspec list --json This tells you: - If there are active changes -- Their names, schemas, and status +- Their names and task status - What the user might be working on That is the *change* list - work in flight. It does not include the project's durable capabilities, so list those too: diff --git a/src/core/templates/workflows/update-change.ts b/src/core/templates/workflows/update-change.ts index e5987961e2..b1a3280ddb 100644 --- a/src/core/templates/workflows/update-change.ts +++ b/src/core/templates/workflows/update-change.ts @@ -30,9 +30,6 @@ ${STORE_SELECTION_GUIDANCE} When prompting, present the top 3-4 most recently modified changes as options, showing: - Change name - - Schema (optional) - \`openspec list --json\` does not report it. Omit the line, or resolve - each candidate with \`openspec status --change "" --json\` and show its \`schemaName\`. - Never fall back to "spec-driven": that mislabels every custom-schema change. - Status (e.g., "0/5 tasks", "complete", "no tasks") - How recently it was modified (from \`lastModified\` field) @@ -123,9 +120,6 @@ ${STORE_SELECTION_GUIDANCE} When prompting, present the top 3-4 most recently modified changes as options, showing: - Change name - - Schema (optional) - \`openspec list --json\` does not report it. Omit the line, or resolve - each candidate with \`openspec status --change "" --json\` and show its \`schemaName\`. - Never fall back to "spec-driven": that mislabels every custom-schema change. - Status (e.g., "0/5 tasks", "complete", "no tasks") - How recently it was modified (from \`lastModified\` field) diff --git a/test/core/templates/list-json-contract.test.ts b/test/core/templates/list-json-contract.test.ts new file mode 100644 index 0000000000..0608614b3c --- /dev/null +++ b/test/core/templates/list-json-contract.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from 'vitest'; + +import { + getBulkArchiveChangeSkillTemplate, + getContinueChangeSkillTemplate, + getExploreSkillTemplate, + getOpsxBulkArchiveCommandTemplate, + getOpsxContinueCommandTemplate, + getOpsxExploreCommandTemplate, + getOpsxUpdateCommandTemplate, + getUpdateChangeSkillTemplate, +} from '../../../src/core/templates/skill-templates.js'; + +describe('workflow list --json field usage', () => { + it('does not invent schema labels in update and continue pickers', () => { + const bodies = [ + getUpdateChangeSkillTemplate().instructions, + getOpsxUpdateCommandTemplate().content, + getContinueChangeSkillTemplate().instructions, + getOpsxContinueCommandTemplate().content, + ]; + + for (const body of bodies) { + expect(body).toContain('openspec list --json'); + expect(body).not.toContain('- Schema (from `schema` field'); + expect(body).not.toContain('- Schema (optional)'); + expect(body).not.toContain('otherwise "spec-driven"'); + } + }); + + it('limits bulk archive selection to list fields', () => { + const bodies = [ + getBulkArchiveChangeSkillTemplate().instructions, + getOpsxBulkArchiveCommandTemplate().content, + ]; + + for (const body of bodies) { + expect(body).toContain('Show each change name and task status from the list output'); + expect(body).not.toContain('Show each change with its schema'); + } + }); + + it('does not claim explore receives schemas from list output', () => { + const bodies = [ + getExploreSkillTemplate().instructions, + getOpsxExploreCommandTemplate().content, + ]; + + for (const body of bodies) { + expect(body).toContain('Their names and task status'); + expect(body).not.toContain('Their names, schemas, and status'); + } + }); +}); diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index e788f858ae..b822800e96 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -38,46 +38,46 @@ import { import { STORE_SELECTION_GUIDANCE } from '../../../src/core/templates/workflows/store-selection.js'; const EXPECTED_FUNCTION_HASHES: Record = { - getExploreSkillTemplate: '06aba775c621e61f00995a9ebc3a02fe873ddcc9bf024e416c4adaf91ccce115', + getExploreSkillTemplate: '77310805765b532c7178c30b176328a7dea3ba8665a7b6eb320438525139ed45', getNewChangeSkillTemplate: 'eabd1e895c5881dcb17dcbaa3fb26098dd59e8eacb318e400820b4dc811ef781', - getContinueChangeSkillTemplate: '4cad591b0fe601da0b6201c3358ee265aaa533b51d076434afa963e7795ba613', + getContinueChangeSkillTemplate: '1f897903a42a9e9bea3fda5a0b4b808abfe5e46ee330a439c897f4eda350f1d1', getApplyChangeSkillTemplate: 'd1e7d5ceb85193c0964057dbb88e9651526754bd33f84020e2440ff0621d5dbb', getFfChangeSkillTemplate: 'efa6a70c111b18b61a7720250b9622afa9a212fb64edf609cf80e2182a9bdf8c', getSyncSpecsSkillTemplate: 'b099e2ff31859c9b10d928066e662524f9aad9ecf2be12fceacb732d718c4146', getOnboardSkillTemplate: '3a836faae463d88c289a1c129cb7ee556a563b7e53e1a52a4711ff152a3b51f7', - getOpsxExploreCommandTemplate: '8046003e97d885a86ed392d4fb522bb78544a02872b042e51347a5021cc10523', + getOpsxExploreCommandTemplate: '909a23b1de600a104821e51781de6997c902081f6153462582642450315510cf', getOpsxNewCommandTemplate: 'f2d30e569798a4c92ba932859d6ba4e0ad10e18feccbade1cfee0957597b3463', - getOpsxContinueCommandTemplate: '24b82d1d34a38ee79b57dbbf4b2392a38acd736de2b3034027003ddad7e79415', + getOpsxContinueCommandTemplate: '48da556936ebb1b169c9e1673c2f67e83821341579b54c08a20355885eb8810d', getOpsxApplyCommandTemplate: 'e3579ac78f2e2c75fa3d3a7ac7dc3e49c395e96f7323398f0f041d94f8de9bb0', getOpsxFfCommandTemplate: '21132fc9c6d3b3ab2d2295d6bbd72d1e0052eb35ea1be0258c8b1ab3e200c4db', getArchiveChangeSkillTemplate: '56bfada1a5f35a127791b70de9d428a75b5aedd1584d6c9803a1ecb1fd1b4a23', - getBulkArchiveChangeSkillTemplate: '93875998cade5322d95b43299fba794bc1da754e917dd63a770406386a6d295d', + getBulkArchiveChangeSkillTemplate: '4f62d0aca4725af933e801766211088f3142afa576f4fac1b2eb15c1c5ed2cac', getOpsxSyncCommandTemplate: '0d2427efb79986e8fff3f96bd075a739c80d45eb29159fae717e950030da8202', getVerifyChangeSkillTemplate: '223b7ffd99299a7d430e13092b9a0a3421b39f0d3217232f46c39d79b5f619ff', getOpsxArchiveCommandTemplate: '9f973c819b11620985b03322945f0e0a92a02a2ef455b94e74482f5e6292ac5d', getOpsxOnboardCommandTemplate: 'ee99aa99252c602720fbb8c63fb3ac438a5bd4e952fd961ddf1ae956cbfc2c8f', - getOpsxBulkArchiveCommandTemplate: '9fa8cdebe2f5667ebfc37bdc023396762c59d5b038c771dac2d8fd2c19e2627b', + getOpsxBulkArchiveCommandTemplate: 'aa253415da3d957057b41513ea8833edd87b14979504b45c2fc1310658876fdf', getOpsxVerifyCommandTemplate: '1efcf7eff0671f48e9d9420f50865c563dd3079ee60f8c380bb7a90dd0102696', getOpsxProposeSkillTemplate: 'b7215583fefddae0127076465de9b3de9c230f2f1ea9ae6e4fb2a46fe510e8d6', getOpsxProposeCommandTemplate: 'f016c66c2b6115b459751154c76a6270e444d6aee31973bb7cb8c0e6d505fb98', getFeedbackSkillTemplate: 'dabeb5e825b9349abc8156c3e7b8608f27987912a6d9bf47ef29addde6138133', - getUpdateChangeSkillTemplate: '412c9ae111ebddd636e1db847c96efa9113d68c494d2d45accc8e90f6349df97', - getOpsxUpdateCommandTemplate: '6dad8927281da4d23c0afa3bd941b77b53b018b505ad77f2b96c2d4580a8fb92', + getUpdateChangeSkillTemplate: '3e886db1208637a116e7604253d2514388c653a32d7751a5995b93ad3f2d9ad8', + getOpsxUpdateCommandTemplate: '2c17e805ed5fc20ea2046d68cd75717b4091ada19f339ee32170fd429f1db19d', }; const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { - 'openspec-explore': '32b20cfbcc7d51ff526bb19571ff3dc3d0c616a5911b8de74cf6d9b15650cf3e', + 'openspec-explore': '3ac483ac4f5327df7aab18705c6b15b251bcf26afb0426e374fb0fa543a5092d', 'openspec-new-change': 'ec4529beef978e34634a6f7286fab55d68fad8fb374dceb45691d52caab33fbb', - 'openspec-continue-change': '4d00f5615dc024bee1b68e1dc5be9bacb09e440c6b77c6b61d0cc1f9599644a3', + 'openspec-continue-change': '00105e94996f2c7f38a99c8a7322cf1ab6a9ce5c1074ebcd5ce0945fb0fb00c3', 'openspec-apply-change': '81ea96d9fa6ec8536cd23c1fe561ed28e1cc1cad0a8ceb700588e08974cc0e49', 'openspec-ff-change': '31355250514bce51b16ff37ee2b833bc9d475cd0dbd4b1f68fe2041694575623', 'openspec-sync-specs': 'd933d8856584d6c1253de91e652e7aee9e85c77ad4d3531f6476f79d84e6e5e8', 'openspec-archive-change': '7c65053d674ba4e1e20e2bf73ba7e5a7f94baef2eaa9b33cee48d4cadea51b7a', - 'openspec-bulk-archive-change': '2039b9ecf6e64339dffe0e16272507a386d9fe326f419ff758315aa736fdd96c', + 'openspec-bulk-archive-change': 'c52882cb2f9e7b00c8bff5aa345699166841265b3f8b3d71e931ae337fe06856', 'openspec-verify-change': 'af9be013dcbe8c6d8f6d9ab10c893fbd03f4c62933c384d82f63894dd0ceb84f', 'openspec-onboard': 'f6f59476acaf5e4d65dbb180da4cef62432612f3cecf207d471a951295e2003a', 'openspec-propose': '679d0f868bed23cfb34a8ecc6b4ba4ff7b88dd7dbaef91563423e98f194f988f', - 'openspec-update-change': '5a5b0f6eacd44ba1e0e980da6bdb1665aa79648d46c5cbe8533d065411f73103', + 'openspec-update-change': '4106014c4283e23d027713864e8455e290196d53a30e8942763fbcf912efeca4', }; // Intentionally excludes getFeedbackSkillTemplate: this list only models templates From 59efd76d9c7e2c386e884e7adb98598640f625b6 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Fri, 28 Aug 2026 13:35:25 -0500 Subject: [PATCH 3/9] test(workflows): verify list and status schema contracts --- test/cli-e2e/basic.test.ts | 49 +++++++++++++++++++ .../core/templates/list-json-contract.test.ts | 25 +++++++--- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/test/cli-e2e/basic.test.ts b/test/cli-e2e/basic.test.ts index c05965ea72..a8bc3a42a9 100644 --- a/test/cli-e2e/basic.test.ts +++ b/test/cli-e2e/basic.test.ts @@ -153,6 +153,55 @@ describe('openspec CLI e2e basics', () => { }); }); + it.each([ + { tasks: '', completedTasks: 0, totalTasks: 0, status: 'no-tasks' }, + { tasks: '- [x] Done\n- [ ] Pending\n', completedTasks: 1, totalTasks: 2, status: 'in-progress' }, + { tasks: '- [x] Done\n', completedTasks: 1, totalTasks: 1, status: 'complete' }, + ])('lists custom-schema task status ($status) without inventing a schema field', async (expected) => { + const projectDir = await prepareFixture('tmp-init'); + const schemaDir = path.join(projectDir, 'openspec', 'schemas', 'custom-workflow'); + await fs.mkdir(schemaDir, { recursive: true }); + await fs.writeFile(path.join(schemaDir, 'schema.yaml'), [ + 'name: custom-workflow', + 'version: 1', + 'description: Custom tracked work', + 'artifacts:', + ' - id: work', + ' generates: work.md', + ' description: Implementation work', + ' template: work.md', + ' requires: []', + 'apply:', + ' requires: [work]', + ' tracks: work.md', + '', + ].join('\n')); + const changeDir = path.join(projectDir, 'openspec', 'changes', 'custom-change'); + await fs.mkdir(changeDir, { recursive: true }); + await fs.writeFile(path.join(changeDir, '.openspec.yaml'), 'schema: custom-workflow\n'); + await fs.writeFile(path.join(changeDir, 'work.md'), expected.tasks); + + const listResult = await runCLI(['list', '--json'], { cwd: projectDir }); + expectJsonOnlyOutput(listResult); + const change = JSON.parse(listResult.stdout).changes.find( + (entry: { name: string }) => entry.name === 'custom-change' + ); + expect(change).toMatchObject({ + name: 'custom-change', + completedTasks: expected.completedTasks, + totalTasks: expected.totalTasks, + status: expected.status, + lastModified: expect.any(String), + }); + expect(Number.isFinite(Date.parse(change.lastModified))).toBe(true); + expect(change).not.toHaveProperty('schema'); + expect(change).not.toHaveProperty('schemaName'); + + const statusResult = await runCLI(['status', '--change', 'custom-change', '--json'], { cwd: projectDir }); + expectJsonOnlyOutput(statusResult); + expect(JSON.parse(statusResult.stdout).schemaName).toBe('custom-workflow'); + }); + it('keeps schemas --json free of spinner output', async () => { const projectDir = await prepareFixture('tmp-init'); const result = await runCLI(['schemas', '--json'], { cwd: projectDir }); diff --git a/test/core/templates/list-json-contract.test.ts b/test/core/templates/list-json-contract.test.ts index 0608614b3c..2fa46aac76 100644 --- a/test/core/templates/list-json-contract.test.ts +++ b/test/core/templates/list-json-contract.test.ts @@ -21,10 +21,17 @@ describe('workflow list --json field usage', () => { ]; for (const body of bodies) { - expect(body).toContain('openspec list --json'); - expect(body).not.toContain('- Schema (from `schema` field'); - expect(body).not.toContain('- Schema (optional)'); - expect(body).not.toContain('otherwise "spec-driven"'); + const picker = body.slice(body.indexOf('1. **Select the change**'), body.indexOf('2. **')); + expect(picker).toContain('openspec list --json'); + expect(picker).toContain('- Change name'); + expect(picker).toContain('- Status'); + expect(picker).toContain('`lastModified`'); + expect(picker).not.toMatch(/schema/i); + expect(picker).not.toContain('openspec status'); + + const status = body.slice(body.indexOf('2. **'), body.indexOf('3. **')); + expect(status).toContain('openspec status --change "" --json'); + expect(status).toContain('`schemaName`'); } }); @@ -35,8 +42,14 @@ describe('workflow list --json field usage', () => { ]; for (const body of bodies) { - expect(body).toContain('Show each change name and task status from the list output'); - expect(body).not.toContain('Show each change with its schema'); + const picker = body.slice(body.indexOf('2. **'), body.indexOf('3. **')); + expect(picker).toContain('Show each change name and task status from the list output'); + expect(picker).not.toMatch(/schema/i); + expect(picker).not.toContain('openspec status'); + + const status = body.slice(body.indexOf('3. **'), body.indexOf('4. **')); + expect(status).toContain('openspec status --change "" --json'); + expect(status).toContain('`schemaName`'); } }); From 95b77dc15e36fdd96f547bdcbea8f14426692dc8 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Mon, 21 Sep 2026 15:28:18 -0500 Subject: [PATCH 4/9] fix(workflows): keep bulk archive sync available in custom profiles --- .../workflows/bulk-archive-change.ts | 4 +-- .../core/templates/list-json-contract.test.ts | 30 +++++++++++++++++++ .../templates/skill-templates-parity.test.ts | 2 +- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/core/templates/workflows/bulk-archive-change.ts b/src/core/templates/workflows/bulk-archive-change.ts index 86fcb553eb..c8e37314fa 100644 --- a/src/core/templates/workflows/bulk-archive-change.ts +++ b/src/core/templates/workflows/bulk-archive-change.ts @@ -212,7 +212,7 @@ ${PROJECT_ROOT_GUARD} Process changes in the determined order (respecting conflict resolution): a. **Sync included delta specs**: - - Run the \`openspec-sync-specs\` workflow inline (agent-driven intelligent merge) only for changes with entries in \`includedDeltas\`, passing only the included delta paths and explicitly instructing it to ignore that change's \`excludedDeltas\`. Wait for it to finish. + - ${optionalWorkflow('sync', 'Run the `openspec-sync-specs` workflow inline (agent-driven intelligent merge)', 'Perform the delta-to-main-spec merge inline yourself (agent-driven intelligent merge)')} only for changes with entries in \`includedDeltas\`, passing only the included delta paths and explicitly instructing it to ignore that change's \`excludedDeltas\`. Wait for it to finish. - For conflicts, apply in resolved order. - Pass that change's fetched specs-rule snapshot into inline sync; inline sync must reuse it without fetching instructions again @@ -365,7 +365,7 @@ No active changes found. Create a new change to get started. - Archive directory target uses the current date, computed once in step 3d and reused at the move: YYYY-MM-DD-; a name that already starts with a \`YYYY-MM-DD-\` prefix is used as-is (never stack a second date) - If archive target exists, fail that change but continue with others - Check every archive target in step 3, before the first main-spec write; a change whose target exists is never synced or moved -- If sync is requested, run the \`openspec-sync-specs\` workflow inline (agent-driven) for each change with included delta specs +- If sync is requested, ${optionalWorkflow('sync', 'run the `openspec-sync-specs` workflow inline (agent-driven)', 'perform the delta-to-main-spec merge inline (agent-driven)')} for each change with included delta specs - Carry the per-delta \`includedDeltas\` and \`excludedDeltas\` decisions into execution; sync and verify only included deltas - Report every excluded delta as \`sync skipped\` without treating the archive itself as skipped - Never archive a change while a spec sync is still in flight — run the sync inline and verify main specs at \`/openspec/specs//spec.md\` before moving \`changeRoot\` diff --git a/test/core/templates/list-json-contract.test.ts b/test/core/templates/list-json-contract.test.ts index 2fa46aac76..6a5f22919c 100644 --- a/test/core/templates/list-json-contract.test.ts +++ b/test/core/templates/list-json-contract.test.ts @@ -10,6 +10,7 @@ import { getOpsxUpdateCommandTemplate, getUpdateChangeSkillTemplate, } from '../../../src/core/templates/skill-templates.js'; +import { getCommandTemplates, getSkillTemplates } from '../../../src/core/shared/skill-generation.js'; describe('workflow list --json field usage', () => { it('does not invent schema labels in update and continue pickers', () => { @@ -64,4 +65,33 @@ describe('workflow list --json field usage', () => { expect(body).not.toContain('Their names, schemas, and status'); } }); + + it('keeps bulk archive sync available with and without the sync workflow', () => { + const variants = [ + [ + getSkillTemplates(['bulk-archive', 'sync']).find((entry) => entry.workflowId === 'bulk-archive')!.template.instructions, + getSkillTemplates(['bulk-archive'])[0].template.instructions, + 'openspec-sync-specs', + ], + [ + getCommandTemplates(['bulk-archive', 'sync']).find((entry) => entry.id === 'bulk-archive')!.template.content, + getCommandTemplates(['bulk-archive'])[0].template.content, + '/opsx:sync', + ], + ] as const; + + for (const [withSync, withoutSync, workflow] of variants) { + const syncStep = (text: string) => text.slice( + text.indexOf('a. **Sync included delta specs**'), + text.indexOf('b. **Verify included delta specs') + ); + + expect(syncStep(withSync)).toContain(workflow); + expect(syncStep(withoutSync)).not.toContain(workflow); + expect(syncStep(withoutSync)).toContain('Perform the delta-to-main-spec merge inline yourself'); + expect(syncStep(withoutSync)).toContain('`includedDeltas`'); + expect(syncStep(withoutSync)).toContain('`excludedDeltas`'); + expect(withoutSync).toContain('If sync is requested, perform the delta-to-main-spec merge inline'); + } + }); }); diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index fde260a0fd..69064d11f0 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -89,7 +89,7 @@ const EXPECTED_FUNCTION_HASHES: Record = { getOpsxApplyCommandTemplate: 'd70cecce3b7d1dd4dbd5fd1fc2bccb538f5e61f5b43d520e4beca896e3f9e6b3', getOpsxFfCommandTemplate: '04cb49b0bf3ebe364b45268a283564ee4fd50b78b01ec1d3f975bcae68179d2d', getArchiveChangeSkillTemplate: '8447a2489240bf0c27f863065d61453dd0264842d1dabafe27b577d6bff96eb3', - getBulkArchiveChangeSkillTemplate: '06f37262243c7fb80053dd3817c6e6678d169c0feaa308c08279647589de6865', + getBulkArchiveChangeSkillTemplate: '4c4b127e1ec95169aa600f8d4d7c7fcd35e3ae5d326cba88fc30fd37ed352e60', getOpsxSyncCommandTemplate: '60550b7bb9829421656d6324a9e4c951bc912f48f88882d1a07ce7f78397a5e7', getVerifyChangeSkillTemplate: '2e069a277dac23818b13bb50b66e806ab405bc3b7f535400e1ebf81b84153699', getOpsxArchiveCommandTemplate: '980109e5f8362610872c70fe0a0f1d48d3d2692275b2b17e2f4c91c3de89c2fd', From f33662eaf37c117d0d44907880d18b4d061dd308 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Tue, 22 Sep 2026 13:35:33 -0500 Subject: [PATCH 5/9] test(parity): regenerate hashes after merging #1940 Co-Authored-By: Claude Opus 5 --- test/core/templates/skill-templates-parity.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 69064d11f0..7c35cd00f3 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -80,20 +80,20 @@ const EXPECTED_FUNCTION_HASHES: Record = { getNewChangeSkillTemplate: '0e5035b7b42198afc430206a1dbc9579096650ef0813d85e837d5a6cd0b98a85', getContinueChangeSkillTemplate: 'c2c8a0ba7f8c8fc7b174793832cd50f7c404eb8e1f7d49c47000993d621633b6', getApplyChangeSkillTemplate: '04ae407c97b5f9cb0cc15199fe877ccc7cd1eff78bfe10ad70c16a112b10a661', - getFfChangeSkillTemplate: '6fb5492e78b9ceec068949080ec9f2e0d2a8baff75a2fe33d07ad33ffe542b65', + getFfChangeSkillTemplate: 'd091600476a815ba99f69b446bcd46af5bf73d1c2810215a0c6196937d019cf6', getSyncSpecsSkillTemplate: 'bc80fe9b07eaa289e5eb8a3ce65eb7df722a16d864e37283c678220712e4f230', - getOnboardSkillTemplate: '7d92756ffc0b30053838716005610daf3f65c3fa011f3f4d29b6488f303f9cfb', + getOnboardSkillTemplate: '8f4bb13c097c7c2dfca714c3f51765039008d87f844e62186e066bebd7637376', getOpsxExploreCommandTemplate: '5d11f8ecb4c457140a3e874a8bf7aa72674e922e698c208832b1f34d3c617719', getOpsxNewCommandTemplate: '6d504fef1e0d4ced7c423f4cc9d9d2cee11b1a6224edf685e06a3f0757e0ebff', getOpsxContinueCommandTemplate: '241c50f97d5d681412d456d6b982743c3a5babeb77017fc8099c418bcf0d92df', getOpsxApplyCommandTemplate: 'd70cecce3b7d1dd4dbd5fd1fc2bccb538f5e61f5b43d520e4beca896e3f9e6b3', - getOpsxFfCommandTemplate: '04cb49b0bf3ebe364b45268a283564ee4fd50b78b01ec1d3f975bcae68179d2d', + getOpsxFfCommandTemplate: '743a7304c7efc84aa87f556154c034e1e0e561c276c51870a30ada58f33eb9af', getArchiveChangeSkillTemplate: '8447a2489240bf0c27f863065d61453dd0264842d1dabafe27b577d6bff96eb3', getBulkArchiveChangeSkillTemplate: '4c4b127e1ec95169aa600f8d4d7c7fcd35e3ae5d326cba88fc30fd37ed352e60', getOpsxSyncCommandTemplate: '60550b7bb9829421656d6324a9e4c951bc912f48f88882d1a07ce7f78397a5e7', getVerifyChangeSkillTemplate: '2e069a277dac23818b13bb50b66e806ab405bc3b7f535400e1ebf81b84153699', getOpsxArchiveCommandTemplate: '980109e5f8362610872c70fe0a0f1d48d3d2692275b2b17e2f4c91c3de89c2fd', - getOpsxOnboardCommandTemplate: '9cad751f7b938eea039b0ba207247776269c81bec5923eb335bee468f515f244', + getOpsxOnboardCommandTemplate: '35332b79e943daefd4118513f03dc48926267af60348f781b8d92bbe5086a986', getOpsxBulkArchiveCommandTemplate: '153ba183a1b596703ca16424d79dc6942d85b167878084525c46dee50ba8c884', getOpsxVerifyCommandTemplate: '938f52f20fb9a3b811ea47314baac1034cd550e8ab363ae878ccba4b6329348f', getOpsxProposeSkillTemplate: '1aa2f2eb9c8cbc4dcab9d777bf8832b92ca04f9ef91d0494f1224a566aefdfe8', @@ -108,12 +108,12 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { 'openspec-new-change': '27e09d43785953827efc9a98bb9d6cf06db48fe6abe7e1c049409fe5b5061323', 'openspec-continue-change': '1f92fad53022270e96f8ea34de75f7c12c08225edd5a9e8f4e864b63b5ef79c5', 'openspec-apply-change': 'f3e92c229fab8d77df9f0a77dcb117cf46279b53a208d53aed89bfe0bab2ac09', - 'openspec-ff-change': '8ffad1b1a2deea5f097eb7294fb8b9474d5dfb1c31ee2fd3311d9a9d78259323', + 'openspec-ff-change': 'a7ab656d46f04d45dff0c8888df4a126a2e62288b7336f7445bce4d1715055f5', 'openspec-sync-specs': '3909936a236a21a9a6d5bf495f90b396b3b68fc9220d7b2c1894668653beb2e4', 'openspec-archive-change': '305a21a9c76a925055f3bdbaac504f208660ef6948d78f73928de166250609bf', 'openspec-bulk-archive-change': '73f81fee0ac7808a123a6fc5b977dea2b15616f22e2da397d5826d41cf06a6a2', 'openspec-verify-change': 'ad8a3098bd27d852721687c47a12db7107ed8b8dfc7f071406bb19961652e7ee', - 'openspec-onboard': 'd4c5f3e24c19c8e389950544ea0d1844027753def14748c9684210ae4c6cd5e5', + 'openspec-onboard': '526bb7f9b8ceb8670b600ce33b0a62fe268393b7e778e120c796316da3a3cc3d', 'openspec-propose': '66e3395adf9f2d93a09e8ef1d20e4efb010e5e8d4811f2d42a9316e4d1ca5a8b', 'openspec-update-change': '5ce46153bf2f8af8abc86bb5480c6aa213022719ecd01277273c4369c0af4430', }; From cd58b3cc1ff415939ac9efbab72bb0c415ac9990 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Tue, 22 Sep 2026 14:52:49 -0500 Subject: [PATCH 6/9] test(parity): restore the #1837 regression tests dropped in the merge The earlier conflict resolution took our whole side of the parity file, which discarded the two threshold tests main gained in #1940. Take main's file verbatim and regenerate the hashes instead. Co-Authored-By: Claude Opus 5 --- .../templates/skill-templates-parity.test.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 7c35cd00f3..6de54b58ea 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -156,6 +156,51 @@ function hash(value: string): string { } describe('skill templates split parity', () => { + it('uses one clarification threshold in fast-forward guidance (#1837)', () => { + const variants: Array<[string, string]> = [ + ['ff skill', getFfChangeSkillTemplate().instructions], + ['ff command', getOpsxFfCommandTemplate().content], + ]; + + for (const [variant, content] of variants) { + expect(content, variant).toContain( + '**If an artifact requires user input** (critically unclear context)' + ); + expect(content, variant).not.toContain( + '**If an artifact requires user input** (unclear context)' + ); + } + }); + + it('approves onboarding tasks before saving or offering implementation (#1837)', () => { + const variants: Array<[string, string]> = [ + ['onboard skill', getOnboardSkillTemplate().instructions], + ['onboard command', getOpsxOnboardCommandTemplate().content], + ]; + + for (const [variant, content] of variants) { + expect(content, variant).toContain('Does this task breakdown look right?'); + expect(content, variant).not.toContain( + 'Each checkbox becomes a unit of work in the apply phase. Ready to implement?' + ); + expect(content, variant).toContain( + '**PAUSE** - Wait for user approval/feedback.\n\n' + + 'After approval, save to the `resolvedOutputPath` from `openspec instructions tasks --change "" --json`.' + ); + expect(content, variant).toContain('> "Tasks are saved. Ready to implement?"'); + expect(content, variant).toContain( + '**PAUSE** - Wait for user to confirm before implementation.' + ); + + const saveAt = content.indexOf('After approval, save to the `resolvedOutputPath`'); + const implementationChoiceAt = content.indexOf('> "Tasks are saved. Ready to implement?"'); + const implementationAt = content.indexOf('## Phase 9: Apply (Implementation)'); + expect(saveAt, variant).toBeGreaterThanOrEqual(0); + expect(implementationChoiceAt, variant).toBeGreaterThan(saveAt); + expect(implementationAt, variant).toBeGreaterThan(implementationChoiceAt); + } + }); + it('preserves all template function payloads exactly', () => { const functionFactories: Record unknown> = { getExploreSkillTemplate, From 684e2b8ab0915b355a6b67fd1efd225cdb958e79 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Wed, 23 Sep 2026 10:43:23 -0500 Subject: [PATCH 7/9] test(parity): regenerate hashes after merging #1955 Co-Authored-By: Claude Opus 5 --- .../templates/skill-templates-parity.test.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 5bbcb847c3..3d5e90ee59 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -76,46 +76,46 @@ function specDrivenTitles(): Record { } const EXPECTED_FUNCTION_HASHES: Record = { - getExploreSkillTemplate: 'b17a409b5634b5e48864a87f038f2111a74c2442288e9f4cb7a704f86b6d75e7', + getExploreSkillTemplate: 'c1fddb294758004936add586f5826694cb06175cff935b75fd3a8d92332332e6', getNewChangeSkillTemplate: '0e5035b7b42198afc430206a1dbc9579096650ef0813d85e837d5a6cd0b98a85', - getContinueChangeSkillTemplate: '550dc22bc8e0921b1ca5cef867379c4f370c5f1902c420bf9fa3bbfa75cea933', + getContinueChangeSkillTemplate: 'c2c8a0ba7f8c8fc7b174793832cd50f7c404eb8e1f7d49c47000993d621633b6', getApplyChangeSkillTemplate: '04ae407c97b5f9cb0cc15199fe877ccc7cd1eff78bfe10ad70c16a112b10a661', getFfChangeSkillTemplate: 'd091600476a815ba99f69b446bcd46af5bf73d1c2810215a0c6196937d019cf6', getSyncSpecsSkillTemplate: 'bc80fe9b07eaa289e5eb8a3ce65eb7df722a16d864e37283c678220712e4f230', getOnboardSkillTemplate: '84258a06c0ca88de708a23dd74e9a17efe11eff63a071b3864c781dcd5a0a4b7', - getOpsxExploreCommandTemplate: 'f6cf22825643281d653355745623a6c1a4566db46cc2f262d2282243c6d8169a', + getOpsxExploreCommandTemplate: '5d11f8ecb4c457140a3e874a8bf7aa72674e922e698c208832b1f34d3c617719', getOpsxNewCommandTemplate: '6d504fef1e0d4ced7c423f4cc9d9d2cee11b1a6224edf685e06a3f0757e0ebff', - getOpsxContinueCommandTemplate: 'ace5c9cc239c12b57dc86fd9a1c02a6ca467cb8e1245127340c07ab1b9d37c11', + getOpsxContinueCommandTemplate: '241c50f97d5d681412d456d6b982743c3a5babeb77017fc8099c418bcf0d92df', getOpsxApplyCommandTemplate: 'd70cecce3b7d1dd4dbd5fd1fc2bccb538f5e61f5b43d520e4beca896e3f9e6b3', getOpsxFfCommandTemplate: '743a7304c7efc84aa87f556154c034e1e0e561c276c51870a30ada58f33eb9af', getArchiveChangeSkillTemplate: '8447a2489240bf0c27f863065d61453dd0264842d1dabafe27b577d6bff96eb3', - getBulkArchiveChangeSkillTemplate: 'f17399959921ff98c7798e4591c8888825b7c9a83b0a90f09d98c7e0984ab793', + getBulkArchiveChangeSkillTemplate: '4c4b127e1ec95169aa600f8d4d7c7fcd35e3ae5d326cba88fc30fd37ed352e60', getOpsxSyncCommandTemplate: '60550b7bb9829421656d6324a9e4c951bc912f48f88882d1a07ce7f78397a5e7', getVerifyChangeSkillTemplate: '2e069a277dac23818b13bb50b66e806ab405bc3b7f535400e1ebf81b84153699', getOpsxArchiveCommandTemplate: '980109e5f8362610872c70fe0a0f1d48d3d2692275b2b17e2f4c91c3de89c2fd', getOpsxOnboardCommandTemplate: '0cf66e164c0e14c916c6d1ebb5d80ded07d7fb8e55d4eb34eba43e8ca9c28558', - getOpsxBulkArchiveCommandTemplate: '3db03eadb764abd74c8c180656c3f64a8b9a4971056c91624d38df3209d7b446', + getOpsxBulkArchiveCommandTemplate: '153ba183a1b596703ca16424d79dc6942d85b167878084525c46dee50ba8c884', getOpsxVerifyCommandTemplate: '938f52f20fb9a3b811ea47314baac1034cd550e8ab363ae878ccba4b6329348f', getOpsxProposeSkillTemplate: '1aa2f2eb9c8cbc4dcab9d777bf8832b92ca04f9ef91d0494f1224a566aefdfe8', getOpsxProposeCommandTemplate: '3b7090ce5e79e879ab9b5bdaf4ff2b52e3c02211f71188838772d36ac337f96c', getFeedbackSkillTemplate: 'dabeb5e825b9349abc8156c3e7b8608f27987912a6d9bf47ef29addde6138133', - getUpdateChangeSkillTemplate: 'f4c38adf3c82b3e0af7c460de97b72740d69a8966b5426b259f7c2cb6dc11d3d', - getOpsxUpdateCommandTemplate: 'a3156c2c3b4a429fed56545f315f66a7cc25bc9f8822c5fe30a60ccd87159a0f', + getUpdateChangeSkillTemplate: '7518de300b476ad82df9d658f39e97eb54265ab84bc56f5e9bd522945e049833', + getOpsxUpdateCommandTemplate: '18cf13a9dd5dd73a8d9ca58c17fab46ee425db2f46df9d6748e0478dc96e5f71', }; const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { - 'openspec-explore': '8b02eb77ae87a4374a43ad33930e5ea19cbaf1c8a7624d5b613c03d7ee1c5f14', + 'openspec-explore': '7d80caf9cd25a2565ba190b1297f1631c7f2c2db5e614597b4284abc0118ea70', 'openspec-new-change': '27e09d43785953827efc9a98bb9d6cf06db48fe6abe7e1c049409fe5b5061323', - 'openspec-continue-change': '182f015de6a1a114c79a6106c0565fd71f368d629641d0ad088de54bd871b52f', + 'openspec-continue-change': '1f92fad53022270e96f8ea34de75f7c12c08225edd5a9e8f4e864b63b5ef79c5', 'openspec-apply-change': 'f3e92c229fab8d77df9f0a77dcb117cf46279b53a208d53aed89bfe0bab2ac09', 'openspec-ff-change': 'a7ab656d46f04d45dff0c8888df4a126a2e62288b7336f7445bce4d1715055f5', 'openspec-sync-specs': '3909936a236a21a9a6d5bf495f90b396b3b68fc9220d7b2c1894668653beb2e4', 'openspec-archive-change': '305a21a9c76a925055f3bdbaac504f208660ef6948d78f73928de166250609bf', - 'openspec-bulk-archive-change': '4bd638a50111d2ee3a667752a2355ed513f770695b137b93fc28848ca7bf60d2', + 'openspec-bulk-archive-change': '73f81fee0ac7808a123a6fc5b977dea2b15616f22e2da397d5826d41cf06a6a2', 'openspec-verify-change': 'ad8a3098bd27d852721687c47a12db7107ed8b8dfc7f071406bb19961652e7ee', 'openspec-onboard': '6993eff867d97d485e080078f9dfb80e968e242f3b17a924eeb077715fd548fa', 'openspec-propose': '66e3395adf9f2d93a09e8ef1d20e4efb010e5e8d4811f2d42a9316e4d1ca5a8b', - 'openspec-update-change': '19163b8c1b40ccdc0840019aa8005877a90a3a1cd9f7aadb87f76ccce1342f19', + 'openspec-update-change': '5ce46153bf2f8af8abc86bb5480c6aa213022719ecd01277273c4369c0af4430', }; // Intentionally excludes getFeedbackSkillTemplate: this list only models templates From 7c50d4fd7e011fb2ef7911d66ec8755563e8857a Mon Sep 17 00:00:00 2001 From: Clay Good Date: Wed, 23 Sep 2026 11:13:52 -0500 Subject: [PATCH 8/9] test(parity): regenerate hashes after merging #1733 Co-Authored-By: Claude Opus 5 --- .../templates/skill-templates-parity.test.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 2d4f52a8f1..fb4dd81b2e 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -76,46 +76,46 @@ function specDrivenTitles(): Record { } const EXPECTED_FUNCTION_HASHES: Record = { - getExploreSkillTemplate: 'b17a409b5634b5e48864a87f038f2111a74c2442288e9f4cb7a704f86b6d75e7', + getExploreSkillTemplate: 'c1fddb294758004936add586f5826694cb06175cff935b75fd3a8d92332332e6', getNewChangeSkillTemplate: '0e5035b7b42198afc430206a1dbc9579096650ef0813d85e837d5a6cd0b98a85', - getContinueChangeSkillTemplate: '550dc22bc8e0921b1ca5cef867379c4f370c5f1902c420bf9fa3bbfa75cea933', + getContinueChangeSkillTemplate: 'c2c8a0ba7f8c8fc7b174793832cd50f7c404eb8e1f7d49c47000993d621633b6', getApplyChangeSkillTemplate: '04ae407c97b5f9cb0cc15199fe877ccc7cd1eff78bfe10ad70c16a112b10a661', getFfChangeSkillTemplate: 'd091600476a815ba99f69b446bcd46af5bf73d1c2810215a0c6196937d019cf6', getSyncSpecsSkillTemplate: 'bc80fe9b07eaa289e5eb8a3ce65eb7df722a16d864e37283c678220712e4f230', getOnboardSkillTemplate: '84258a06c0ca88de708a23dd74e9a17efe11eff63a071b3864c781dcd5a0a4b7', - getOpsxExploreCommandTemplate: 'f6cf22825643281d653355745623a6c1a4566db46cc2f262d2282243c6d8169a', + getOpsxExploreCommandTemplate: '5d11f8ecb4c457140a3e874a8bf7aa72674e922e698c208832b1f34d3c617719', getOpsxNewCommandTemplate: '6d504fef1e0d4ced7c423f4cc9d9d2cee11b1a6224edf685e06a3f0757e0ebff', - getOpsxContinueCommandTemplate: 'ace5c9cc239c12b57dc86fd9a1c02a6ca467cb8e1245127340c07ab1b9d37c11', + getOpsxContinueCommandTemplate: '241c50f97d5d681412d456d6b982743c3a5babeb77017fc8099c418bcf0d92df', getOpsxApplyCommandTemplate: 'd70cecce3b7d1dd4dbd5fd1fc2bccb538f5e61f5b43d520e4beca896e3f9e6b3', getOpsxFfCommandTemplate: '743a7304c7efc84aa87f556154c034e1e0e561c276c51870a30ada58f33eb9af', getArchiveChangeSkillTemplate: '8447a2489240bf0c27f863065d61453dd0264842d1dabafe27b577d6bff96eb3', - getBulkArchiveChangeSkillTemplate: 'f17399959921ff98c7798e4591c8888825b7c9a83b0a90f09d98c7e0984ab793', + getBulkArchiveChangeSkillTemplate: '4c4b127e1ec95169aa600f8d4d7c7fcd35e3ae5d326cba88fc30fd37ed352e60', getOpsxSyncCommandTemplate: '60550b7bb9829421656d6324a9e4c951bc912f48f88882d1a07ce7f78397a5e7', getVerifyChangeSkillTemplate: '2e069a277dac23818b13bb50b66e806ab405bc3b7f535400e1ebf81b84153699', getOpsxArchiveCommandTemplate: '980109e5f8362610872c70fe0a0f1d48d3d2692275b2b17e2f4c91c3de89c2fd', getOpsxOnboardCommandTemplate: '0cf66e164c0e14c916c6d1ebb5d80ded07d7fb8e55d4eb34eba43e8ca9c28558', - getOpsxBulkArchiveCommandTemplate: '3db03eadb764abd74c8c180656c3f64a8b9a4971056c91624d38df3209d7b446', + getOpsxBulkArchiveCommandTemplate: '153ba183a1b596703ca16424d79dc6942d85b167878084525c46dee50ba8c884', getOpsxVerifyCommandTemplate: '938f52f20fb9a3b811ea47314baac1034cd550e8ab363ae878ccba4b6329348f', getOpsxProposeSkillTemplate: '1aa2f2eb9c8cbc4dcab9d777bf8832b92ca04f9ef91d0494f1224a566aefdfe8', getOpsxProposeCommandTemplate: '3b7090ce5e79e879ab9b5bdaf4ff2b52e3c02211f71188838772d36ac337f96c', getFeedbackSkillTemplate: 'dabeb5e825b9349abc8156c3e7b8608f27987912a6d9bf47ef29addde6138133', - getUpdateChangeSkillTemplate: '90c49d56fbc4feb67983d18064aa6b60909a9f81d5b7f6ff192f877d6b7a6b6c', - getOpsxUpdateCommandTemplate: 'aa86facbb5354ba7316e0a708bae40d5ab87553be37ef2c220a832130cd73e0c', + getUpdateChangeSkillTemplate: '8380139769cf9b247cb64089c07628e73923405fca4704052d52a67f35526fe8', + getOpsxUpdateCommandTemplate: 'ec6c8b7f3f366d65a216c4ba423bc41b8dbd18974a432186ad44e60c1891340b', }; const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { - 'openspec-explore': '8b02eb77ae87a4374a43ad33930e5ea19cbaf1c8a7624d5b613c03d7ee1c5f14', + 'openspec-explore': '7d80caf9cd25a2565ba190b1297f1631c7f2c2db5e614597b4284abc0118ea70', 'openspec-new-change': '27e09d43785953827efc9a98bb9d6cf06db48fe6abe7e1c049409fe5b5061323', - 'openspec-continue-change': '182f015de6a1a114c79a6106c0565fd71f368d629641d0ad088de54bd871b52f', + 'openspec-continue-change': '1f92fad53022270e96f8ea34de75f7c12c08225edd5a9e8f4e864b63b5ef79c5', 'openspec-apply-change': 'f3e92c229fab8d77df9f0a77dcb117cf46279b53a208d53aed89bfe0bab2ac09', 'openspec-ff-change': 'a7ab656d46f04d45dff0c8888df4a126a2e62288b7336f7445bce4d1715055f5', 'openspec-sync-specs': '3909936a236a21a9a6d5bf495f90b396b3b68fc9220d7b2c1894668653beb2e4', 'openspec-archive-change': '305a21a9c76a925055f3bdbaac504f208660ef6948d78f73928de166250609bf', - 'openspec-bulk-archive-change': '4bd638a50111d2ee3a667752a2355ed513f770695b137b93fc28848ca7bf60d2', + 'openspec-bulk-archive-change': '73f81fee0ac7808a123a6fc5b977dea2b15616f22e2da397d5826d41cf06a6a2', 'openspec-verify-change': 'ad8a3098bd27d852721687c47a12db7107ed8b8dfc7f071406bb19961652e7ee', 'openspec-onboard': '6993eff867d97d485e080078f9dfb80e968e242f3b17a924eeb077715fd548fa', 'openspec-propose': '66e3395adf9f2d93a09e8ef1d20e4efb010e5e8d4811f2d42a9316e4d1ca5a8b', - 'openspec-update-change': 'c53a8416550d1c2dd7b2131acddd3498b8eef5c10c86bc2bc21bb1cdecb891aa', + 'openspec-update-change': '5f4ea19aa732b33d87a2120ec393ee34578e70678d97e8c3bb10f988c00cb4d3', }; // Intentionally excludes getFeedbackSkillTemplate: this list only models templates From fe4837079d39a0fc465da612bfa062779acf7345 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Wed, 23 Sep 2026 11:43:34 -0500 Subject: [PATCH 9/9] test(archive-progress): resolve optional-workflow blocks before generating This change makes the bulk-archive surfaces conditional on the sync workflow being installed. #1795's task-progress test built them from the raw templates, which leaves the [[opsx:if-workflow ...]] markers in the text and makes skill generation throw. Build both surfaces through getSkillTemplates/getCommandTemplates, which resolve the blocks against an installed set, and name sync in that set so the assertions keep testing the wording they were written for. Co-Authored-By: Claude Opus 5 --- .../templates/archive-task-progress.test.ts | 33 +++++++++++++------ .../templates/skill-templates-parity.test.ts | 24 +++++++------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/test/core/templates/archive-task-progress.test.ts b/test/core/templates/archive-task-progress.test.ts index 9bd2df18ae..eefa1672ab 100644 --- a/test/core/templates/archive-task-progress.test.ts +++ b/test/core/templates/archive-task-progress.test.ts @@ -3,20 +3,33 @@ import { promises as fs } from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { - getArchiveChangeSkillTemplate, - getBulkArchiveChangeSkillTemplate, - getOpsxArchiveCommandTemplate, - getOpsxBulkArchiveCommandTemplate, -} from '../../../src/core/templates/skill-templates.js'; -import { generateSkillContent } from '../../../src/core/shared/skill-generation.js'; + generateSkillContent, + getCommandTemplates, + getSkillTemplates, +} from '../../../src/core/shared/skill-generation.js'; import { getGlobalDataDir, registerStore } from '../../../src/core/index.js'; import { runCLI } from '../../helpers/run-cli.js'; +// Go through getSkillTemplates/getCommandTemplates rather than the raw +// templates: these workflows carry optional-workflow blocks, and only these +// entry points resolve them against an installed set. Building from the raw +// template leaves `[[opsx:if-workflow ...]]` in the text, which skill +// generation rejects. The set names sync so the installed branch is chosen, +// which is the wording these assertions are about. +const WORKFLOWS = ['archive', 'bulk-archive', 'sync']; +const skill = (workflowId: string): string => + generateSkillContent( + getSkillTemplates(WORKFLOWS).find((entry) => entry.workflowId === workflowId)!.template, + 'test' + ); +const command = (id: string): string => + getCommandTemplates(WORKFLOWS).find((entry) => entry.id === id)!.template.content; + const surfaces = [ - ['archive skill', generateSkillContent(getArchiveChangeSkillTemplate(), 'test')], - ['archive command', getOpsxArchiveCommandTemplate().content], - ['bulk archive skill', generateSkillContent(getBulkArchiveChangeSkillTemplate(), 'test')], - ['bulk archive command', getOpsxBulkArchiveCommandTemplate().content], + ['archive skill', skill('archive')], + ['archive command', command('archive')], + ['bulk archive skill', skill('bulk-archive')], + ['bulk archive command', command('bulk-archive')], ] as const; describe('archive task discovery uses schema-resolved CLI progress', () => { diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index b394b30ca4..4157a5d9d0 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -76,46 +76,46 @@ function specDrivenTitles(): Record { } const EXPECTED_FUNCTION_HASHES: Record = { - getExploreSkillTemplate: 'b17a409b5634b5e48864a87f038f2111a74c2442288e9f4cb7a704f86b6d75e7', + getExploreSkillTemplate: 'c1fddb294758004936add586f5826694cb06175cff935b75fd3a8d92332332e6', getNewChangeSkillTemplate: '0e5035b7b42198afc430206a1dbc9579096650ef0813d85e837d5a6cd0b98a85', - getContinueChangeSkillTemplate: '550dc22bc8e0921b1ca5cef867379c4f370c5f1902c420bf9fa3bbfa75cea933', + getContinueChangeSkillTemplate: 'c2c8a0ba7f8c8fc7b174793832cd50f7c404eb8e1f7d49c47000993d621633b6', getApplyChangeSkillTemplate: '04ae407c97b5f9cb0cc15199fe877ccc7cd1eff78bfe10ad70c16a112b10a661', getFfChangeSkillTemplate: 'd091600476a815ba99f69b446bcd46af5bf73d1c2810215a0c6196937d019cf6', getSyncSpecsSkillTemplate: 'bc80fe9b07eaa289e5eb8a3ce65eb7df722a16d864e37283c678220712e4f230', getOnboardSkillTemplate: '84258a06c0ca88de708a23dd74e9a17efe11eff63a071b3864c781dcd5a0a4b7', - getOpsxExploreCommandTemplate: 'f6cf22825643281d653355745623a6c1a4566db46cc2f262d2282243c6d8169a', + getOpsxExploreCommandTemplate: '5d11f8ecb4c457140a3e874a8bf7aa72674e922e698c208832b1f34d3c617719', getOpsxNewCommandTemplate: '6d504fef1e0d4ced7c423f4cc9d9d2cee11b1a6224edf685e06a3f0757e0ebff', - getOpsxContinueCommandTemplate: 'ace5c9cc239c12b57dc86fd9a1c02a6ca467cb8e1245127340c07ab1b9d37c11', + getOpsxContinueCommandTemplate: '241c50f97d5d681412d456d6b982743c3a5babeb77017fc8099c418bcf0d92df', getOpsxApplyCommandTemplate: 'd70cecce3b7d1dd4dbd5fd1fc2bccb538f5e61f5b43d520e4beca896e3f9e6b3', getOpsxFfCommandTemplate: '743a7304c7efc84aa87f556154c034e1e0e561c276c51870a30ada58f33eb9af', getArchiveChangeSkillTemplate: '71715f9d5899498942af03e182e6d1ac2c95952dde967950c2a9161084a53a8b', - getBulkArchiveChangeSkillTemplate: '5997f8c5c33f8933b7b3e61a0e122222e27e36e0b713167c6254e0508d1b95bf', + getBulkArchiveChangeSkillTemplate: '2a6ec08fea0f942158b4abe9c8d1af9622038e0c4dc684e7c73dad2fb8379a54', getOpsxSyncCommandTemplate: '60550b7bb9829421656d6324a9e4c951bc912f48f88882d1a07ce7f78397a5e7', getVerifyChangeSkillTemplate: '2e069a277dac23818b13bb50b66e806ab405bc3b7f535400e1ebf81b84153699', getOpsxArchiveCommandTemplate: '3d2a330b46043fbb9f220831aa42ebbb62f411e9597b2bb491ad1ac1fa2d0873', getOpsxOnboardCommandTemplate: '0cf66e164c0e14c916c6d1ebb5d80ded07d7fb8e55d4eb34eba43e8ca9c28558', - getOpsxBulkArchiveCommandTemplate: '090913b517d62bf6dc093689ab44d1c1c00df22719b6824977c7cc092dbed38f', + getOpsxBulkArchiveCommandTemplate: '4e2e39c4d634074f4a1ed67f076d5c4d0ead8b998f4d75218c33cdc6173719be', getOpsxVerifyCommandTemplate: '938f52f20fb9a3b811ea47314baac1034cd550e8ab363ae878ccba4b6329348f', getOpsxProposeSkillTemplate: '1aa2f2eb9c8cbc4dcab9d777bf8832b92ca04f9ef91d0494f1224a566aefdfe8', getOpsxProposeCommandTemplate: '3b7090ce5e79e879ab9b5bdaf4ff2b52e3c02211f71188838772d36ac337f96c', getFeedbackSkillTemplate: 'dabeb5e825b9349abc8156c3e7b8608f27987912a6d9bf47ef29addde6138133', - getUpdateChangeSkillTemplate: '90c49d56fbc4feb67983d18064aa6b60909a9f81d5b7f6ff192f877d6b7a6b6c', - getOpsxUpdateCommandTemplate: 'aa86facbb5354ba7316e0a708bae40d5ab87553be37ef2c220a832130cd73e0c', + getUpdateChangeSkillTemplate: '8380139769cf9b247cb64089c07628e73923405fca4704052d52a67f35526fe8', + getOpsxUpdateCommandTemplate: 'ec6c8b7f3f366d65a216c4ba423bc41b8dbd18974a432186ad44e60c1891340b', }; const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { - 'openspec-explore': '8b02eb77ae87a4374a43ad33930e5ea19cbaf1c8a7624d5b613c03d7ee1c5f14', + 'openspec-explore': '7d80caf9cd25a2565ba190b1297f1631c7f2c2db5e614597b4284abc0118ea70', 'openspec-new-change': '27e09d43785953827efc9a98bb9d6cf06db48fe6abe7e1c049409fe5b5061323', - 'openspec-continue-change': '182f015de6a1a114c79a6106c0565fd71f368d629641d0ad088de54bd871b52f', + 'openspec-continue-change': '1f92fad53022270e96f8ea34de75f7c12c08225edd5a9e8f4e864b63b5ef79c5', 'openspec-apply-change': 'f3e92c229fab8d77df9f0a77dcb117cf46279b53a208d53aed89bfe0bab2ac09', 'openspec-ff-change': 'a7ab656d46f04d45dff0c8888df4a126a2e62288b7336f7445bce4d1715055f5', 'openspec-sync-specs': '3909936a236a21a9a6d5bf495f90b396b3b68fc9220d7b2c1894668653beb2e4', 'openspec-archive-change': 'd01d9eeb06223ee89708b7963e82c5ebc11719c5b2dc62d4abb268ee016fcb7b', - 'openspec-bulk-archive-change': '8018999e6444bdfe97b3b95ffdb5451fb06becf2cfb46bd8fd3277157bdde5d7', + 'openspec-bulk-archive-change': '10f050ad5ef77084dc55a202427988b23903ee122f00985238a4eb9354a5dc3c', 'openspec-verify-change': 'ad8a3098bd27d852721687c47a12db7107ed8b8dfc7f071406bb19961652e7ee', 'openspec-onboard': '6993eff867d97d485e080078f9dfb80e968e242f3b17a924eeb077715fd548fa', 'openspec-propose': '66e3395adf9f2d93a09e8ef1d20e4efb010e5e8d4811f2d42a9316e4d1ca5a8b', - 'openspec-update-change': 'c53a8416550d1c2dd7b2131acddd3498b8eef5c10c86bc2bc21bb1cdecb891aa', + 'openspec-update-change': '5f4ea19aa732b33d87a2120ec393ee34578e70678d97e8c3bb10f988c00cb4d3', }; // Intentionally excludes getFeedbackSkillTemplate: this list only models templates