From e39126b2cacefa99cc4335fafb3952631eef010b Mon Sep 17 00:00:00 2001 From: shohu Date: Tue, 31 Mar 2026 08:23:26 +0900 Subject: [PATCH 1/3] feat: add /opsx:refine command with scratchpad-based artifact quality review Add a new workflow command that reviews change artifacts for cross-artifact consistency, best-practice alignment, and implementation readiness. Issues are tracked in a per-change scratchpad file (scratchpad.md) that persists across sessions and gets committed with artifact updates for convergence traceability. Inspired by @demianmnave's nhc-opsx-refine command (#783). --- docs/commands.md | 58 +++++++ src/core/shared/skill-generation.ts | 4 + src/core/templates/skill-templates.ts | 1 + src/core/templates/workflows/refine-change.ts | 157 ++++++++++++++++++ test/core/shared/skill-generation.test.ts | 6 +- .../templates/skill-templates-parity.test.ts | 8 + 6 files changed, 231 insertions(+), 3 deletions(-) create mode 100644 src/core/templates/workflows/refine-change.ts diff --git a/docs/commands.md b/docs/commands.md index fd4bb7fe1..76e8e1c4d 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -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 | @@ -315,6 +316,63 @@ 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) | + +**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. diff --git a/src/core/shared/skill-generation.ts b/src/core/shared/skill-generation.ts index 898e7a25e..597ddc0cb 100644 --- a/src/core/shared/skill-generation.ts +++ b/src/core/shared/skill-generation.ts @@ -16,6 +16,8 @@ import { getVerifyChangeSkillTemplate, getOnboardSkillTemplate, getOpsxProposeSkillTemplate, + getRefineChangeSkillTemplate, + getOpsxRefineCommandTemplate, getOpsxExploreCommandTemplate, getOpsxNewCommandTemplate, getOpsxContinueCommandTemplate, @@ -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; @@ -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; diff --git a/src/core/templates/skill-templates.ts b/src/core/templates/skill-templates.ts index ff687d900..8e2690d95 100644 --- a/src/core/templates/skill-templates.ts +++ b/src/core/templates/skill-templates.ts @@ -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'; diff --git a/src/core/templates/workflows/refine-change.ts b/src/core/templates/workflows/refine-change.ts new file mode 100644 index 000000000..365cb7f48 --- /dev/null +++ b/src/core/templates/workflows/refine-change.ts @@ -0,0 +1,157 @@ +/** + * 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 refineInstructions = `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**: Optionally specify a change name (e.g., \`/opsx:refine add-auth\`). + +**Steps** + +1. **If no change name provided, prompt for selection** + + Run \`openspec list --json\` to get available changes. Use the **AskUserQuestion tool** to let the user select. + + 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 "" --json + \`\`\` + Read all existing artifacts (proposal.md, design.md, tasks.md, specs/) from the change directory. + +3. **Create the scratchpad** + + Create \`openspec/changes//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. + +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 + - Update the scratchpad status to reflect the artifact state + - **Commit the scratchpad and updated artifacts together** (if the user approves) + + **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 "" + \`\`\` + + 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(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 +## Refinement Scratchpad + +Tracks openspec-refine issues and working decisions for the \\\`\\\` 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): +- **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: refineInstructions, + 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: refineInstructions.replace( + 'Optionally specify a change name (e.g., `/opsx:refine add-auth`).', + 'The argument after `/opsx:refine` is the change name (e.g., `/opsx:refine add-auth`).', + ), + }; +} diff --git a/test/core/shared/skill-generation.test.ts b/test/core/shared/skill-generation.test.ts index 6c755f51d..8c9417c8f 100644 --- a/test/core/shared/skill-generation.test.ts +++ b/test/core/shared/skill-generation.test.ts @@ -10,7 +10,7 @@ describe('skill-generation', () => { describe('getSkillTemplates', () => { it('should return all 11 skill templates', () => { const templates = getSkillTemplates(); - expect(templates).toHaveLength(11); + expect(templates).toHaveLength(12); }); it('should have unique directory names', () => { @@ -90,7 +90,7 @@ describe('skill-generation', () => { describe('getCommandTemplates', () => { it('should return all 11 command templates', () => { const templates = getCommandTemplates(); - expect(templates).toHaveLength(11); + expect(templates).toHaveLength(12); }); it('should have unique IDs', () => { @@ -144,7 +144,7 @@ describe('skill-generation', () => { describe('getCommandContents', () => { it('should return all 11 command contents', () => { const contents = getCommandContents(); - expect(contents).toHaveLength(11); + expect(contents).toHaveLength(12); }); it('should have valid content structure', () => { diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index f8fb1307b..9a078de51 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -23,7 +23,9 @@ import { getOpsxSyncCommandTemplate, getOpsxProposeCommandTemplate, getOpsxProposeSkillTemplate, + getOpsxRefineCommandTemplate, getOpsxVerifyCommandTemplate, + getRefineChangeSkillTemplate, getSyncSpecsSkillTemplate, getVerifyChangeSkillTemplate, } from '../../../src/core/templates/skill-templates.js'; @@ -52,6 +54,8 @@ const EXPECTED_FUNCTION_HASHES: Record<string, string> = { getOpsxVerifyCommandTemplate: '9b4d3ca422553b7534764eb3a009da87a051612c5238e9baab294c7b1233e9a2', getOpsxProposeSkillTemplate: 'd67f937d44650e9c61d2158c865309fbab23cb3f50a3d4868a640a97776e3999', getOpsxProposeCommandTemplate: '41ad59b37eafd7a161bab5c6e41997a37368f9c90b194451295ede5cd42e4d46', + getRefineChangeSkillTemplate: '3d281e43d5a7ebd1abc88025e013e88ce9d705343cb4fce126dccd0af5302f78', + getOpsxRefineCommandTemplate: '19d2ee7e1d5dc003fb9caeda069fa634c3341206bcec1c094cbc773a2750a08f', getFeedbackSkillTemplate: 'd7d83c5f7fc2b92fe8f4588a5bf2d9cb315e4c73ec19bcd5ef28270906319a0d', }; @@ -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': 'e54f1be0306233e826d189f63cb18099fd7ff813f24aad2723b6f9dd9094d3a5', }; function stableStringify(value: unknown): string { @@ -114,6 +119,8 @@ describe('skill templates split parity', () => { getOpsxVerifyCommandTemplate, getOpsxProposeSkillTemplate, getOpsxProposeCommandTemplate, + getRefineChangeSkillTemplate, + getOpsxRefineCommandTemplate, getFeedbackSkillTemplate, }; @@ -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( From fc95a3f5a5136129b65c852ffc560beafca44a6c Mon Sep 17 00:00:00 2001 From: shohu <shohu@users.noreply.github.com> Date: Tue, 31 Mar 2026 09:37:58 +0900 Subject: [PATCH 2/3] fix: address CodeRabbit review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace brittle .replace() with buildInstructions() + explicit constants - Fix over-escaped backticks in scratchpad template (\\\` → \`) - Add blank line before markdown table in commands.md (MD058) - Update test titles from "11" to "12" - Add explicit refine presence checks in skill/command tests --- docs/commands.md | 1 + src/core/templates/workflows/refine-change.ts | 20 ++++++++++--------- test/core/shared/skill-generation.test.ts | 8 +++++--- .../templates/skill-templates-parity.test.ts | 6 +++--- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 76e8e1c4d..f8def955c 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -326,6 +326,7 @@ Review and refine change artifacts for cross-artifact consistency, best-practice ``` **Arguments:** + | Argument | Required | Description | |----------|----------|-------------| | `change-name` | No | Which change to refine (prompted if not provided) | diff --git a/src/core/templates/workflows/refine-change.ts b/src/core/templates/workflows/refine-change.ts index 365cb7f48..9e4df3a91 100644 --- a/src/core/templates/workflows/refine-change.ts +++ b/src/core/templates/workflows/refine-change.ts @@ -6,9 +6,13 @@ */ import type { SkillTemplate, CommandTemplate } from '../types.js'; -const refineInstructions = `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. +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`).'; -**Input**: Optionally specify a 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** @@ -94,7 +98,7 @@ const refineInstructions = `Review and refine change artifacts for cross-artifac \`\`\`markdown ## <change-name> Refinement Scratchpad -Tracks openspec-refine issues and working decisions for the \\\`<change-name>\\\` change. +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 @@ -116,7 +120,7 @@ Last updated: YYYY-MM-DD - **Status**: <status> - **Notes**: <clarify the status> - **Artifacts touched**: - - \\\`openspec/changes/<change-name>/...\\\` + - \`openspec/changes/<change-name>/...\` #### P1(1): <title> ... @@ -131,12 +135,13 @@ Last updated: YYYY-MM-DD - 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: refineInstructions, + instructions: buildInstructions(SKILL_INPUT_LINE), license: 'MIT', compatibility: 'Requires openspec CLI.', metadata: { author: 'openspec', version: '1.0' }, @@ -149,9 +154,6 @@ export function getOpsxRefineCommandTemplate(): CommandTemplate { description: 'Refine change artifacts for consistency and quality before implementation', category: 'Workflow', tags: ['workflow', 'refine', 'quality'], - content: refineInstructions.replace( - 'Optionally specify a change name (e.g., `/opsx:refine add-auth`).', - 'The argument after `/opsx:refine` is the change name (e.g., `/opsx:refine add-auth`).', - ), + content: buildInstructions(COMMAND_INPUT_LINE), }; } diff --git a/test/core/shared/skill-generation.test.ts b/test/core/shared/skill-generation.test.ts index 8c9417c8f..8c2fb6c97 100644 --- a/test/core/shared/skill-generation.test.ts +++ b/test/core/shared/skill-generation.test.ts @@ -8,7 +8,7 @@ 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(12); }); @@ -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', () => { @@ -88,7 +89,7 @@ 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(12); }); @@ -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', () => { @@ -142,7 +144,7 @@ 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(12); }); diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 9a078de51..d0a5a0b94 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -54,8 +54,8 @@ const EXPECTED_FUNCTION_HASHES: Record<string, string> = { getOpsxVerifyCommandTemplate: '9b4d3ca422553b7534764eb3a009da87a051612c5238e9baab294c7b1233e9a2', getOpsxProposeSkillTemplate: 'd67f937d44650e9c61d2158c865309fbab23cb3f50a3d4868a640a97776e3999', getOpsxProposeCommandTemplate: '41ad59b37eafd7a161bab5c6e41997a37368f9c90b194451295ede5cd42e4d46', - getRefineChangeSkillTemplate: '3d281e43d5a7ebd1abc88025e013e88ce9d705343cb4fce126dccd0af5302f78', - getOpsxRefineCommandTemplate: '19d2ee7e1d5dc003fb9caeda069fa634c3341206bcec1c094cbc773a2750a08f', + getRefineChangeSkillTemplate: '4238ffd230c790aeba2c030ec55985c14175202073714e8f80b5f1e68cb23c0d', + getOpsxRefineCommandTemplate: '329ecadd448b71d39e94758644d84fc47f8d7b023cc33a5c69b463f50bef8aa1', getFeedbackSkillTemplate: 'd7d83c5f7fc2b92fe8f4588a5bf2d9cb315e4c73ec19bcd5ef28270906319a0d', }; @@ -71,7 +71,7 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record<string, string> = { 'openspec-verify-change': '30d07c6f7051965f624f5964db51844ec17c7dfd05f0da95281fe0ca73616326', 'openspec-onboard': 'dbce376cf895f3fe4f63b4bce66d258c35b7b8884ac746670e5e35fabcefd255', 'openspec-propose': '20e36dabefb90e232bad0667292bd5007ec280f8fc4fc995dbc4282bf45a22e7', - 'openspec-refine-change': 'e54f1be0306233e826d189f63cb18099fd7ff813f24aad2723b6f9dd9094d3a5', + 'openspec-refine-change': '2e12cde4879b14428f9a00dbabfa726e121e6c2eebbd5cfc35fb698d41a8dea1', }; function stableStringify(value: unknown): string { From e5cb29c9e727483cb8122dfa7defb47b51a17611 Mon Sep 17 00:00:00 2001 From: shohu <shohu@users.noreply.github.com> Date: Tue, 31 Mar 2026 21:53:03 +0900 Subject: [PATCH 3/3] fix: address maintainer review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Register 'refine' in ALL_WORKFLOWS (profiles.ts) and WORKFLOW_TO_SKILL_DIR (profile-sync-drift.ts) — fixes the blocker where custom profile users couldn't select refine - Replace Cursor-specific 'AskUserQuestion tool' with agent-agnostic phrasing - Add 'openspec validate --strict' step in the refine loop before commits - Update profiles test (11 → 12, add 'refine' to expected list) - Update parity test hashes --- src/core/profile-sync-drift.ts | 1 + src/core/profiles.ts | 1 + src/core/templates/workflows/refine-change.ts | 10 +++++++--- test/core/profiles.test.ts | 6 +++--- test/core/templates/skill-templates-parity.test.ts | 6 +++--- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/core/profile-sync-drift.ts b/src/core/profile-sync-drift.ts index 782bdcc9f..e68ecaf72 100644 --- a/src/core/profile-sync-drift.ts +++ b/src/core/profile-sync-drift.ts @@ -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', }; diff --git a/src/core/profiles.ts b/src/core/profiles.ts index f61215dfc..4aa1a820e 100644 --- a/src/core/profiles.ts +++ b/src/core/profiles.ts @@ -27,6 +27,7 @@ export const ALL_WORKFLOWS = [ 'archive', 'bulk-archive', 'verify', + 'refine', 'onboard', ] as const; diff --git a/src/core/templates/workflows/refine-change.ts b/src/core/templates/workflows/refine-change.ts index 9e4df3a91..42b043e27 100644 --- a/src/core/templates/workflows/refine-change.ts +++ b/src/core/templates/workflows/refine-change.ts @@ -18,7 +18,7 @@ function buildInstructions(inputLine: string): string { 1. **If no change name provided, prompt for selection** - Run \`openspec list --json\` to get available changes. Use the **AskUserQuestion tool** to let the user select. + 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. @@ -69,8 +69,12 @@ function buildInstructions(inputLine: string): string { - Present the issue to the user with context - Propose a fix (artifact update) - Apply the fix after user approval - - Update the scratchpad status to reflect the artifact state - - **Commit the scratchpad and updated artifacts together** (if the user approves) + - 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. diff --git a/test/core/profiles.test.ts b/test/core/profiles.test.ts index 4df8e66c0..bba33a1f8 100644 --- a/test/core/profiles.test.ts +++ b/test/core/profiles.test.ts @@ -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); }); diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index d0a5a0b94..2420f70b6 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -54,8 +54,8 @@ const EXPECTED_FUNCTION_HASHES: Record<string, string> = { getOpsxVerifyCommandTemplate: '9b4d3ca422553b7534764eb3a009da87a051612c5238e9baab294c7b1233e9a2', getOpsxProposeSkillTemplate: 'd67f937d44650e9c61d2158c865309fbab23cb3f50a3d4868a640a97776e3999', getOpsxProposeCommandTemplate: '41ad59b37eafd7a161bab5c6e41997a37368f9c90b194451295ede5cd42e4d46', - getRefineChangeSkillTemplate: '4238ffd230c790aeba2c030ec55985c14175202073714e8f80b5f1e68cb23c0d', - getOpsxRefineCommandTemplate: '329ecadd448b71d39e94758644d84fc47f8d7b023cc33a5c69b463f50bef8aa1', + getRefineChangeSkillTemplate: '61d79ed4d9673ea032d80d3ec88c617062433ff1a3ee39b1ee8b422ddc834e5b', + getOpsxRefineCommandTemplate: 'da0b28a867314bce78f2302ad11f64b8806687c97f554656969291a71fb9b45e', getFeedbackSkillTemplate: 'd7d83c5f7fc2b92fe8f4588a5bf2d9cb315e4c73ec19bcd5ef28270906319a0d', }; @@ -71,7 +71,7 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record<string, string> = { 'openspec-verify-change': '30d07c6f7051965f624f5964db51844ec17c7dfd05f0da95281fe0ca73616326', 'openspec-onboard': 'dbce376cf895f3fe4f63b4bce66d258c35b7b8884ac746670e5e35fabcefd255', 'openspec-propose': '20e36dabefb90e232bad0667292bd5007ec280f8fc4fc995dbc4282bf45a22e7', - 'openspec-refine-change': '2e12cde4879b14428f9a00dbabfa726e121e6c2eebbd5cfc35fb698d41a8dea1', + 'openspec-refine-change': 'cc84efb5d418561868bca76ac7d51cbd6f1f3a93a02746db9550c9d52db9f8dc', }; function stableStringify(value: unknown): string {