Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ For workflow patterns and when to use each command, see [Workflows](workflows.md
| `/opsx:new` | Start a new change scaffold |
| `/opsx:continue` | Create the next artifact based on dependencies |
| `/opsx:ff` | Fast-forward: create all planning artifacts at once |
| `/opsx:refine` | Review artifact quality and cross-artifact consistency |
| `/opsx:verify` | Validate implementation matches artifacts |
| `/opsx:sync` | Merge delta specs into main specs |
| `/opsx:bulk-archive` | Archive multiple changes at once |
Expand Down Expand Up @@ -315,6 +316,64 @@ AI: Implementing add-dark-mode...

---

### `/opsx:refine`

Review and refine change artifacts for cross-artifact consistency, best-practice alignment, and implementation readiness. Uses a scratchpad file to track issues per change.

**Syntax:**
```text
/opsx:refine [change-name]
```

**Arguments:**

| Argument | Required | Description |
|----------|----------|-------------|
| `change-name` | No | Which change to refine (prompted if not provided) |

Comment on lines +328 to +333
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix markdownlint MD058 in the refine arguments table.

Add blank lines around the table under **Arguments:** (Line 329 area) to satisfy lint.

✏️ Proposed docs fix
 **Arguments:**
+
 | Argument | Required | Description |
 |----------|----------|-------------|
 | `change-name` | No | Which change to refine (prompted if not provided) |
+
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
**Arguments:**
| Argument | Required | Description |
|----------|----------|-------------|
| `change-name` | No | Which change to refine (prompted if not provided) |
**Arguments:**
| Argument | Required | Description |
|----------|----------|-------------|
| `change-name` | No | Which change to refine (prompted if not provided) |
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 329-329: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/commands.md` around lines 328 - 332, The Markdown table under the
"**Arguments:**" heading (the table that documents the `change-name` argument)
needs blank lines before and after it to satisfy markdownlint MD058; edit the
section containing the "**Arguments:**" heading and ensure there is an empty
line between the heading and the table and another empty line after the table so
the table is separated from surrounding text.

**What it does:**
- Cross-checks artifacts against each other (proposal ↔ specs ↔ design ↔ tasks)
- Validates best practices (acceptance criteria, measurable scenarios, rationale)
- Creates a prioritized scratchpad (`scratchpad.md`) in the change directory
- Walks through issues one at a time for user review
- Commits scratchpad + artifact updates together for convergence traceability

**Example:**
```text
You: /opsx:refine add-dark-mode

AI: Refining add-dark-mode...

Loading artifacts: proposal.md, specs/, design.md, tasks.md

Created scratchpad.md with 5 issues:
- P0(1): Spec requires "system preference detection" but design has no mention
- P1(1): Task 1.3 has no acceptance criteria
- P1(2): design.md says CSS variables, tasks reference Tailwind
- P2(1): No error state for localStorage unavailable
- P2(2): Scenario "first visit" lacks Given/When/Then

Starting with P0(1)...

Spec requirement "System Preference Detection" (specs/ui/spec.md:42)
has no corresponding section in design.md.

Proposed fix: Add "System Preference Detection" section to design.md
with prefers-color-scheme media query approach.

Apply this fix?
```

**Tips:**
- Run after `/opsx:propose` or `/opsx:ff` and before `/opsx:apply`
- The scratchpad persists across sessions — resume where you left off
- Committing the scratchpad with each fix creates a history of how specs converged
- Works with partial artifacts (graceful degradation)

**Prior art:** Inspired by [@demianmnave's nhc-opsx-refine command](https://github.com/NaveHaus/NhcAgentConfig/blob/main/.opencode/command/nhc-opsx-refine.md) ([#783](https://github.com/Fission-AI/OpenSpec/issues/783)).

---

### `/opsx:verify`

Validate that implementation matches your change artifacts. Checks completeness, correctness, and coherence.
Expand Down
1 change: 1 addition & 0 deletions src/core/profile-sync-drift.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const WORKFLOW_TO_SKILL_DIR: Record<WorkflowId, string> = {
'archive': 'openspec-archive-change',
'bulk-archive': 'openspec-bulk-archive-change',
'verify': 'openspec-verify-change',
'refine': 'openspec-refine-change',
'onboard': 'openspec-onboard',
'propose': 'openspec-propose',
};
Expand Down
1 change: 1 addition & 0 deletions src/core/profiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const ALL_WORKFLOWS = [
'archive',
'bulk-archive',
'verify',
'refine',
'onboard',
] as const;

Expand Down
4 changes: 4 additions & 0 deletions src/core/shared/skill-generation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
getVerifyChangeSkillTemplate,
getOnboardSkillTemplate,
getOpsxProposeSkillTemplate,
getRefineChangeSkillTemplate,
getOpsxRefineCommandTemplate,
getOpsxExploreCommandTemplate,
getOpsxNewCommandTemplate,
getOpsxContinueCommandTemplate,
Expand Down Expand Up @@ -66,6 +68,7 @@ export function getSkillTemplates(workflowFilter?: readonly string[]): SkillTemp
{ template: getVerifyChangeSkillTemplate(), dirName: 'openspec-verify-change', workflowId: 'verify' },
{ template: getOnboardSkillTemplate(), dirName: 'openspec-onboard', workflowId: 'onboard' },
{ template: getOpsxProposeSkillTemplate(), dirName: 'openspec-propose', workflowId: 'propose' },
{ template: getRefineChangeSkillTemplate(), dirName: 'openspec-refine-change', workflowId: 'refine' },
];

if (!workflowFilter) return all;
Expand All @@ -92,6 +95,7 @@ export function getCommandTemplates(workflowFilter?: readonly string[]): Command
{ template: getOpsxVerifyCommandTemplate(), id: 'verify' },
{ template: getOpsxOnboardCommandTemplate(), id: 'onboard' },
{ template: getOpsxProposeCommandTemplate(), id: 'propose' },
{ template: getOpsxRefineCommandTemplate(), id: 'refine' },
];

if (!workflowFilter) return all;
Expand Down
1 change: 1 addition & 0 deletions src/core/templates/skill-templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ export { getBulkArchiveChangeSkillTemplate, getOpsxBulkArchiveCommandTemplate }
export { getVerifyChangeSkillTemplate, getOpsxVerifyCommandTemplate } from './workflows/verify-change.js';
export { getOnboardSkillTemplate, getOpsxOnboardCommandTemplate } from './workflows/onboard.js';
export { getOpsxProposeSkillTemplate, getOpsxProposeCommandTemplate } from './workflows/propose.js';
export { getRefineChangeSkillTemplate, getOpsxRefineCommandTemplate } from './workflows/refine-change.js';
export { getFeedbackSkillTemplate } from './workflows/feedback.js';
163 changes: 163 additions & 0 deletions src/core/templates/workflows/refine-change.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/**
* Skill Template Workflow Modules
*
* This file is generated by splitting the legacy monolithic
* templates file into workflow-focused modules.
*/
import type { SkillTemplate, CommandTemplate } from '../types.js';

const SKILL_INPUT_LINE = 'Optionally specify a change name (e.g., `/opsx:refine add-auth`).';
const COMMAND_INPUT_LINE = 'The argument after `/opsx:refine` is the change name (e.g., `/opsx:refine add-auth`).';

function buildInstructions(inputLine: string): string {
return `Review and refine change artifacts for cross-artifact consistency, best-practice alignment, and implementation readiness. Tracks issues in a scratchpad file scoped to the change.

**Input**: ${inputLine}

**Steps**

1. **If no change name provided, prompt for selection**

Run \`openspec list --json\` to get available changes. Present the list and ask the user which change to refine.

Show only active changes (not already archived).
Include the schema used for each change if available.

**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.

2. **Load change artifacts**
\`\`\`bash
openspec status --change "<name>" --json
\`\`\`
Read all existing artifacts (proposal.md, design.md, tasks.md, specs/) from the change directory.

3. **Create the scratchpad**

Create \`openspec/changes/<change-name>/scratchpad.md\` using the format in [Scratchpad Format](#scratchpad-format).

**IMPORTANT**: The scratchpad MUST be used to track issues and working decisions throughout the refine process.

4. **Cross-artifact consistency checks**

Compare artifacts against each other for contradictions, gaps, and ambiguities:

- **Proposal → Specs**: Does every goal in the proposal have corresponding requirements in specs?
- **Specs → Design**: Does the design address all requirements and scenarios?
- **Design → Tasks**: Do tasks cover all design decisions? Are implementation steps aligned?
- **Specs → Tasks**: Are all requirements traceable to at least one task?

Record each issue found in the scratchpad with priority:
- **P0**: Contradiction between artifacts (e.g., spec says X, design says Y)
- **P1**: Gap in coverage (e.g., requirement with no corresponding task)
- **P2**: Ambiguity or unclear intent (e.g., vague acceptance criteria)

5. **Best-practice validation**

Check artifacts against common quality standards:

- Tasks have clear acceptance criteria
- Specs define measurable scenarios (Given/When/Then or equivalent)
- Design decisions include rationale
- No circular or missing dependencies in task ordering
- Edge cases and error states are addressed

Add findings to the scratchpad.
Comment on lines +40 to +64
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add an explicit duplication/reuse review against existing specs/components.

Cross-checks currently stay within change artifacts. The workflow should also inspect existing specs/shared components to flag reimplementation risk and suggest reuse vs duplication.

♻️ Proposed check to add
 5. **Best-practice validation**
 
    Check artifacts against common quality standards:
@@
    - Edge cases and error states are addressed
+   - Existing `openspec/specs/` and shared components are reviewed to detect duplication
+   - Prefer reuse/refactor recommendations over reimplementation where feasible
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
4. **Cross-artifact consistency checks**
Compare artifacts against each other for contradictions, gaps, and ambiguities:
- **Proposal Specs**: Does every goal in the proposal have corresponding requirements in specs?
- **Specs Design**: Does the design address all requirements and scenarios?
- **Design Tasks**: Do tasks cover all design decisions? Are implementation steps aligned?
- **Specs Tasks**: Are all requirements traceable to at least one task?
Record each issue found in the scratchpad with priority:
- **P0**: Contradiction between artifacts (e.g., spec says X, design says Y)
- **P1**: Gap in coverage (e.g., requirement with no corresponding task)
- **P2**: Ambiguity or unclear intent (e.g., vague acceptance criteria)
5. **Best-practice validation**
Check artifacts against common quality standards:
- Tasks have clear acceptance criteria
- Specs define measurable scenarios (Given/When/Then or equivalent)
- Design decisions include rationale
- No circular or missing dependencies in task ordering
- Edge cases and error states are addressed
Add findings to the scratchpad.
4. **Cross-artifact consistency checks**
Compare artifacts against each other for contradictions, gaps, and ambiguities:
- **Proposal Specs**: Does every goal in the proposal have corresponding requirements in specs?
- **Specs Design**: Does the design address all requirements and scenarios?
- **Design Tasks**: Do tasks cover all design decisions? Are implementation steps aligned?
- **Specs Tasks**: Are all requirements traceable to at least one task?
Record each issue found in the scratchpad with priority:
- **P0**: Contradiction between artifacts (e.g., spec says X, design says Y)
- **P1**: Gap in coverage (e.g., requirement with no corresponding task)
- **P2**: Ambiguity or unclear intent (e.g., vague acceptance criteria)
5. **Best-practice validation**
Check artifacts against common quality standards:
- Tasks have clear acceptance criteria
- Specs define measurable scenarios (Given/When/Then or equivalent)
- Design decisions include rationale
- No circular or missing dependencies in task ordering
- Edge cases and error states are addressed
- Existing `openspec/specs/` and shared components are reviewed to detect duplication
- Prefer reuse/refactor recommendations over reimplementation where feasible
Add findings to the scratchpad.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/templates/workflows/refine-change.ts` around lines 36 - 60, Update
the "Cross-artifact consistency checks" step (step 4) in the refine-change
workflow to include an explicit duplication/reuse review against the existing
codebase and shared specs/components: add checks that compare current change
artifacts (Proposal, Specs, Design, Tasks) against existing specs/components to
detect potential reimplementations, flag reuse opportunities, and record a
recommended action (reuse/extend/duplicate) and risk level in the scratchpad;
ensure this new check is implemented alongside the existing Proposal→Specs,
Specs→Design, Design→Tasks, and Specs→Tasks comparisons so findings are appended
with priority (P0/P1/P2) and a reuse/duplication tag.


6. **Address issues one at a time**

For each issue in priority order (P0 first):
- Present the issue to the user with context
- Propose a fix (artifact update)
- Apply the fix after user approval
- Validate the updated artifacts:
\`\`\`bash
openspec validate --change "<name>" --strict
\`\`\`
If validation fails, mark the issue as "Needs refinement" in the scratchpad, keep changes uncommitted, and ask the user for direction.
- If validation passes, update the scratchpad status and **commit the scratchpad and updated artifacts together**

**IMPORTANT**: Address one issue at a time so the user can review each change.

7. **Final status**

When all issues are resolved (or the user decides to stop):
\`\`\`bash
openspec status --change "<name>"
\`\`\`

Show summary: how many issues were found, resolved, and remaining.
If issues remain, note them as open in the scratchpad for future sessions.

**Scratchpad Rules**

- Issue status MUST reflect the state of the openspec artifacts (not implementation status)
- Keep "Last updated" date current
- List issues in priority order: P0, P1, P2
- Use format \`P<L>(i)\` where L is level and i is index (e.g., P0(1), P1(2))
- The scratchpad is scoped per-change and persists across refine sessions
- Commit the scratchpad with artifact updates to preserve convergence history

**Scratchpad Format**

\`\`\`markdown
## <change-name> Refinement Scratchpad

Tracks openspec-refine issues and working decisions for the \`<change-name>\` change.
This is a working document, not a spec artifact.

Last updated: YYYY-MM-DD

### Status Legend
- **Open**: Not yet captured consistently in OpenSpec artifacts
- **Needs refinement**: Partially captured; artifacts still need work
- **Consistent**: Artifacts are aligned with current intended behavior

### Key References
- (List any external references used to resolve issues)

### Current Working Constraints / Decisions
- (List constraints or decisions affecting the current issue)

### Issue List

#### P0(1): <title>
- **Status**: <status>
- **Notes**: <clarify the status>
- **Artifacts touched**:
- \`openspec/changes/<change-name>/...\`

#### P1(1): <title>
...

### Open Questions
- (Questions needing user clarification)
\`\`\`

**Graceful Degradation**

- If only proposal.md exists: check for completeness and clarity, skip cross-artifact checks
- If proposal + specs exist: check proposal-spec consistency, skip design/task checks
- If full artifacts: run all checks
- Always note which checks were skipped and why`;
}

