Skip to content

Commit 697738b

Browse files
authored
fix(opencode): transform command references from colon to hyphen format (#626)
* Add OpenCode files to gitignore * docs(changes): add opencode-command-references change artifacts * fix(opencode): transform command references from colon to hyphen format
1 parent 3768694 commit 697738b

File tree

16 files changed

+342
-7
lines changed

16 files changed

+342
-7
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,7 @@ CLAUDE.md
149149
# Pnpm
150150
.pnpm-store/
151151
result
152+
153+
# OpenCode
154+
.opencode/
155+
opencode.json
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-01-30
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# opencode-command-references
2+
3+
Transform /opsx: to /opsx- in both commands and skills for OpenCode
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
## Context
2+
3+
OpenCode is one of many supported AI tools. Each tool has:
4+
- A **command adapter** (in `src/core/command-generation/adapters/`) for generating tool-specific command files
5+
- **Skills** generated via `generateSkillContent()` in `src/core/shared/skill-generation.ts`
6+
7+
Currently:
8+
- Commands go through the adapter system which can transform content per-tool
9+
- Skills use a single shared function with no tool-specific transformation
10+
11+
The templates in `src/core/templates/skill-templates.ts` use Claude's colon-based format (`/opsx:new`) as the canonical format. Tools that use different formats need transformation at generation time.
12+
13+
## Goals / Non-Goals
14+
15+
**Goals:**
16+
- Transform all `/opsx:` command references to `/opsx-` for OpenCode in both commands and skills
17+
- Create a shared, reusable transformation utility
18+
- Keep the transformation opt-in via a callback parameter (not hard-coded tool detection)
19+
20+
**Non-Goals:**
21+
- Modifying the canonical template format (templates stay with `/opsx:`)
22+
- Applying transformation to other tools (only OpenCode for now)
23+
- Creating a full adapter system for skills (overkill for current needs)
24+
25+
## Decisions
26+
27+
### Decision 1: Shared Utility Function
28+
29+
**Choice**: Create `transformToHyphenCommands()` in `src/utils/command-references.ts`
30+
31+
**Rationale**:
32+
- Single source of truth for the transformation logic
33+
- Can be used by both command adapter and skill generation
34+
- Easy to test in isolation
35+
- Follows existing utils pattern in the codebase
36+
37+
**Alternatives considered**:
38+
- Inline the transformation in each location - Duplicates logic, harder to maintain
39+
40+
### Decision 2: Callback Parameter for Skill Generation
41+
42+
**Choice**: Add optional `transformInstructions?: (instructions: string) => string` parameter to `generateSkillContent()`
43+
44+
**Rationale**:
45+
- Flexible - callers define the transformation, not the generation function
46+
- No coupling - `generateSkillContent()` doesn't need to know about tool formats
47+
- Extensible - could support other transformations in the future
48+
- Follows inversion of control principle
49+
50+
**Alternatives considered**:
51+
- Add tool ID parameter and switch on it - Creates coupling, harder to extend
52+
- Create skill adapter system parallel to commands - Over-engineering for current needs
53+
- Transform in templates directly - Breaks single-source-of-truth principle
54+
55+
### Decision 3: Apply at Generation Sites
56+
57+
**Choice**: Pass transformer in `init.ts` and `update.ts` when `tool.value === 'opencode'`
58+
59+
**Rationale**:
60+
- These are the only two places that generate skills
61+
- Simple conditional check, no new abstractions needed
62+
- Easy to extend to other tools if needed later
63+
64+
## Risks / Trade-offs
65+
66+
| Risk | Mitigation |
67+
|------|------------|
68+
| Other `/opsx:` patterns exist that shouldn't be transformed | All occurrences in templates are command invocations - verified by inspection |
69+
| Future tools may need same transformation | Utility is shared and easy to reuse; can add to other tools' generation |
70+
| Callback adds complexity to function signature | Optional parameter with sensible default (no transformation) |
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
## Why
2+
3+
OpenCode uses hyphen-based command syntax (`/opsx-new`) but our templates contain colon-based references (`/opsx:new`). This creates inconsistency where generated command files and skill files contain references that don't match the actual command invocation syntax, confusing both the AI and users.
4+
5+
## What Changes
6+
7+
- Create a shared transformation utility (`transformToHyphenCommands`) for converting `/opsx:` to `/opsx-`
8+
- Update the OpenCode command adapter to transform body text using this utility
9+
- Add an optional `transformInstructions` callback parameter to `generateSkillContent()`
10+
- Update `init.ts` and `update.ts` to pass the transformer when generating skills for OpenCode
11+
12+
## Capabilities
13+
14+
### New Capabilities
15+
16+
None - this is a bug fix, not a new capability.
17+
18+
### Modified Capabilities
19+
20+
None - no spec-level behavior changes. This is an implementation fix in the OpenCode adapter and skill generation that doesn't change any external requirements or contracts.
21+
22+
## Impact
23+
24+
- **Code**:
25+
- `src/utils/command-references.ts` (new file)
26+
- `src/utils/index.ts` (export)
27+
- `src/core/shared/skill-generation.ts` (add callback parameter)
28+
- `src/core/command-generation/adapters/opencode.ts` (use transformer)
29+
- `src/core/init.ts` (pass transformer for OpenCode)
30+
- `src/core/update.ts` (pass transformer for OpenCode)
31+
- **Users**: OpenCode users will see correct `/opsx-` command references in both generated command files AND skill files
32+
- **Other tools**: No impact - transformation only applies to OpenCode
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# No Spec Changes
2+
3+
This is a bug fix that doesn't modify any external requirements or contracts.
4+
5+
The proposal's Capabilities section indicates:
6+
- **New Capabilities**: None
7+
- **Modified Capabilities**: None
8+
9+
No spec files are needed for this implementation-only fix.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
## 1. Implementation
2+
3+
- [x] 1.1 Create `src/utils/command-references.ts` with `transformToHyphenCommands()` function
4+
- [x] 1.2 Export `transformToHyphenCommands` from `src/utils/index.ts`
5+
- [x] 1.3 Update `generateSkillContent()` in `src/core/shared/skill-generation.ts` to accept optional `transformInstructions` callback
6+
- [x] 1.4 Update OpenCode adapter in `src/core/command-generation/adapters/opencode.ts` to use `transformToHyphenCommands()` for body text
7+
- [x] 1.5 Update `init.ts` to pass transformer when generating skills for OpenCode
8+
- [x] 1.6 Update `update.ts` to pass transformer when generating skills for OpenCode
9+
10+
## 2. Testing
11+
12+
- [x] 2.1 Create `test/utils/command-references.test.ts` with unit tests for `transformToHyphenCommands()`
13+
- [x] 2.2 Add test to `test/core/command-generation/adapters.test.ts` for OpenCode body transformation
14+
- [x] 2.3 Add test to `test/core/shared/skill-generation.test.ts` for transformer callback
15+
16+
## 3. Verification
17+
18+
- [x] 3.1 Run `npx vitest run test/utils/command-references.test.ts test/core/command-generation/adapters.test.ts test/core/shared/skill-generation.test.ts` to ensure tests pass
19+
- [x] 3.2 Run `pnpm run build` to ensure no TypeScript errors
20+
- [x] 3.3 Run `openspec init --tools opencode` in a temp directory and verify:
21+
- Command files in `.opencode/command/` contain `/opsx-` references (not `/opsx:`)
22+
- Skill files in `.opencode/skills/` contain `/opsx-` references (not `/opsx:`)

src/core/command-generation/adapters/opencode.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import path from 'path';
88
import type { CommandContent, ToolCommandAdapter } from '../types.js';
9+
import { transformToHyphenCommands } from '../../../utils/command-references.js';
910

1011
/**
1112
* OpenCode adapter for command generation.
@@ -20,11 +21,14 @@ export const opencodeAdapter: ToolCommandAdapter = {
2021
},
2122

2223
formatFile(content: CommandContent): string {
24+
// Transform command references from colon to hyphen format for OpenCode
25+
const transformedBody = transformToHyphenCommands(content.body);
26+
2327
return `---
2428
description: ${content.description}
2529
---
2630
27-
${content.body}
31+
${transformedBody}
2832
`;
2933
},
3034
};

src/core/init.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import ora from 'ora';
1111
import * as fs from 'fs';
1212
import { createRequire } from 'module';
1313
import { FileSystemUtils } from '../utils/file-system.js';
14+
import { transformToHyphenCommands } from '../utils/command-references.js';
1415
import {
1516
AI_TOOLS,
1617
OPENSPEC_DIR_NAME,
@@ -440,7 +441,9 @@ export class InitCommand {
440441
const skillFile = path.join(skillDir, 'SKILL.md');
441442

442443
// Generate SKILL.md content with YAML frontmatter including generatedBy
443-
const skillContent = generateSkillContent(template, OPENSPEC_VERSION);
444+
// Use hyphen-based command references for OpenCode
445+
const transformer = tool.value === 'opencode' ? transformToHyphenCommands : undefined;
446+
const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
444447

445448
// Write the skill file
446449
await FileSystemUtils.writeFile(skillFile, skillContent);

src/core/shared/skill-generation.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,17 @@ export function getCommandContents(): CommandContent[] {
101101
*
102102
* @param template - The skill template
103103
* @param generatedByVersion - The OpenSpec version to embed in the file
104+
* @param transformInstructions - Optional callback to transform the instructions content
104105
*/
105106
export function generateSkillContent(
106107
template: SkillTemplate,
107-
generatedByVersion: string
108+
generatedByVersion: string,
109+
transformInstructions?: (instructions: string) => string
108110
): string {
111+
const instructions = transformInstructions
112+
? transformInstructions(template.instructions)
113+
: template.instructions;
114+
109115
return `---
110116
name: ${template.name}
111117
description: ${template.description}
@@ -117,6 +123,6 @@ metadata:
117123
generatedBy: "${generatedByVersion}"
118124
---
119125
120-
${template.instructions}
126+
${instructions}
121127
`;
122128
}

0 commit comments

Comments
 (0)