From d50b630b59732cf7b0834af224e47a4213e4cbb8 Mon Sep 17 00:00:00 2001 From: mc856 Date: Sun, 19 Jul 2026 12:25:52 +0800 Subject: [PATCH 1/3] fix(templates): stop instructing a second date prefix on dated archive names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The archive-change and bulk-archive-change workflow templates told agents to unconditionally build the archive target as YYYY-MM-DD-, so a change already named with the common YYYY-MM-DD- convention came out double-dated — the template-side twin of the CLI bug fixed in #1316, which a CLI fix cannot reach because the behavior is baked into instruction text. The generate-target-name step and the bulk guardrail now mirror the CLI rule: use the change name as-is when it already starts with a YYYY-MM-DD- prefix, otherwise prepend the current date. The literal mv commands move to so an agent copying them verbatim cannot stack dates, and the onboarding walkthrough's archived-path example carries the same caveat. Regenerated skills/ and updated the pinned parity hashes; a new parity test guards the caveat and rejects the raw stacked mv target. --- .changeset/fix-template-archive-date-dedup.md | 7 ++++++ skills/openspec-archive-change/SKILL.md | 4 ++-- skills/openspec-bulk-archive-change/SKILL.md | 7 ++++-- skills/openspec-onboard/SKILL.md | 2 +- .../templates/workflows/archive-change.ts | 8 +++---- .../workflows/bulk-archive-change.ts | 14 +++++++---- src/core/templates/workflows/onboard.ts | 2 +- .../templates/skill-templates-parity.test.ts | 24 +++++++++++++++++-- 8 files changed, 52 insertions(+), 16 deletions(-) create mode 100644 .changeset/fix-template-archive-date-dedup.md diff --git a/.changeset/fix-template-archive-date-dedup.md b/.changeset/fix-template-archive-date-dedup.md new file mode 100644 index 0000000000..866661fd3b --- /dev/null +++ b/.changeset/fix-template-archive-date-dedup.md @@ -0,0 +1,7 @@ +--- +"@fission-ai/openspec": patch +--- + +### Bug Fixes + +- **Archive workflow templates no longer teach agents to stack a second date prefix** — the `openspec-archive-change` and `openspec-bulk-archive-change` skill/command templates (and the onboarding walkthrough's archived-path example) now mirror the `openspec archive` rule: a change whose name already starts with a `YYYY-MM-DD-` prefix is archived under its own name, while other names get the current date prepended as before. Previously an agent following the workflow instructions on a change named `2026-07-04-voice-copilot-v1` produced `archive/2026-07-07-2026-07-04-voice-copilot-v1`, whatever the CLI did. diff --git a/skills/openspec-archive-change/SKILL.md b/skills/openspec-archive-change/SKILL.md index e198c0099c..2546aec4e6 100644 --- a/skills/openspec-archive-change/SKILL.md +++ b/skills/openspec-archive-change/SKILL.md @@ -89,14 +89,14 @@ Archive a completed change in the experimental workflow. mkdir -p "/archive" ``` - Generate target name using current date: `YYYY-MM-DD-` + Generate the target name: use the change name as-is when it already starts with a `YYYY-MM-DD-` prefix; otherwise prepend the current date as `YYYY-MM-DD-`. Never stack a second date (same rule as `openspec archive`). **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - If no: Move `changeRoot` to the archive directory ```bash - mv "" "/archive/YYYY-MM-DD-" + mv "" "/archive/" ``` 6. **Display summary** diff --git a/skills/openspec-bulk-archive-change/SKILL.md b/skills/openspec-bulk-archive-change/SKILL.md index 6076216ec9..3a97f06cff 100644 --- a/skills/openspec-bulk-archive-change/SKILL.md +++ b/skills/openspec-bulk-archive-change/SKILL.md @@ -128,9 +128,12 @@ This skill allows you to batch-archive changes, handling spec conflicts intellig - Track if sync was done b. **Perform the archive**: + + Target name: use the change name as-is when it already starts with a `YYYY-MM-DD-` prefix; otherwise prepend the current date as `YYYY-MM-DD-` (same rule as `openspec archive`). + ```bash mkdir -p "/archive" - mv "" "/archive/YYYY-MM-DD-" + mv "" "/archive/" ``` c. **Track outcome** for each change: @@ -244,5 +247,5 @@ No active changes found. Create a new change to get started. - Use single confirmation for entire batch - Track and report all outcomes (success/skip/fail) - Preserve .openspec.yaml when moving to archive -- Archive directory target uses current date: YYYY-MM-DD- +- Archive directory target uses current date: 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 diff --git a/skills/openspec-onboard/SKILL.md b/skills/openspec-onboard/SKILL.md index a06f0fd26b..d4c4b0de3e 100644 --- a/skills/openspec-onboard/SKILL.md +++ b/skills/openspec-onboard/SKILL.md @@ -437,7 +437,7 @@ openspec archive "" **SHOW:** ``` -Archived to: `/archive/YYYY-MM-DD-/` +Archived to: `/archive/YYYY-MM-DD-/` (a name that already starts with a `YYYY-MM-DD-` prefix is kept as-is — no second date) The change is now part of your project's history. The code is in your codebase, the decision record is preserved. ``` diff --git a/src/core/templates/workflows/archive-change.ts b/src/core/templates/workflows/archive-change.ts index 564a7fe0c7..b4db01bd10 100644 --- a/src/core/templates/workflows/archive-change.ts +++ b/src/core/templates/workflows/archive-change.ts @@ -91,14 +91,14 @@ ${STORE_SELECTION_GUIDANCE} mkdir -p "/archive" \`\`\` - Generate target name using current date: \`YYYY-MM-DD-\` + Generate the target name: use the change name as-is when it already starts with a \`YYYY-MM-DD-\` prefix; otherwise prepend the current date as \`YYYY-MM-DD-\`. Never stack a second date (same rule as \`openspec archive\`). **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - If no: Move \`changeRoot\` to the archive directory \`\`\`bash - mv "" "/archive/YYYY-MM-DD-" + mv "" "/archive/" \`\`\` 6. **Display summary** @@ -224,14 +224,14 @@ ${STORE_SELECTION_GUIDANCE} mkdir -p "/archive" \`\`\` - Generate target name using current date: \`YYYY-MM-DD-\` + Generate the target name: use the change name as-is when it already starts with a \`YYYY-MM-DD-\` prefix; otherwise prepend the current date as \`YYYY-MM-DD-\`. Never stack a second date (same rule as \`openspec archive\`). **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - If no: Move \`changeRoot\` to the archive directory \`\`\`bash - mv "" "/archive/YYYY-MM-DD-" + mv "" "/archive/" \`\`\` 6. **Display summary** diff --git a/src/core/templates/workflows/bulk-archive-change.ts b/src/core/templates/workflows/bulk-archive-change.ts index 607796818f..85b63bd683 100644 --- a/src/core/templates/workflows/bulk-archive-change.ts +++ b/src/core/templates/workflows/bulk-archive-change.ts @@ -130,9 +130,12 @@ ${STORE_SELECTION_GUIDANCE} - Track if sync was done b. **Perform the archive**: + + Target name: use the change name as-is when it already starts with a \`YYYY-MM-DD-\` prefix; otherwise prepend the current date as \`YYYY-MM-DD-\` (same rule as \`openspec archive\`). + \`\`\`bash mkdir -p "/archive" - mv "" "/archive/YYYY-MM-DD-" + mv "" "/archive/" \`\`\` c. **Track outcome** for each change: @@ -246,7 +249,7 @@ No active changes found. Create a new change to get started. - Use single confirmation for entire batch - Track and report all outcomes (success/skip/fail) - Preserve .openspec.yaml when moving to archive -- Archive directory target uses current date: YYYY-MM-DD- +- Archive directory target uses current date: 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`, license: 'MIT', compatibility: 'Requires openspec CLI.', @@ -379,9 +382,12 @@ ${STORE_SELECTION_GUIDANCE} - Track if sync was done b. **Perform the archive**: + + Target name: use the change name as-is when it already starts with a \`YYYY-MM-DD-\` prefix; otherwise prepend the current date as \`YYYY-MM-DD-\` (same rule as \`openspec archive\`). + \`\`\`bash mkdir -p "/archive" - mv "" "/archive/YYYY-MM-DD-" + mv "" "/archive/" \`\`\` c. **Track outcome** for each change: @@ -495,7 +501,7 @@ No active changes found. Create a new change to get started. - Use single confirmation for entire batch - Track and report all outcomes (success/skip/fail) - Preserve .openspec.yaml when moving to archive -- Archive directory target uses current date: YYYY-MM-DD- +- Archive directory target uses current date: 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` }; } diff --git a/src/core/templates/workflows/onboard.ts b/src/core/templates/workflows/onboard.ts index 96f1b943bc..c3791d07bd 100644 --- a/src/core/templates/workflows/onboard.ts +++ b/src/core/templates/workflows/onboard.ts @@ -447,7 +447,7 @@ openspec archive "" **SHOW:** \`\`\` -Archived to: \`/archive/YYYY-MM-DD-/\` +Archived to: \`/archive/YYYY-MM-DD-/\` (a name that already starts with a \`YYYY-MM-DD-\` prefix is kept as-is — no second date) The change is now part of your project's history. The code is in your codebase, the decision record is preserved. \`\`\` diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 0d8b783852..004c7e4c31 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -43,7 +43,7 @@ const EXPECTED_FUNCTION_HASHES: Record = { getApplyChangeSkillTemplate: '0f5a15fc7fb9ad6059a5643d0e01365d27642637a4aaebf182f9eabb45348197', getFfChangeSkillTemplate: '20ebb682ba89809a100cd4985c074908df5bada2bd649ca1b0f4059a63a1c728', getSyncSpecsSkillTemplate: 'dc07ea0312687f3edc602329c889dbbab737c6d79327eb7a723553d346b43433', - getOnboardSkillTemplate: 'e871d8ce172bb805ae62a7611aee7a3154d89414f427ad5ef31721c903f13002', + getOnboardSkillTemplate: '95240ade743024dcf4da6e0f384dee43cd246ba4bf3808330a656eed7d4ad7fa', getOpsxExploreCommandTemplate: '37e53590aae7ac6621d4393aa80a5b8af21881323887fa924ed329199fda27e0', getOpsxNewCommandTemplate: '57c600cce318d16b9b4308a18d0d983ea3c0673034e606a7cceec07b4c705e87', getOpsxContinueCommandTemplate: 'f63964fab7720ede097aa48808baff196c391b962930ca960459205c724800e5', @@ -74,7 +74,7 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { 'openspec-archive-change': '4679a077d34016bf38f0d0aa5432b53ea83ae82c2c5fec6dcb7dc15571ee8ac6', 'openspec-bulk-archive-change': '545b9528df52fbb0b4898405b42a2ce10416678d469d20cf597d022fa6e16e3b', 'openspec-verify-change': '57693d22940f06080c6cf8d590ac2f48240d4a5e9ce7074dacd0f8d3c9945afa', - 'openspec-onboard': 'b1b6fc9a1b3ff64dafe9b8c39a761ee1bd001b542d47b4e4deaf058e0aa21256', + 'openspec-onboard': '405b8f8b779847898eafaba6fcba72db83de44c91e08d60ecaaea42a91febacc', 'openspec-propose': '024db4bce28d9a4d7b25fa92525da6fc701a64ac07dfdcf777d286c95b5281b5', 'openspec-update-change': '77ff4d1f1cd08a57649cce1f25e0ebc4f55d6d032dfde5c301d1b479561b72fa', }; @@ -238,4 +238,24 @@ describe('skill templates split parity', () => { expect(content, variant).toContain('not only the ones the sync reports it touched'); } }); + + // The archive instructions must mirror `openspec archive`'s date-prefix + // rule (#1316): a change already named with a `YYYY-MM-DD-` prefix keeps + // its name, so archived names never stack dates. Guard both the caveat + // and the literal `mv` target an agent would copy verbatim (#1317). + it('never instructs stacking a date prefix on an already-dated change (#1317)', () => { + const archiveInstructions: Array<[string, string]> = [ + ['openspec-archive-change', getArchiveChangeSkillTemplate().instructions], + ['openspec-bulk-archive-change', getBulkArchiveChangeSkillTemplate().instructions], + ['openspec-onboard', getOnboardSkillTemplate().instructions], + ['opsx-archive', getOpsxArchiveCommandTemplate().content], + ['opsx-bulk-archive', getOpsxBulkArchiveCommandTemplate().content], + ['opsx-onboard', getOpsxOnboardCommandTemplate().content], + ]; + + for (const [id, text] of archiveInstructions) { + expect(text, id).toContain('already starts with a `YYYY-MM-DD-` prefix'); + expect(text, id).not.toContain('archive/YYYY-MM-DD-"'); + } + }); }); From 076827d5b5d74c70e9f7f9c82861b75cf97c8eae Mon Sep 17 00:00:00 2001 From: mc856 Date: Tue, 21 Jul 2026 11:54:24 +0800 Subject: [PATCH 2/3] fix(templates): report the derived archive name in success summaries The success and failure summaries still printed archive/YYYY-MM-DD-, so an agent copying them would report a stacked date for a change whose name already carries a YYYY-MM-DD- prefix. Point those examples at instead, and widen the regression guard from the mv target to any date used as a path segment, which leaves the rule statements that must keep explaining the derivation untouched. The opsx-archive-skill spec still specified the unconditional current-date rule the previous commit removed from the template, so bring it in line with the wording cli-archive already carries. --- openspec/specs/opsx-archive-skill/spec.md | 2 +- skills/openspec-archive-change/SKILL.md | 2 +- skills/openspec-bulk-archive-change/SKILL.md | 6 ++-- skills/openspec-onboard/SKILL.md | 2 +- .../templates/workflows/archive-change.ts | 10 +++--- .../workflows/bulk-archive-change.ts | 12 +++---- src/core/templates/workflows/onboard.ts | 2 +- .../templates/skill-templates-parity.test.ts | 33 ++++++++++++------- 8 files changed, 39 insertions(+), 30 deletions(-) diff --git a/openspec/specs/opsx-archive-skill/spec.md b/openspec/specs/opsx-archive-skill/spec.md index 95ba9dc2d0..325132326e 100644 --- a/openspec/specs/opsx-archive-skill/spec.md +++ b/openspec/specs/opsx-archive-skill/spec.md @@ -94,7 +94,7 @@ The skill SHALL move the change to the archive folder with date prefix. - **WHEN** archiving a change - **THEN** create `archive/` directory if it doesn't exist -- **AND** generate target name as `YYYY-MM-DD-` using current date +- **AND** generate target name as `YYYY-MM-DD-` using current date, keeping the name as-is when it already starts with a `YYYY-MM-DD-` prefix - **AND** move entire change directory to archive location - **AND** preserve `.openspec.yaml` file in archived change diff --git a/skills/openspec-archive-change/SKILL.md b/skills/openspec-archive-change/SKILL.md index 2546aec4e6..b531e87641 100644 --- a/skills/openspec-archive-change/SKILL.md +++ b/skills/openspec-archive-change/SKILL.md @@ -115,7 +115,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`// **Specs:** <"✓ Synced to main specs" only if the step 4 verification passed; otherwise "No delta specs" or "Sync skipped"> <"All artifacts complete. All tasks complete." — or, if archived with warnings, list them instead (e.g. "Archived with 2 incomplete tasks")> diff --git a/skills/openspec-bulk-archive-change/SKILL.md b/skills/openspec-bulk-archive-change/SKILL.md index 3a97f06cff..8ef031b931 100644 --- a/skills/openspec-bulk-archive-change/SKILL.md +++ b/skills/openspec-bulk-archive-change/SKILL.md @@ -206,8 +206,8 @@ then add-graphql specs (chronological order, newer takes precedence). ## Bulk Archive Complete Archived N changes: -- -> archive/YYYY-MM-DD-/ -- -> archive/YYYY-MM-DD-/ +- -> archive// +- -> archive// Spec sync summary: - N delta specs synced to main specs @@ -220,7 +220,7 @@ Spec sync summary: ## Bulk Archive Complete (partial) Archived N changes: -- -> archive/YYYY-MM-DD-/ +- -> archive// Skipped M changes: - (user chose not to archive incomplete) diff --git a/skills/openspec-onboard/SKILL.md b/skills/openspec-onboard/SKILL.md index d4c4b0de3e..7722c3414d 100644 --- a/skills/openspec-onboard/SKILL.md +++ b/skills/openspec-onboard/SKILL.md @@ -437,7 +437,7 @@ openspec archive "" **SHOW:** ``` -Archived to: `/archive/YYYY-MM-DD-/` (a name that already starts with a `YYYY-MM-DD-` prefix is kept as-is — no second date) +Archived to: `/archive//` (the target name prepends today's date, unless the name already starts with a `YYYY-MM-DD-` prefix — then it is kept as-is, no second date) The change is now part of your project's history. The code is in your codebase, the decision record is preserved. ``` diff --git a/src/core/templates/workflows/archive-change.ts b/src/core/templates/workflows/archive-change.ts index b4db01bd10..d0c1e6fd14 100644 --- a/src/core/templates/workflows/archive-change.ts +++ b/src/core/templates/workflows/archive-change.ts @@ -117,7 +117,7 @@ ${STORE_SELECTION_GUIDANCE} **Change:** **Schema:** -**Archived to:** the archive path derived from \`planningHome.changesDir\`/YYYY-MM-DD-/ +**Archived to:** the archive path derived from \`planningHome.changesDir\`// **Specs:** <"✓ Synced to main specs" only if the step 4 verification passed; otherwise "No delta specs" or "Sync skipped"> <"All artifacts complete. All tasks complete." — or, if archived with warnings, list them instead (e.g. "Archived with 2 incomplete tasks")> @@ -250,7 +250,7 @@ ${STORE_SELECTION_GUIDANCE} **Change:** **Schema:** -**Archived to:** the archive path derived from \`planningHome.changesDir\`/YYYY-MM-DD-/ +**Archived to:** the archive path derived from \`planningHome.changesDir\`// **Specs:** ✓ Synced to main specs All artifacts complete. All tasks complete. @@ -263,7 +263,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** the archive path derived from \`planningHome.changesDir\`/YYYY-MM-DD-/ +**Archived to:** the archive path derived from \`planningHome.changesDir\`// **Specs:** No delta specs All artifacts complete. All tasks complete. @@ -276,7 +276,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** the archive path derived from \`planningHome.changesDir\`/YYYY-MM-DD-/ +**Archived to:** the archive path derived from \`planningHome.changesDir\`// **Specs:** Sync skipped (user chose to skip) **Warnings:** @@ -293,7 +293,7 @@ Review the archive if this was not intentional. ## Archive Failed **Change:** -**Target:** the archive path derived from \`planningHome.changesDir\`/YYYY-MM-DD-/ +**Target:** the archive path derived from \`planningHome.changesDir\`// Target archive directory already exists. diff --git a/src/core/templates/workflows/bulk-archive-change.ts b/src/core/templates/workflows/bulk-archive-change.ts index 85b63bd683..3acc0b2add 100644 --- a/src/core/templates/workflows/bulk-archive-change.ts +++ b/src/core/templates/workflows/bulk-archive-change.ts @@ -208,8 +208,8 @@ then add-graphql specs (chronological order, newer takes precedence). ## Bulk Archive Complete Archived N changes: -- -> archive/YYYY-MM-DD-/ -- -> archive/YYYY-MM-DD-/ +- -> archive// +- -> archive// Spec sync summary: - N delta specs synced to main specs @@ -222,7 +222,7 @@ Spec sync summary: ## Bulk Archive Complete (partial) Archived N changes: -- -> archive/YYYY-MM-DD-/ +- -> archive// Skipped M changes: - (user chose not to archive incomplete) @@ -460,8 +460,8 @@ then add-graphql specs (chronological order, newer takes precedence). ## Bulk Archive Complete Archived N changes: -- -> archive/YYYY-MM-DD-/ -- -> archive/YYYY-MM-DD-/ +- -> archive// +- -> archive// Spec sync summary: - N delta specs synced to main specs @@ -474,7 +474,7 @@ Spec sync summary: ## Bulk Archive Complete (partial) Archived N changes: -- -> archive/YYYY-MM-DD-/ +- -> archive// Skipped M changes: - (user chose not to archive incomplete) diff --git a/src/core/templates/workflows/onboard.ts b/src/core/templates/workflows/onboard.ts index c3791d07bd..d175b08322 100644 --- a/src/core/templates/workflows/onboard.ts +++ b/src/core/templates/workflows/onboard.ts @@ -447,7 +447,7 @@ openspec archive "" **SHOW:** \`\`\` -Archived to: \`/archive/YYYY-MM-DD-/\` (a name that already starts with a \`YYYY-MM-DD-\` prefix is kept as-is — no second date) +Archived to: \`/archive//\` (the target name prepends today's date, unless the name already starts with a \`YYYY-MM-DD-\` prefix — then it is kept as-is, no second date) The change is now part of your project's history. The code is in your codebase, the decision record is preserved. \`\`\` diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 004c7e4c31..ec3f5ca6aa 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -43,19 +43,19 @@ const EXPECTED_FUNCTION_HASHES: Record = { getApplyChangeSkillTemplate: '0f5a15fc7fb9ad6059a5643d0e01365d27642637a4aaebf182f9eabb45348197', getFfChangeSkillTemplate: '20ebb682ba89809a100cd4985c074908df5bada2bd649ca1b0f4059a63a1c728', getSyncSpecsSkillTemplate: 'dc07ea0312687f3edc602329c889dbbab737c6d79327eb7a723553d346b43433', - getOnboardSkillTemplate: '95240ade743024dcf4da6e0f384dee43cd246ba4bf3808330a656eed7d4ad7fa', + getOnboardSkillTemplate: 'bc2216b72724b01c3a733e63b8bf4aff457f561c0e9ff7288bdacc39780a37a7', getOpsxExploreCommandTemplate: '37e53590aae7ac6621d4393aa80a5b8af21881323887fa924ed329199fda27e0', getOpsxNewCommandTemplate: '57c600cce318d16b9b4308a18d0d983ea3c0673034e606a7cceec07b4c705e87', getOpsxContinueCommandTemplate: 'f63964fab7720ede097aa48808baff196c391b962930ca960459205c724800e5', getOpsxApplyCommandTemplate: 'daeb507206707169de73c828e199648dde5732cbc17791ef2a027adffd028574', getOpsxFfCommandTemplate: 'b859b1955cda6012877ae7f9ec6980e468f2e949a3838dfcdebc17209d133749', - getArchiveChangeSkillTemplate: 'a8f1d9cb06c20c7335ac35826dd09bfadead75ef6d624d359912734f74232cbc', - getBulkArchiveChangeSkillTemplate: 'f675122bce3ef583b245352abedecf50ff4043e45bea6bac091885f83c7b6362', + getArchiveChangeSkillTemplate: 'b04eccde2c57af4bc484fa7279fa873ad1d46474eb024467d68e784d8b985c18', + getBulkArchiveChangeSkillTemplate: 'f31d17602c274a3fc24d688fb368156618cd31e07762a267d2c506c63b4b4760', getOpsxSyncCommandTemplate: '98b20e00da5c588ff83ed6e6f0e959dfc540349090fb3f5792ea030d099b8169', getVerifyChangeSkillTemplate: 'cab4db01b5d2b1243d63d90c53747d8b39e488c60f76eba3fe8b994467f69267', - getOpsxArchiveCommandTemplate: '9d14e1ea23ae8be8971fafa1d6a4d4717a8a7b922b6e76c6fb07aa568a420632', - getOpsxOnboardCommandTemplate: '0673f34a0f81fd173bcfb8c3ac83e2b1c617f7b7564e24e5298d3bd5665a05a9', - getOpsxBulkArchiveCommandTemplate: 'd0d84040bcbd44e89ac525bb21100bee7befb3604e51095bfa65b8453d85290c', + getOpsxArchiveCommandTemplate: '8c113e2a8bca36fecd0e2152ae262fbfbef508e81378838e15d31308fb069b57', + getOpsxOnboardCommandTemplate: '9430a0fb6530791ab720e068f4b172bc3dfc4e96a1ae29102bee0b92c2afe7b5', + getOpsxBulkArchiveCommandTemplate: '22dde4864ec494eee774a46fe5c0c6a68f4ca9ff67272c3177a5d4f5c2be07b7', getOpsxVerifyCommandTemplate: 'f01c0c0cef53be0956de52363d955d4ace131b1b2d77adf902f35fead9a1486d', getOpsxProposeSkillTemplate: '59197064a46c53264b62925a1c725af4ebe7caf9f0eaed4101990b7c13a40db1', getOpsxProposeCommandTemplate: '04f808a36e850b9cdbc4f943ef324a9fd2b1b0cc59b92f127ab6cc452d66cc4e', @@ -71,10 +71,10 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { 'openspec-apply-change': '09c0e1cdf5ccc82416d0969d6bd715cc70616bdbc3531358a5c36057f78be55a', 'openspec-ff-change': '0c82830cd9bc98f86eb56b63ddaabe2bf5d35fe25b6c40a7059311aee2c8acac', 'openspec-sync-specs': 'b3f694ab81956d05126b089fe82dea78dec21788978bb9651485f996aee96740', - 'openspec-archive-change': '4679a077d34016bf38f0d0aa5432b53ea83ae82c2c5fec6dcb7dc15571ee8ac6', - 'openspec-bulk-archive-change': '545b9528df52fbb0b4898405b42a2ce10416678d469d20cf597d022fa6e16e3b', + 'openspec-archive-change': 'b24d326662ef58809de4464960440713748b9a281323357facdca24af52014e7', + 'openspec-bulk-archive-change': '98c682899a6fd4c83e71b790b27d6d4ccf832e51c0e754119537992a469c75ec', 'openspec-verify-change': '57693d22940f06080c6cf8d590ac2f48240d4a5e9ce7074dacd0f8d3c9945afa', - 'openspec-onboard': '405b8f8b779847898eafaba6fcba72db83de44c91e08d60ecaaea42a91febacc', + 'openspec-onboard': '76225d10352454a304e56566997811d16f91de1b37653816f2bc5d8ec976febc', 'openspec-propose': '024db4bce28d9a4d7b25fa92525da6fc701a64ac07dfdcf777d286c95b5281b5', 'openspec-update-change': '77ff4d1f1cd08a57649cce1f25e0ebc4f55d6d032dfde5c301d1b479561b72fa', }; @@ -241,8 +241,9 @@ describe('skill templates split parity', () => { // The archive instructions must mirror `openspec archive`'s date-prefix // rule (#1316): a change already named with a `YYYY-MM-DD-` prefix keeps - // its name, so archived names never stack dates. Guard both the caveat - // and the literal `mv` target an agent would copy verbatim (#1317). + // its name, so archived names never stack dates. Guard the caveat, the + // literal `mv` target, and the success-summary examples an agent would + // copy verbatim (#1317). it('never instructs stacking a date prefix on an already-dated change (#1317)', () => { const archiveInstructions: Array<[string, string]> = [ ['openspec-archive-change', getArchiveChangeSkillTemplate().instructions], @@ -255,7 +256,15 @@ describe('skill templates split parity', () => { for (const [id, text] of archiveInstructions) { expect(text, id).toContain('already starts with a `YYYY-MM-DD-` prefix'); - expect(text, id).not.toContain('archive/YYYY-MM-DD-"'); + + // Every archive path an agent reproduces must name the derived target, + // never a hardcoded date. + expect(text, id).toContain(''); + + // Discriminator: a `YYYY-MM-DD-` after a path separator belongs to a + // literal archive path the agent copies verbatim. The rule statements + // only name the prefix, never place it in a path, so they stay legal. + expect(text, id).not.toMatch(/\/YYYY-MM-DD-/); } }); }); From 53dfe1064fc1bc039893cc6f2225deff0f2f9492 Mon Sep 17 00:00:00 2001 From: mc856 Date: Tue, 21 Jul 2026 20:44:53 +0800 Subject: [PATCH 3/3] fix(specs): name the derived target in the archive scenario The successful-archive scenario still spelled the destination as archive/YYYY-MM-DD-/, the same literal form this PR removed from the templates, so it contradicted the keep-as-is rule the behavior requirements now carry. --- openspec/specs/opsx-archive-skill/spec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openspec/specs/opsx-archive-skill/spec.md b/openspec/specs/opsx-archive-skill/spec.md index 325132326e..a6256b37b7 100644 --- a/openspec/specs/opsx-archive-skill/spec.md +++ b/openspec/specs/opsx-archive-skill/spec.md @@ -15,7 +15,7 @@ The system SHALL provide an `/opsx:archive` skill that archives completed change - **WHEN** agent executes `/opsx:archive` with a change name - **AND** all artifacts in the schema are complete - **AND** all tasks are complete -- **THEN** the agent moves the change to `openspec/changes/archive/YYYY-MM-DD-/` +- **THEN** the agent moves the change to `openspec/changes/archive//` - **AND** displays success message with archived location #### Scenario: Change selection prompt