export function getRefineChangeSkillTemplate(): SkillTemplate {
return {
name: 'openspec-refine-change',
description: 'Refine change artifacts for cross-artifact consistency, best-practice alignment, and implementation readiness. Use when the user wants to review artifact quality before implementation.',
instructions: buildInstructions(SKILL_INPUT_LINE),
license: 'MIT',
compatibility: 'Requires openspec CLI.',
metadata: { author: 'openspec', version: '1.0' },
};
}

export function getOpsxRefineCommandTemplate(): CommandTemplate {
return {
name: 'OPSX: Refine',
description: 'Refine change artifacts for consistency and quality before implementation',
category: 'Workflow',
tags: ['workflow', 'refine', 'quality'],
content: buildInstructions(COMMAND_INPUT_LINE),
};
}
6 changes: 3 additions & 3 deletions test/core/profiles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ describe('profiles', () => {
});

describe('ALL_WORKFLOWS', () => {
it('should contain all 11 workflows', () => {
expect(ALL_WORKFLOWS).toHaveLength(11);
it('should contain all 12 workflows', () => {
expect(ALL_WORKFLOWS).toHaveLength(12);
});

it('should contain expected workflow IDs', () => {
const expected = [
'propose', 'explore', 'new', 'continue', 'apply',
'ff', 'sync', 'archive', 'bulk-archive', 'verify', 'onboard',
'ff', 'sync', 'archive', 'bulk-archive', 'verify', 'refine', 'onboard',
];
expect([...ALL_WORKFLOWS]).toEqual(expected);
});
Comment on lines 27 to 33
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify 'refine' is in ALL_WORKFLOWS and whether prompt metadata keys match workflow IDs.
python - <<'PY'
import re
from pathlib import Path

profiles = Path("src/core/profiles.ts").read_text(encoding="utf-8")
config = Path("src/commands/config.ts").read_text(encoding="utf-8")

arr = re.search(r'ALL_WORKFLOWS\s*=\s*\[([^\]]+)\]', profiles, re.S)
workflows = re.findall(r"'([^']+)'", arr.group(1)) if arr else []

obj = re.search(r'WORKFLOW_PROMPT_META\s*:\s*Record<[^>]+>\s*=\s*\{(.*?)\n\};', config, re.S)
keys = set()
if obj:
    body = obj.group(1)
    keys.update(re.findall(r"^\s*'([^']+)'\s*:", body, re.M))
    keys.update(re.findall(r'^\s*([A-Za-z][\w-]*)\s*:', body, re.M))

missing = sorted(set(workflows) - keys)
extra = sorted(keys - set(workflows))

print("ALL_WORKFLOWS:", workflows)
print("WORKFLOW_PROMPT_META keys:", sorted(keys))
print("Missing in WORKFLOW_PROMPT_META:", missing)
print("Extra keys not in ALL_WORKFLOWS:", extra)

# Expected: both lists empty.
PY

Repository: Fission-AI/OpenSpec

Length of output: 464


Add a guard for workflow prompt metadata parity with ALL_WORKFLOWS.

This test asserts 'refine' in ALL_WORKFLOWS, but metadata is out of sync: 'refine' is missing from WORKFLOW_PROMPT_META keys. A workflow in ALL_WORKFLOWS without prompt metadata cannot be selected in the UI. Add a test that verifies all workflows in ALL_WORKFLOWS have corresponding entries in WORKFLOW_PROMPT_META.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/core/profiles.test.ts` around lines 27 - 33, Add a parity guard test to
ensure every workflow id in ALL_WORKFLOWS has corresponding prompt metadata in
WORKFLOW_PROMPT_META: update the test suite in profiles.test.ts to iterate
ALL_WORKFLOWS and assert each id exists as a key in WORKFLOW_PROMPT_META (e.g.,
compare ALL_WORKFLOWS against Object.keys(WORKFLOW_PROMPT_META) or assert every
id satisfies id in WORKFLOW_PROMPT_META), so missing entries like 'refine' will
fail the test and force metadata sync.

Expand Down
14 changes: 8 additions & 6 deletions test/core/shared/skill-generation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import {

describe('skill-generation', () => {
describe('getSkillTemplates', () => {
it('should return all 11 skill templates', () => {
it('should return all 12 skill templates', () => {
const templates = getSkillTemplates();
expect(templates).toHaveLength(11);
expect(templates).toHaveLength(12);
});

it('should have unique directory names', () => {
Expand All @@ -35,6 +35,7 @@ describe('skill-generation', () => {
expect(dirNames).toContain('openspec-verify-change');
expect(dirNames).toContain('openspec-onboard');
expect(dirNames).toContain('openspec-propose');
expect(dirNames).toContain('openspec-refine-change');
});

it('should have valid template structure', () => {
Expand Down Expand Up @@ -88,9 +89,9 @@ describe('skill-generation', () => {
});

describe('getCommandTemplates', () => {
it('should return all 11 command templates', () => {
it('should return all 12 command templates', () => {
const templates = getCommandTemplates();
expect(templates).toHaveLength(11);
expect(templates).toHaveLength(12);
});

it('should have unique IDs', () => {
Expand All @@ -115,6 +116,7 @@ describe('skill-generation', () => {
expect(ids).toContain('verify');
expect(ids).toContain('onboard');
expect(ids).toContain('propose');
expect(ids).toContain('refine');
});

it('should filter by workflow IDs when provided', () => {
Expand Down Expand Up @@ -142,9 +144,9 @@ describe('skill-generation', () => {
});

describe('getCommandContents', () => {
it('should return all 11 command contents', () => {
it('should return all 12 command contents', () => {
const contents = getCommandContents();
expect(contents).toHaveLength(11);
expect(contents).toHaveLength(12);
});

it('should have valid content structure', () => {
Expand Down
8 changes: 8 additions & 0 deletions test/core/templates/skill-templates-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import {
getOpsxSyncCommandTemplate,
getOpsxProposeCommandTemplate,
getOpsxProposeSkillTemplate,
getOpsxRefineCommandTemplate,
getOpsxVerifyCommandTemplate,
getRefineChangeSkillTemplate,
getSyncSpecsSkillTemplate,
getVerifyChangeSkillTemplate,
} from '../../../src/core/templates/skill-templates.js';
Expand Down Expand Up @@ -52,6 +54,8 @@ const EXPECTED_FUNCTION_HASHES: Record<string, string> = {
getOpsxVerifyCommandTemplate: '9b4d3ca422553b7534764eb3a009da87a051612c5238e9baab294c7b1233e9a2',
getOpsxProposeSkillTemplate: 'd67f937d44650e9c61d2158c865309fbab23cb3f50a3d4868a640a97776e3999',
getOpsxProposeCommandTemplate: '41ad59b37eafd7a161bab5c6e41997a37368f9c90b194451295ede5cd42e4d46',
getRefineChangeSkillTemplate: '61d79ed4d9673ea032d80d3ec88c617062433ff1a3ee39b1ee8b422ddc834e5b',
getOpsxRefineCommandTemplate: 'da0b28a867314bce78f2302ad11f64b8806687c97f554656969291a71fb9b45e',
getFeedbackSkillTemplate: 'd7d83c5f7fc2b92fe8f4588a5bf2d9cb315e4c73ec19bcd5ef28270906319a0d',
};

Expand All @@ -67,6 +71,7 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record<string, string> = {
'openspec-verify-change': '30d07c6f7051965f624f5964db51844ec17c7dfd05f0da95281fe0ca73616326',
'openspec-onboard': 'dbce376cf895f3fe4f63b4bce66d258c35b7b8884ac746670e5e35fabcefd255',
'openspec-propose': '20e36dabefb90e232bad0667292bd5007ec280f8fc4fc995dbc4282bf45a22e7',
'openspec-refine-change': 'cc84efb5d418561868bca76ac7d51cbd6f1f3a93a02746db9550c9d52db9f8dc',
};

function stableStringify(value: unknown): string {
Expand Down Expand Up @@ -114,6 +119,8 @@ describe('skill templates split parity', () => {
getOpsxVerifyCommandTemplate,
getOpsxProposeSkillTemplate,
getOpsxProposeCommandTemplate,
getRefineChangeSkillTemplate,
getOpsxRefineCommandTemplate,
getFeedbackSkillTemplate,
};

Expand All @@ -139,6 +146,7 @@ describe('skill templates split parity', () => {
['openspec-verify-change', getVerifyChangeSkillTemplate],
['openspec-onboard', getOnboardSkillTemplate],
['openspec-propose', getOpsxProposeSkillTemplate],
['openspec-refine-change', getRefineChangeSkillTemplate],
];

const actualHashes = Object.fromEntries(
Expand Down