mirror of
https://github.com/Fission-AI/OpenSpec.git
synced 2026-10-02 05:24:34 +08:00
fix(templates): deduplicate apply skill and command instructions (#1153)
* fix(templates): deduplicate apply skill and command instructions Extract shared APPLY_INSTRUCTIONS constant so skill and command templates reference the same string. Eliminates content drift reported in #1139. * fix(templates): update parity hashes after parameterizing apply instructions * test(templates): add normalized body parity assertion for apply skill vs command * docs(templates): add JSDoc to getApplyInstructions * docs(templates): add JSDoc to all functions in apply-change * fix(templates): parameterize /opsx:apply examples and add regression tests for skill /opsx: references --------- Co-authored-by: Clay Good <hi@claygood.com>
This commit is contained in:
co-authored by
Clay Good
parent
7a4a745d80
commit
0b233efb86
@@ -13,7 +13,7 @@ Implement tasks from an OpenSpec change.
|
||||
|
||||
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`, `view`). Once selected, treat `--store <id>` as sticky for the rest of the workflow. Every unscoped example of those commands below is shorthand: before running it, append the flag. For example, run `openspec status --change "<name>" --json --store "<id>"`, not the unscoped form shown below. Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
|
||||
|
||||
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
**Input**: Optionally specify a change name (e.g., `/openspec-apply-change add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
|
||||
**Steps**
|
||||
|
||||
@@ -42,7 +42,7 @@ Implement tasks from an OpenSpec change.
|
||||
```
|
||||
|
||||
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 (varies by schema)
|
||||
- Progress (total, complete, remaining)
|
||||
- Task list with status
|
||||
- Dynamic instruction based on current state
|
||||
@@ -50,7 +50,7 @@ Implement tasks from an OpenSpec change.
|
||||
- Optional `operationGuidance`: current advisory guidance for apply
|
||||
|
||||
**Handle states:**
|
||||
- If `state: "blocked"` (missing artifacts): show message, suggest using openspec-continue-change (if it is not installed, run `openspec status --change "<name>" --json` to see the next artifact and `openspec instructions <artifact-id> --change "<name>" --json` for how to create it)
|
||||
- If `state: "blocked"` (missing artifacts): show message, suggest using `/openspec-continue-change` (if it is not installed, run `openspec status --change "<name>" --json` to see the next artifact and `openspec instructions <artifact-id> --change "<name>" --json` for how to create it)
|
||||
- If `state: "all_done"`: congratulate, suggest archive
|
||||
- Otherwise: proceed to implementation
|
||||
|
||||
@@ -138,7 +138,7 @@ Working on task 4/7: <task description>
|
||||
- [x] Task 2
|
||||
...
|
||||
|
||||
All tasks complete! Ready to archive this change.
|
||||
All tasks complete! You can archive this change with `/openspec-archive-change`.
|
||||
```
|
||||
|
||||
**Output On Pause (Issue Encountered)**
|
||||
|
||||
@@ -7,196 +7,8 @@
|
||||
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.
|
||||
|
||||
${STORE_SELECTION_GUIDANCE}
|
||||
|
||||
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
|
||||
**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: <name>" and how to override (e.g., \`/opsx:apply <other>\`).
|
||||
|
||||
2. **Check status to understand the schema**
|
||||
\`\`\`bash
|
||||
openspec status --change "<name>" --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 "<name>" --json
|
||||
\`\`\`
|
||||
|
||||
This returns:
|
||||
- \`contextFiles\`: artifact ID -> array of concrete file paths (varies by schema - could be proposal/specs/design/tasks or spec/tests/implementation/docs)
|
||||
- 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 openspec-continue-change (if it is not installed, run \`openspec status --change "<name>" --json\` to see the next artifact and \`openspec instructions <artifact-id> --change "<name>" --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: <change-name> (schema: <schema-name>)
|
||||
|
||||
Working on task 3/7: <task description>
|
||||
[...implementation happening...]
|
||||
✓ Task complete
|
||||
|
||||
Working on task 4/7: <task description>
|
||||
[...implementation happening...]
|
||||
✓ Task complete
|
||||
\`\`\`
|
||||
|
||||
**Output On Completion**
|
||||
|
||||
\`\`\`
|
||||
## Implementation Complete
|
||||
|
||||
**Change:** <change-name>
|
||||
**Schema:** <schema-name>
|
||||
**Progress:** 7/7 tasks complete ✓
|
||||
|
||||
### Completed This Session
|
||||
- [x] Task 1
|
||||
- [x] Task 2
|
||||
...
|
||||
|
||||
All tasks complete! Ready to archive this change.
|
||||
\`\`\`
|
||||
|
||||
**Output On Pause (Issue Encountered)**
|
||||
|
||||
\`\`\`
|
||||
## Implementation Paused
|
||||
|
||||
**Change:** <change-name>
|
||||
**Schema:** <schema-name>
|
||||
**Progress:** 4/7 tasks complete
|
||||
|
||||
### Issue Encountered
|
||||
<description of the issue>
|
||||
|
||||
**Options:**
|
||||
1. <option 1>
|
||||
2. <option 2>
|
||||
3. Other approach
|
||||
|
||||
What would you like to do?
|
||||
\`\`\`
|
||||
|
||||
**Guardrails**
|
||||
- Keep going through tasks until done or blocked
|
||||
- Always read context files before starting (from the apply instructions output)
|
||||
- If task is ambiguous, pause and ask before implementing
|
||||
- If implementation reveals issues, pause and suggest artifact updates
|
||||
- Keep code changes minimal and scoped to each task
|
||||
- Update task checkbox immediately after completing each task
|
||||
- Pause on errors, blockers, or unclear requirements - don't guess
|
||||
- Use contextFiles from CLI output, don't assume specific file names
|
||||
- Do not use context or operation guidance as proof that a task is complete
|
||||
- Apply relevant project context; report conflicts with controlling workflow inputs
|
||||
- Consider every guidance entry; explain any inapplicable or conflicting advice
|
||||
- Do not copy runtime context or operation guidance into implementation files or planning artifacts
|
||||
- Preserve CLI-controlled blocked/ready/all-done behavior and completion criteria
|
||||
|
||||
**Fluid Workflow Integration**
|
||||
|
||||
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`,
|
||||
license: 'MIT',
|
||||
compatibility: 'Requires openspec CLI.',
|
||||
metadata: { author: 'openspec', version: '1.0' },
|
||||
};
|
||||
}
|
||||
|
||||
export function getOpsxApplyCommandTemplate(): CommandTemplate {
|
||||
return {
|
||||
name: 'OPSX: Apply',
|
||||
description: 'Implement tasks from an OpenSpec change (Experimental)',
|
||||
category: 'Workflow',
|
||||
tags: ['workflow', 'artifacts', 'experimental'],
|
||||
content: `Implement tasks from an OpenSpec change.
|
||||
function getApplyInstructions(): string {
|
||||
return `Implement tasks from an OpenSpec change.
|
||||
|
||||
${STORE_SELECTION_GUIDANCE}
|
||||
|
||||
@@ -368,6 +180,26 @@ 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`;
|
||||
}
|
||||
|
||||
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(),
|
||||
license: 'MIT',
|
||||
compatibility: 'Requires openspec CLI.',
|
||||
metadata: { author: 'openspec', version: '1.0' },
|
||||
};
|
||||
}
|
||||
|
||||
export function getOpsxApplyCommandTemplate(): CommandTemplate {
|
||||
return {
|
||||
name: 'OPSX: Apply',
|
||||
description: 'Implement tasks from an OpenSpec change (Experimental)',
|
||||
category: 'Workflow',
|
||||
tags: ['workflow', 'artifacts', 'experimental'],
|
||||
content: getApplyInstructions(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ const EXPECTED_FUNCTION_HASHES: Record<string, string> = {
|
||||
getExploreSkillTemplate: 'fec38ba01c5c20695aca0ec7eff78c26e278ead21459cab8ec1562af51053427',
|
||||
getNewChangeSkillTemplate: '935f6335e2d4b7d1bd4f0538c88386350c25e8b16e11b627556262229583ca51',
|
||||
getContinueChangeSkillTemplate: 'ed41e2356af7aad6ef760f60fad19c6843cefe436d8f90084dcba4dbc6bf7272',
|
||||
getApplyChangeSkillTemplate: 'e5fc093637d3100a61acf934553002a5e9f5bccab5110136d7680af4133f7351',
|
||||
getApplyChangeSkillTemplate: '18b19aec04e95cd4cce694a64cf84ac6a0fa522b69ace00390a55bb78df46778',
|
||||
getFfChangeSkillTemplate: 'fc2a45a08533ee9c7ab30fdab5f832b7d440070048e2a153f03db1620dc379bb',
|
||||
getSyncSpecsSkillTemplate: 'd43b112a3c74bc951b094d220c8e75cca26bb00640d404b78af0752af1ff7bd9',
|
||||
getOnboardSkillTemplate: 'a9f6134b187ec4f3a5aa6c7c181e51a15fec11b7ac1044a076fdfe79b47fbc80',
|
||||
@@ -68,7 +68,7 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record<string, string> = {
|
||||
'openspec-explore': '80109dec3abf1505ab1037f7196baac4fcdf175ca954411e8d439e5da881bf62',
|
||||
'openspec-new-change': '579d432771703f947a331a6ed288bf9c6660ca015fcd376d76f19b6ac7683082',
|
||||
'openspec-continue-change': '5c34be8194cdb4c5158335e47aece71143e8a22bfb4179dba47fd8aaf436d395',
|
||||
'openspec-apply-change': '1726319cd4305a47f9c827acaeb84a9de57f7e44aba9ed60869c1758338e18ae',
|
||||
'openspec-apply-change': '919db34873151b8a573fcb38631fd79a0b1256da1677b7608b2d8c2475227893',
|
||||
'openspec-ff-change': '19315644df7c582d920acfb67f3c500ca4e06fccc900265b3ac39621d85f7cdb',
|
||||
'openspec-sync-specs': '6e85521de10858bb020885eb657aa843e5746b2f09c846aa44545694f456cda9',
|
||||
'openspec-archive-change': '019d580a13eee5892cc9233a899919b572a3abfc6a05c1f0aabf9c4ba9bf3d4d',
|
||||
@@ -117,6 +117,12 @@ function hash(value: string): string {
|
||||
}
|
||||
|
||||
describe('skill templates split parity', () => {
|
||||
it('uses one canonical instruction body for apply skills and commands', () => {
|
||||
expect(getApplyChangeSkillTemplate().instructions).toBe(
|
||||
getOpsxApplyCommandTemplate().content
|
||||
);
|
||||
});
|
||||
|
||||
it('preserves all template function payloads exactly', () => {
|
||||
const functionFactories: Record<string, () => unknown> = {
|
||||
getExploreSkillTemplate,
|
||||
|
||||
Reference in New Issue
Block a user