From efc5f3e40d57d1230b56c2d4d64ee267203bd458 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Wed, 5 Aug 2026 09:35:20 -0500 Subject: [PATCH 1/2] refactor(templates): share one apply instruction body across skill and command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The apply skill and command templates each carried a full ~150-line copy of the same instruction body, differing in exactly one line (the `contextFiles` note). Two near-identical copies invite silent drift. Author the body once in `getApplyInstructions(contextFilesNote)` and render it per surface, passing each surface's own note. The single intentional wording difference stays explicit as a named constant, and further per-surface parameters can be added here as the surfaces evolve — the skill and command remain distinct templates. Pure refactor: the generated skill and command output is byte-identical to before (SKILL.md and all parity hashes unchanged). Added a contract test that fails both if the shared body drifts between surfaces and if the intentional contextFiles difference is flattened away. Co-Authored-By: Claude Opus 4.8 --- src/core/templates/workflows/apply-change.ts | 206 +++--------------- .../templates/skill-templates-parity.test.ts | 25 +++ 2 files changed, 51 insertions(+), 180 deletions(-) diff --git a/src/core/templates/workflows/apply-change.ts b/src/core/templates/workflows/apply-change.ts index 393e83e237..e532ea9ab7 100644 --- a/src/core/templates/workflows/apply-change.ts +++ b/src/core/templates/workflows/apply-change.ts @@ -7,11 +7,15 @@ import type { SkillTemplate, CommandTemplate } from '../types.js'; import { STORE_SELECTION_GUIDANCE } from './store-selection.js'; -export function getApplyChangeSkillTemplate(): SkillTemplate { - return { - name: 'openspec-apply-change', - description: 'Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks.', - instructions: `Implement tasks from an OpenSpec change. +/** + * The apply workflow instructions are shared by the skill and command + * surfaces and authored once here, so the two outputs cannot silently drift. + * Each surface renders this body with its own `contextFiles` note — the only + * intentional wording difference between them — and can add further per-surface + * parameters here as the surfaces evolve. + */ +function getApplyInstructions(contextFilesNote: string): string { + return `Implement tasks from an OpenSpec change. ${STORE_SELECTION_GUIDANCE} @@ -44,7 +48,7 @@ ${STORE_SELECTION_GUIDANCE} \`\`\` This returns: - - \`contextFiles\`: artifact ID -> array of concrete file paths (varies by schema - could be proposal/specs/design/tasks or spec/tests/implementation/docs) + - \`contextFiles\`: artifact ID -> array of concrete file paths (${contextFilesNote}) - Progress (total, complete, remaining) - Task list with status - Dynamic instruction based on current state @@ -183,7 +187,21 @@ What would you like to do? This skill supports the "actions on a change" model: - **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions -- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly`, +- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly`; +} + +/** Skill surface spells out example artifact sets in the contextFiles note. */ +const APPLY_SKILL_CONTEXT_FILES_NOTE = + 'varies by schema - could be proposal/specs/design/tasks or spec/tests/implementation/docs'; + +/** Command surface keeps the contextFiles note terse. */ +const APPLY_COMMAND_CONTEXT_FILES_NOTE = 'varies by schema'; + +export function getApplyChangeSkillTemplate(): SkillTemplate { + return { + name: 'openspec-apply-change', + description: 'Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks.', + instructions: getApplyInstructions(APPLY_SKILL_CONTEXT_FILES_NOTE), license: 'MIT', compatibility: 'Requires openspec CLI.', metadata: { author: 'openspec', version: '1.0' }, @@ -196,178 +214,6 @@ export function getOpsxApplyCommandTemplate(): CommandTemplate { description: 'Implement tasks from an OpenSpec change (Experimental)', category: 'Workflow', tags: ['workflow', 'artifacts', 'experimental'], - content: `Implement tasks from an OpenSpec change. - -${STORE_SELECTION_GUIDANCE} - -**Input**: Optionally specify a change name (e.g., \`/opsx:apply add-auth\`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. - -**Steps** - -1. **Select the change** - - If a name is provided, use it. Otherwise: - - Infer from conversation context if the user mentioned a change - - Auto-select if only one active change exists - - If ambiguous, run \`openspec list --json\` to get available changes and ask the user to select one - - Always announce: "Using change: " and how to override (e.g., \`/opsx:apply \`). - -2. **Check status to understand the schema** - \`\`\`bash - openspec status --change "" --json - \`\`\` - Parse the JSON to understand: - - \`schemaName\`: The workflow being used (e.g., "spec-driven") - - \`planningHome\`, \`changeRoot\`, and \`actionContext\`: planning scope and edit constraints - - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) - -3. **Get apply instructions** - - \`\`\`bash - openspec instructions apply --change "" --json - \`\`\` - - This returns: - - \`contextFiles\`: artifact ID -> array of concrete file paths (varies by schema) - - Progress (total, complete, remaining) - - Task list with status - - Dynamic instruction based on current state - - Optional \`context\`: current required project instruction input from the selected root - - 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: "all_done"\`: congratulate, suggest archive - - Otherwise: proceed to implementation - - Treat \`context\` as a required prompt-level input. Read and consider it, and - apply relevant project facts, conventions, and constraints while implementing. - Treat \`operationGuidance\` as optional additive advice. Read and consider every - entry, and follow entries that are applicable and compatible with the built-in - workflow. - - Keep both fields separate from CLI-returned state, missing artifacts, tasks, - progress, \`contextFiles\`, and the built-in \`instruction\`. They are not - evidence of task completion, do not replace the built-in instruction, and do - not permit bypassing a blocked state. If context conflicts with the built-in - instruction, an explicit user choice, or a CLI-controlled value, report the - conflict and preserve the controlling value. If guidance is inapplicable or - conflicts with those controlling inputs, do not follow it and explain why. - These are prompt-level behavior contracts, not enforceable checks. - -4. **Read context files** - - Read every file path listed under \`contextFiles\` from the apply instructions output. - The files depend on the schema being used: - - **spec-driven**: proposal, specs, design, tasks - - Other schemas: follow the contextFiles from CLI output - - Do not copy \`context\` or \`operationGuidance\` verbatim into implementation - files or planning artifacts unless the user separately asks for that content. - -5. **Show current progress** - - Display: - - Schema being used - - Progress: "N/M tasks complete" - - Remaining tasks overview - - Dynamic instruction from CLI - -6. **Implement tasks (loop until done or blocked)** - - For each pending task: - - Show which task is being worked on - - Make the code changes required - - Keep changes minimal and focused - - Mark task complete in the tasks file: \`- [ ]\` → \`- [x]\` - - Continue to next task - - **Pause if:** - - Task is unclear → ask for clarification - - Implementation reveals a design issue → suggest updating artifacts - - Error or blocker encountered → report and wait for guidance - - User interrupts - -7. **On completion or pause, show status** - - Display: - - Tasks completed this session - - Overall progress: "N/M tasks complete" - - If all done: suggest archive - - If paused: explain why and wait for guidance - -**Output During Implementation** - -\`\`\` -## Implementing: (schema: ) - -Working on task 3/7: -[...implementation happening...] -✓ Task complete - -Working on task 4/7: -[...implementation happening...] -✓ Task complete -\`\`\` - -**Output On Completion** - -\`\`\` -## Implementation Complete - -**Change:** -**Schema:** -**Progress:** 7/7 tasks complete ✓ - -### Completed This Session -- [x] Task 1 -- [x] Task 2 -... - -All tasks complete! You can archive this change with \`/opsx:archive\`. -\`\`\` - -**Output On Pause (Issue Encountered)** - -\`\`\` -## Implementation Paused - -**Change:** -**Schema:** -**Progress:** 4/7 tasks complete - -### Issue Encountered - - -**Options:** -1.