Skip to content

fix: unify tool command invocation rendering across generation paths#740

Open
TabishB wants to merge 2 commits intomainfrom
codex/command-invocation-style-fix
Open

fix: unify tool command invocation rendering across generation paths#740
TabishB wants to merge 2 commits intomainfrom
codex/command-invocation-style-fix

Conversation

@TabishB
Copy link
Contributor

@TabishB TabishB commented Feb 22, 2026

Summary

  • centralize command invocation style handling with explicit tool metadata (opsx-colon, opsx-hyphen, openspec-hyphen)
  • apply invocation-style transforms consistently for both skill instructions and generated command bodies in init and update
  • remove OpenCode-only adapter rewrite and move syntax rewriting to shared generation pipeline
  • make init/update getting-started output use tool-appropriate command syntax when selected tools share a common style
  • add tests for shared invocation-style resolver, Codex/Trae rendering behavior, and updated adapter expectations
  • update OpenSpec specs and link this incremental pattern into unify-template-generation-pipeline

Why

Issue #727 reported stale command references after command naming updates (especially for Codex). The prior implementation only rewrote OpenCode content and left other tools with mismatched references.

Verification

  • pnpm run build
  • npx vitest run test/core/command-invocation-style.test.ts test/utils/command-references.test.ts test/core/command-generation/adapters.test.ts test/core/init.test.ts test/core/update.test.ts

Notes

  • This is an incremental step that keeps the fix aligned with the future unify-template-generation-pipeline architecture, rather than introducing another one-off path.

Closes #727

Summary by CodeRabbit

  • Documentation
    • Introduced explicit tool command pattern customization with terminology mapping support
    • Enhanced template generation pipeline with token-aware rendering and safe fallback behavior for unverified tool commands

@1code-async
Copy link
Contributor

1code-async bot commented Feb 22, 2026

Task completed.

PR Review: fix: unify tool command invocation rendering across generation paths

Overall Assessment

This is a well-structured PR that addresses a real consistency problem (issue #727). The approach of centralizing command invocation style handling into explicit tool metadata is sound and sets up a clean path toward the future ToolProfileRegistry architecture. Good test coverage across the new utility functions, integration points in init/update, and updated adapter expectations.


Architecture & Design (Positive)

  • The layering is clean: pure utility functions in src/utils/command-references.ts, tool-aware orchestration in src/core/command-invocation-style.ts, and consumption in init.ts/update.ts. This separation makes the utilities testable independently of tool config.
  • Moving the rewrite out of the OpenCode adapter and into the shared generation pipeline is the right call. It eliminates the asymmetry where one adapter was responsible for content transformation while others were not.
  • The OPSX_TO_OPENSPEC_COMMAND mapping table in command-references.ts:14-26 is explicit and auditable, which is preferable to constructing names algorithmically.

Issues & Suggestions

1. Getting-started column alignment will break for longer command names

src/core/update.ts:261-263 uses fixed-width padding:

console.log(`  ${formatCommandInvocation('new', commandStyle)}       Start a new change`);
console.log(`  ${formatCommandInvocation('continue', commandStyle)}  Create the next artifact`);
console.log(`  ${formatCommandInvocation('apply', commandStyle)}     Implement tasks`);

For opsx-colon (/opsx:new, /opsx:continue, /opsx:apply) the descriptions roughly line up. But for openspec-hyphen the names become /openspec-new-change, /openspec-continue-change, /openspec-apply-change (18-24 chars vs 9-14). The hardcoded spaces will misalign the descriptions. Consider computing padding to the longest command string, or at minimum using a fixed-column padEnd:

const commands = [
  { id: 'new', label: 'Start a new change' },
  { id: 'continue', label: 'Create the next artifact' },
  { id: 'apply', label: 'Implement tasks' },
];
const formatted = commands.map(c => formatCommandInvocation(c.id, commandStyle));
const maxLen = Math.max(...formatted.map(s => s.length));
commands.forEach((c, i) => {
  console.log(`  ${formatted[i].padEnd(maxLen + 2)}${c.label}`);
});

The same issue applies to src/core/init.ts in the getting-started section, though it's a single-line format there so it's less visible.

2. Regex uses global flag on a module-level const — stale lastIndex risk

src/utils/command-references.ts:12:

const OPSX_COMMAND_ID_PATTERN = /\/opsx:([a-z][a-z-]*)/g;

This regex has the /g flag and is stored as a module-level constant. When used with String.prototype.replace() this is fine (replace always resets lastIndex). But if anyone ever calls .test() or .exec() on this shared pattern, the stateful lastIndex will cause intermittent bugs. Two options:

  • Move the pattern inside transformCommandReferences so a fresh regex is created per call (negligible perf difference).
  • Add a comment warning not to use .test()/.exec() on this constant directly.

This isn't a bug today, but it's a latent hazard worth addressing.

3. commandReferenceStyle defaults are implicit and split across two locations

Tools without an explicit commandReferenceStyle in config.ts (Claude, CodeBuddy, Crush, Gemini, Qoder, AGENTS.md) fall through to DEFAULT_COMMAND_REFERENCE_STYLE in command-invocation-style.ts:9. This works, but makes it non-obvious which tools use colon style — you have to know the default. Consider either:

  • Making the property required on AIToolOption (enforces explicit documentation per tool), or
  • Adding a comment on the tools that intentionally omit it, noting they inherit the default.

This would also prevent accidentally adding a new tool and forgetting to set the style.

4. formatCommandFamily is exported but unused

formatCommandFamily in command-references.ts:47-58 is exported and tested, but I don't see any call site in the codebase. It's also re-exported from src/utils/index.ts. If this is scaffolding for future work, a brief comment noting that would help. Otherwise, consider deferring it to avoid dead code.

5. formatToolCommandInvocation has no call sites or tests

src/core/command-invocation-style.ts:20-22:

export function formatToolCommandInvocation(toolId: string, commandId: string): string {
  return formatCommandInvocation(commandId, getToolCommandReferenceStyle(toolId));
}

Similar to formatCommandFamily — this is exported but not called anywhere in the diff nor tested. If it's forward-looking plumbing, note it as such; otherwise it could be added when needed.

6. getDisplayCommandReferenceStyle fallback behavior could surprise mixed-tool users

command-invocation-style.ts:36-47: When tools disagree on style, the function silently falls back to opsx-colon. Since most tools in the list use opsx-hyphen, a setup with e.g. ['codex', 'trae'] would display opsx-colon in the getting-started output — a style neither tool actually uses. The test at command-invocation-style.test.ts:47-48 covers the case where claude + cursor disagree, but doesn't cover two non-colon tools disagreeing. Consider:

  • Testing ['codex', 'trae'] explicitly to document this behavior.
  • Alternatively, falling back to the majority style rather than always colon.

7. Minor: description field could be transformed too

transformCommandContentsForTool transforms content.body but not content.description. If any command description contains a /opsx: reference, it would remain in colon form. This may not be an issue today if descriptions are plain text, but worth verifying.


Test Coverage

The test additions are solid:

  • command-invocation-style.test.ts covers all three style resolutions, body transformation for both non-default styles, and the display-style consensus logic.
  • command-references.test.ts adds direct coverage for transformCommandReferences, formatCommandInvocation, and formatCommandFamily.
  • init.test.ts adds integration tests for Trae skill rewriting and Codex command rewriting.
  • The adapter test updates correctly verify that OpenCode's adapter no longer rewrites inline — the responsibility has moved upstream.

One gap: there's no test for transformCommandContentsForTool with a claude (colon-style) tool to verify the identity/passthrough path returns the original array reference (not a copy). This would document the optimization.


Summary

This is a well-motivated, well-tested change. The core approach — making invocation style a declarative tool attribute and applying transforms in the shared pipeline — is clearly better than the prior per-adapter approach. The main items worth addressing are the column alignment issue (user-visible), the latent regex lastIndex hazard, and the unused exported functions. The rest are minor suggestions for robustness.


View full conversation

Powered by 1Code

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

This pull request introduces explicit tool-specific rendering metadata to the template generation pipeline, including command surface patterns, terminology definitions, and verification flags. It extends the transformation pipeline with token-aware rendering and adds fallback behavior for unverified tool command surfaces, updating implementation tasks and validation test plans accordingly.

Changes

Cohort / File(s) Summary
Design & Specification Updates
openspec/changes/unify-template-generation-pipeline/design.md, openspec/changes/unify-template-generation-pipeline/proposal.md, openspec/changes/unify-template-generation-pipeline/tasks.md
Introduces ToolProfile extensions with commandSurface (pattern options: opsx-colon, opsx-hyphen, opsx-slash, openspec-hyphen, custom; verified flag; aliases) and terminology (change, workflow, command fields). Augments transform pipeline to include token rendering as distinct step. Adds fallback/unverified command surface policy for neutral workflow guidance. Expands implementation plan and validation test coverage to include tokenization, surface verification, and fallback rendering scenarios.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • #627: Addresses command-surface rendering and formatting for OpenCode/opsx command references, directly related to the colon-to-hyphen transformation and tool-specific command generation patterns introduced in this PR.

Poem

🐰✨ Tokens hop and surfaces dance,
Tool-aware rendering takes its chance,
Verified profiles guide the way,
While fallback wisdom saves the day!
From colon to hyphen, we now align—
The template pipeline's design divine! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective: unifying tool command invocation rendering across generation paths to ensure consistent command syntax.
Linked Issues check ✅ Passed The PR addresses issue #727 by implementing explicit tool metadata for command invocation styles and applying consistent rendering across init/update paths, resolving syntax mismatches.
Out of Scope Changes check ✅ Passed All changes are scoped to the command invocation style unification and related metadata infrastructure, with no unrelated alterations detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/command-invocation-style-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link

greptile-apps bot commented Feb 22, 2026

Greptile Summary

Centralizes command invocation style handling by moving transformation logic from adapter-specific code into a shared generation pipeline.

Key Changes:

  • Introduces command-invocation-style.ts module with tool-aware style resolution (opsx-colon, opsx-hyphen, openspec-hyphen)
  • Adds commandReferenceStyle metadata to tool definitions in config.ts for declarative style specification
  • Removes OpenCode-only adapter rewriting logic and applies transforms consistently before adapter formatting
  • Updates both init.ts and update.ts to use centralized transformers for skills and commands
  • Makes getting-started output use tool-appropriate command syntax when all selected tools share a common style
  • Adds comprehensive test coverage for style resolution, transformations, and adapter expectations

Impact:
Fixes issue #727 where command references became stale after naming updates (especially for Codex). The prior implementation only rewrote OpenCode content and left other tools with mismatched references. This change ensures all tools receive consistent, correct command references through a unified pipeline.

Confidence Score: 5/5

  • Safe to merge with high confidence
  • Well-structured refactoring with excellent test coverage (213 tests passing), clear architectural improvement, and proper alignment with specifications
  • No files require special attention

Important Files Changed

Filename Overview
src/core/command-invocation-style.ts New module centralizes command invocation style resolution and transformation logic
src/utils/command-references.ts Enhanced with comprehensive command reference transformation utilities for all invocation styles
src/core/config.ts Added commandReferenceStyle metadata to tool definitions for declarative style specification
src/core/command-generation/adapters/opencode.ts Removed adapter-specific command reference rewriting, now handled centrally in generation pipeline
src/core/init.ts Uses centralized invocation-style transforms for both skills and commands, with style-aware getting-started output
src/core/update.ts Applies invocation-style transforms consistently, matching init.ts implementation

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Workflow Templates] --> B[Generate Skill Content]
    A --> C[Generate Command Content]
    
    B --> D{Tool-specific<br/>transformation}
    C --> D
    
    D --> E[command-invocation-style.ts]
    E --> F{Get Tool Style}
    F --> G[opsx-colon<br/>Claude]
    F --> H[opsx-hyphen<br/>Codex, Cursor, etc]
    F --> I[openspec-hyphen<br/>Trae]
    
    G --> J[No Transform]
    H --> K[Transform References<br/>/opsx:X → /opsx-X]
    I --> L[Transform References<br/>/opsx:X → /openspec-X-Y]
    
    J --> M[Tool Adapter]
    K --> M
    L --> M
    
    M --> N[Write Skill Files]
    M --> O[Write Command Files]
    
    style E fill:#e1f5ff
    style F fill:#fff4e1
    style M fill:#e8f5e9
Loading

Last reviewed commit: c9bc915

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (6)
src/core/config.ts (1)

17-19: commandReferenceStyle field lacks a doc comment explaining the default.

skillsDir documents itself inline; commandReferenceStyle does not. The implicit default (opsx-colon when absent) is buried in command-invocation-style.ts. A brief comment would make the field self-documenting:

✏️ Suggested doc comment
   skillsDir?: string; // e.g., '.claude' - /skills suffix per Agent Skills spec
+  /** Command reference style for this tool. Defaults to 'opsx-colon' when absent. */
   commandReferenceStyle?: 'opsx-colon' | 'opsx-hyphen' | 'openspec-hyphen';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/config.ts` around lines 17 - 19, Add a brief doc comment to the
commandReferenceStyle field in the Config type explaining what the options mean
and that the default behavior is 'opsx-colon' when the field is omitted; update
the comment adjacent to the commandReferenceStyle declaration in
src/core/config.ts (the Config interface/type) and, if helpful, mention that the
implicit default is implemented in command-invocation-style.ts so readers can
find the runtime behavior.
test/core/command-generation/adapters.test.ts (1)

458-481: New tests correctly verify the adapter's pass-through contract; minor clarity nit.

The tests accurately document that opencodeAdapter.formatFile no longer transforms command references internally. One thing that could confuse future readers: in production, content reaching this adapter is already in opsx-hyphen format (transformed upstream), but the tests pass colon-style input. A brief inline comment clarifying "transformation happens upstream; adapter is format-agnostic" would remove that ambiguity.

💬 Suggested clarifying comment
   describe('opencodeAdapter', () => {
+    // NOTE: In production, content is pre-transformed to opsx-hyphen style before
+    // reaching this adapter. These tests use colon-style input to verify the adapter
+    // itself is format-agnostic (no internal rewriting).
     it('should have correct toolId', () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/core/command-generation/adapters.test.ts` around lines 458 - 481, Add a
short inline comment above the tests that exercise opencodeAdapter.formatFile
(the two it blocks that use CommandContent and sampleContent) clarifying that
command-format transformation (colon -> hyphen, e.g., opsx:new -> opsx-new) is
performed upstream and that the adapter intentionally preserves input verbatim;
reference opencodeAdapter.formatFile and the test cases so future readers
understand these are pass-through behavior tests, not a failure to transform.
openspec/changes/unify-template-generation-pipeline/design.md (1)

57-65: commandReferenceStyle is required in ToolProfile but optional in AIToolOption — reconcile before implementing task 2.2.

When ToolProfileRegistry is implemented, all tool entries must supply an explicit commandReferenceStyle. Tools currently without the field in AI_TOOLS (claude, codebuddy, crush, gemini, qoder) rely on the implicit opsx-colon default in command-invocation-style.ts. Consider either:

  • Making the field optional in ToolProfile with a documented default, or
  • Explicitly enumerating opsx-colon for those tools when building the registry entries.

Either choice is fine, but the mismatch should be a deliberate decision documented in the registry implementation.

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

In `@openspec/changes/unify-template-generation-pipeline/design.md` around lines
57 - 65, ToolProfile declares commandReferenceStyle as required while
AIToolOption treats it as optional, causing a mismatch; update the registry
implementation (ToolProfileRegistry / AI_TOOLS builder) to reconcile this by
either making ToolProfile.commandReferenceStyle optional and documenting the
default 'opsx-colon' (referenced in command-invocation-style.ts) or by
explicitly adding commandReferenceStyle: 'opsx-colon' to every tool entry
missing it (e.g., claude, codebuddy, crush, gemini, qoder) when constructing
AI_TOOLS; apply one consistent approach and add a short comment in the registry
explaining the chosen decision.
src/core/update.ts (1)

195-203: Hoist transformer outside the inner skillTemplates loop.

getToolCommandReferenceTransformer(tool.value) is invariant across templates — it only depends on tool.value. Moving it before the for loop avoids redundant lookups per template.

The same pattern applies at Line 594 in upgradeLegacyTools.

Suggested diff
         if (shouldGenerateSkills) {
+          const transformer = getToolCommandReferenceTransformer(tool.value);
           for (const { template, dirName } of skillTemplates) {
             const skillDir = path.join(skillsDir, dirName);
             const skillFile = path.join(skillDir, 'SKILL.md');
 
-            const transformer = getToolCommandReferenceTransformer(tool.value);
             const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
             await FileSystemUtils.writeFile(skillFile, skillContent);
           }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/update.ts` around lines 195 - 203, The code repeatedly calls
getToolCommandReferenceTransformer(tool.value) inside the skillTemplates loop;
hoist this call into the outer shouldGenerateSkills branch (e.g., compute const
transformer = getToolCommandReferenceTransformer(tool.value) before iterating
skillTemplates) and then pass that transformer into generateSkillContent; apply
the same refactor in the upgradeLegacyTools function where
getToolCommandReferenceTransformer is called per-template so you compute it once
per tool and reuse it for all template iterations (references:
shouldGenerateSkills, skillTemplates, getToolCommandReferenceTransformer,
generateSkillContent, upgradeLegacyTools).
test/core/command-invocation-style.test.ts (1)

1-49: Tests cover the core paths well.

The four tests validate tool→style resolution, per-tool body transformation for both opsx-hyphen and openspec-hyphen, and the consensus logic. Assertions align with the config data and transformation logic.

Consider adding edge-case tests for an empty tool array and an unknown tool ID to fully cover the fallback behavior of getDisplayCommandReferenceStyle and getToolCommandReferenceStyle.

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

In `@test/core/command-invocation-style.test.ts` around lines 1 - 49, Add two
tests covering fallback behavior: one calling getDisplayCommandReferenceStyle
with an empty array and asserting it returns the default display style (verify
expected default value), and one calling getToolCommandReferenceStyle with an
unknown tool id (e.g., 'unknown-tool') and asserting it returns the configured
fallback style; place both tests alongside existing ones in
command-invocation-style.test.ts to ensure the functions' fallback logic is
exercised.
src/core/init.ts (1)

545-555: Same transformer hoisting opportunity as noted in update.ts.

getToolCommandReferenceTransformer(tool.value) at Line 550 is invariant within the skillTemplates loop and could be computed once before the loop.

Suggested diff
         if (shouldGenerateSkills) {
           // Use tool-specific skillsDir
           const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
 
           // Create skill directories and SKILL.md files
+          const transformer = getToolCommandReferenceTransformer(tool.value);
           for (const { template, dirName } of skillTemplates) {
             const skillDir = path.join(skillsDir, dirName);
             const skillFile = path.join(skillDir, 'SKILL.md');
 
             // Generate SKILL.md content with YAML frontmatter including generatedBy
-            const transformer = getToolCommandReferenceTransformer(tool.value);
             const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/init.ts` around lines 545 - 555, The call to
getToolCommandReferenceTransformer(tool.value) is invariant inside the loop over
skillTemplates and should be hoisted out to avoid repeated computation; compute
const transformer = getToolCommandReferenceTransformer(tool.value) once before
the for (const { template, dirName } of skillTemplates) loop and then use that
transformer variable inside the loop when calling generateSkillContent(template,
OPENSPEC_VERSION, transformer), keeping the rest of the SKILL.md generation
logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/core/init.test.ts`:
- Around line 358-367: The test for InitCommand's Trae rewrite is implicitly
coupled to the openspec-propose template content so it can break silently;
update the test (the it block that instantiates InitCommand and reads SKILL.md)
to either (a) load/use a controlled fixture/template that contains a known
'/opsx:apply' reference and assert it becomes '/openspec-apply-change', or (b)
add a clear comment and explicitly write a minimal SKILL.md with '/opsx:apply'
into the testDir before running InitCommand so the assertion on
content('/openspec-apply-change') and notContains('/opsx:apply') targets the
rewrite behavior of InitCommand.execute rather than relying on openspec-propose
template content. Ensure you reference InitCommand.execute and the skillFile
path logic in your change.

---

Nitpick comments:
In `@openspec/changes/unify-template-generation-pipeline/design.md`:
- Around line 57-65: ToolProfile declares commandReferenceStyle as required
while AIToolOption treats it as optional, causing a mismatch; update the
registry implementation (ToolProfileRegistry / AI_TOOLS builder) to reconcile
this by either making ToolProfile.commandReferenceStyle optional and documenting
the default 'opsx-colon' (referenced in command-invocation-style.ts) or by
explicitly adding commandReferenceStyle: 'opsx-colon' to every tool entry
missing it (e.g., claude, codebuddy, crush, gemini, qoder) when constructing
AI_TOOLS; apply one consistent approach and add a short comment in the registry
explaining the chosen decision.

In `@src/core/config.ts`:
- Around line 17-19: Add a brief doc comment to the commandReferenceStyle field
in the Config type explaining what the options mean and that the default
behavior is 'opsx-colon' when the field is omitted; update the comment adjacent
to the commandReferenceStyle declaration in src/core/config.ts (the Config
interface/type) and, if helpful, mention that the implicit default is
implemented in command-invocation-style.ts so readers can find the runtime
behavior.

In `@src/core/init.ts`:
- Around line 545-555: The call to
getToolCommandReferenceTransformer(tool.value) is invariant inside the loop over
skillTemplates and should be hoisted out to avoid repeated computation; compute
const transformer = getToolCommandReferenceTransformer(tool.value) once before
the for (const { template, dirName } of skillTemplates) loop and then use that
transformer variable inside the loop when calling generateSkillContent(template,
OPENSPEC_VERSION, transformer), keeping the rest of the SKILL.md generation
logic unchanged.

In `@src/core/update.ts`:
- Around line 195-203: The code repeatedly calls
getToolCommandReferenceTransformer(tool.value) inside the skillTemplates loop;
hoist this call into the outer shouldGenerateSkills branch (e.g., compute const
transformer = getToolCommandReferenceTransformer(tool.value) before iterating
skillTemplates) and then pass that transformer into generateSkillContent; apply
the same refactor in the upgradeLegacyTools function where
getToolCommandReferenceTransformer is called per-template so you compute it once
per tool and reuse it for all template iterations (references:
shouldGenerateSkills, skillTemplates, getToolCommandReferenceTransformer,
generateSkillContent, upgradeLegacyTools).

In `@test/core/command-generation/adapters.test.ts`:
- Around line 458-481: Add a short inline comment above the tests that exercise
opencodeAdapter.formatFile (the two it blocks that use CommandContent and
sampleContent) clarifying that command-format transformation (colon -> hyphen,
e.g., opsx:new -> opsx-new) is performed upstream and that the adapter
intentionally preserves input verbatim; reference opencodeAdapter.formatFile and
the test cases so future readers understand these are pass-through behavior
tests, not a failure to transform.

In `@test/core/command-invocation-style.test.ts`:
- Around line 1-49: Add two tests covering fallback behavior: one calling
getDisplayCommandReferenceStyle with an empty array and asserting it returns the
default display style (verify expected default value), and one calling
getToolCommandReferenceStyle with an unknown tool id (e.g., 'unknown-tool') and
asserting it returns the configured fallback style; place both tests alongside
existing ones in command-invocation-style.test.ts to ensure the functions'
fallback logic is exercised.

Comment on lines 358 to 367
it('should rewrite skill command references for Trae invocation style', async () => {
const initCommand = new InitCommand({ tools: 'trae', force: true });
await initCommand.execute(testDir);

const skillFile = path.join(testDir, '.trae', 'skills', 'openspec-propose', 'SKILL.md');
const content = await fs.readFile(skillFile, 'utf-8');

expect(content).toContain('/openspec-apply-change');
expect(content).not.toContain('/opsx:apply');
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Trae test has an implicit coupling to openspec-propose template content.

expect(content).toContain('/openspec-apply-change') will only pass if the openspec-propose skill template body actually contains a /opsx:apply cross-reference (which gets rewritten). If the template is later refactored to remove or rename that reference, this test fails with a misleading message that looks like an invocation-style regression rather than a template content change.

A brief comment or a targeted fixture body would make the intent explicit and guard against silent test-passes:

  it('should rewrite skill command references for Trae invocation style', async () => {
    const initCommand = new InitCommand({ tools: 'trae', force: true });
    await initCommand.execute(testDir);

    const skillFile = path.join(testDir, '.trae', 'skills', 'openspec-propose', 'SKILL.md');
    const content = await fs.readFile(skillFile, 'utf-8');

+   // openspec-propose body cross-references /opsx:apply; Trae's openspec-hyphen style
+   // rewrites it to /openspec-apply-change. This assertion will fail if that
+   // cross-reference is ever removed from the template.
    expect(content).toContain('/openspec-apply-change');
    expect(content).not.toContain('/opsx:apply');
  });
📝 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
it('should rewrite skill command references for Trae invocation style', async () => {
const initCommand = new InitCommand({ tools: 'trae', force: true });
await initCommand.execute(testDir);
const skillFile = path.join(testDir, '.trae', 'skills', 'openspec-propose', 'SKILL.md');
const content = await fs.readFile(skillFile, 'utf-8');
expect(content).toContain('/openspec-apply-change');
expect(content).not.toContain('/opsx:apply');
});
it('should rewrite skill command references for Trae invocation style', async () => {
const initCommand = new InitCommand({ tools: 'trae', force: true });
await initCommand.execute(testDir);
const skillFile = path.join(testDir, '.trae', 'skills', 'openspec-propose', 'SKILL.md');
const content = await fs.readFile(skillFile, 'utf-8');
// openspec-propose body cross-references /opsx:apply; Trae's openspec-hyphen style
// rewrites it to /openspec-apply-change. This assertion will fail if that
// cross-reference is ever removed from the template.
expect(content).toContain('/openspec-apply-change');
expect(content).not.toContain('/opsx:apply');
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/core/init.test.ts` around lines 358 - 367, The test for InitCommand's
Trae rewrite is implicitly coupled to the openspec-propose template content so
it can break silently; update the test (the it block that instantiates
InitCommand and reads SKILL.md) to either (a) load/use a controlled
fixture/template that contains a known '/opsx:apply' reference and assert it
becomes '/openspec-apply-change', or (b) add a clear comment and explicitly
write a minimal SKILL.md with '/opsx:apply' into the testDir before running
InitCommand so the assertion on content('/openspec-apply-change') and
notContains('/opsx:apply') targets the rewrite behavior of InitCommand.execute
rather than relying on openspec-propose template content. Ensure you reference
InitCommand.execute and the skillFile path logic in your change.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
openspec/changes/unify-template-generation-pipeline/design.md (2)

70-74: commandSurface.aliases purpose is unexplained.

The aliases?: string[] field has no rationale or example in the design. It's unclear whether this is for:

  • Alternative invocation patterns the tool also accepts (e.g., a tool that supports both /opsx:apply and /opsx-apply)
  • Display names for the command surface
  • Something else entirely

A brief clarifying comment or rationale entry (matching the style of the other commandSurface decisions) would prevent incorrect implementations during task 2.4.

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

In `@openspec/changes/unify-template-generation-pipeline/design.md` around lines
70 - 74, The comment points out that commandSurface.aliases is undocumented and
ambiguous; update the design.md entry for the commandSurface block to explain
the purpose and give examples: state whether aliases are alternative invocation
patterns (e.g., '/opsx:apply' vs '/opsx-apply'), display names, or other, and
include at least one concrete example and expected behavior (how parsers should
treat aliases and whether they require verification via the verified flag).
Specifically update the commandSurface.aliases description and add a short
rationale line matching other decisions so implementers know how to use
commandSurface.aliases when implementing command parsing/validation.

98-114: ArtifactTransform.phase has no value for the token-render step — design gap.

The execution order places token rendering as step 2, explicitly before preAdapter transforms. However, the ArtifactTransform interface only defines phase: 'preAdapter' | 'postAdapter'. If the token renderer is implemented as an ArtifactTransform (as task 3.3 implies), neither phase is semantically correct and ordering becomes ambiguous.

Two approaches to resolve this:

  • Add a dedicated phase — extend the union to include a 'tokenRender' phase and document it as the built-in first pass:
♻️ Option A – add `tokenRender` phase
 interface ArtifactTransform {
   id: string;
   scope: 'skill' | 'command' | 'both';
-  phase: 'preAdapter' | 'postAdapter';
+  phase: 'tokenRender' | 'preAdapter' | 'postAdapter';
   priority: number;
   applies(ctx: GenerationContext): boolean;
   transform(content: string, ctx: GenerationContext): string;
 }
  • Make it a built-in step — document that token rendering is a first-class, non-configurable pipeline step rather than an ArtifactTransform plug-in, and reserve the interface for developer-defined transforms only.

Whichever option is chosen, the execution order comment (steps 1–6) should reference it explicitly so implementers of task 3.3 aren't left guessing.

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

In `@openspec/changes/unify-template-generation-pipeline/design.md` around lines
98 - 114, The ArtifactTransform interface lacks a phase value for the
token-render step, causing ambiguity between the token renderer and
preAdapter/postAdapter transforms; update the design by either (A) adding a
dedicated phase string like 'tokenRender' to ArtifactTransform.phase (so
implementations of ArtifactTransform can declare phase: 'tokenRender' and the
execution order 1–6 explicitly references this phase), or (B) document token
rendering as a built-in, non-pluggable pipeline step outside ArtifactTransform
(and update the doc to state that ArtifactTransform implementations only use
'preAdapter'|'postAdapter'); reference ArtifactTransform, its phase field, and
the token-render step in GenerationContext/docs so task 3.3 implementers know
the correct placement and ordering.
openspec/changes/unify-template-generation-pipeline/tasks.md (1)

15-15: Task 2.6 is misplaced under Tool Profile Layer.

SKILL_NAMES is a manifest-derived projection, not a tool profile concern. The implementation approach in design.md (step 3: "Rewire getSkillTemplates/getCommandContents to derive from manifest") places this squarely in Section 1 (Manifest Foundation). Keeping it under Section 2 may cause implementers to add it as a profile dependency when it only needs the manifest.

♻️ Suggested move
## 1. Manifest Foundation

 - [ ] 1.1 Create canonical workflow manifest registry under `src/core/templates/`
 - [ ] 1.2 Define shared manifest types for workflow IDs, skill metadata, and optional command descriptors
 - [ ] 1.3 Migrate existing workflow registration (`getSkillTemplates`, `getCommandTemplates`, `getCommandContents`) to derive from the manifest
 - [ ] 1.4 Preserve existing external exports/API compatibility for `src/core/templates/skill-templates.ts`
+- [ ] 1.5 Replace hardcoded detection arrays (for example `SKILL_NAMES`) with manifest-derived values

 ## 2. Tool Profile Layer

 - [ ] 2.1 Add `ToolProfile` types and `ToolProfileRegistry`
 - [ ] 2.2 Map all currently supported tools to explicit profile entries
 - [ ] 2.3 Wire profile lookups to command adapter resolution and skills path resolution
 - [ ] 2.4 Add per-tool `commandSurface` metadata (pattern, aliases, `verified` flag)
 - [ ] 2.5 Add per-tool terminology metadata (for example change/workflow/command labels)
-- [ ] 2.6 Replace hardcoded detection arrays (for example `SKILL_NAMES`) with manifest-derived values
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openspec/changes/unify-template-generation-pipeline/tasks.md` at line 15,
Task 2.6 is in the wrong section: move the item about replacing hardcoded
detection arrays (e.g., SKILL_NAMES) out of the Tool Profile Layer and into the
Manifest Foundation (Section 1) because SKILL_NAMES is a manifest-derived
projection; update the tasks list so the checklist entry "- [ ] 2.6 Replace
hardcoded detection arrays (for example `SKILL_NAMES`) with manifest-derived
values" is relocated to Section 1 and add a short note linking it to the
redesign step that rewires getSkillTemplates/getCommandContents to derive values
from the manifest.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openspec/changes/unify-template-generation-pipeline/design.md`:
- Around line 47-51: The example token {{cmd.continue.withArg}} is ambiguous;
either define the third-level modifier contract here or replace the example with
a supported two-level token like {{cmd.continue}}. Update the "Examples in
canonical workflow text" block to (a) explicitly document what a modifier such
as .withArg means (e.g., produces a placeholder argument, toggles inclusion, or
is invalid), (b) enumerate allowed modifier names and whether multiple modifiers
are permitted, and (c) describe how the renderer resolves a three-part path
(lookup order and fallback behavior). Make these edits so the example tokens
({{cmd.apply}}, {{term.change}}, and the revised {{cmd.continue}} or the newly
specified {{cmd.continue.withArg}} semantics) match the resolver behavior
expected by tasks 3.3 and 5.3.

---

Nitpick comments:
In `@openspec/changes/unify-template-generation-pipeline/design.md`:
- Around line 70-74: The comment points out that commandSurface.aliases is
undocumented and ambiguous; update the design.md entry for the commandSurface
block to explain the purpose and give examples: state whether aliases are
alternative invocation patterns (e.g., '/opsx:apply' vs '/opsx-apply'), display
names, or other, and include at least one concrete example and expected behavior
(how parsers should treat aliases and whether they require verification via the
verified flag). Specifically update the commandSurface.aliases description and
add a short rationale line matching other decisions so implementers know how to
use commandSurface.aliases when implementing command parsing/validation.
- Around line 98-114: The ArtifactTransform interface lacks a phase value for
the token-render step, causing ambiguity between the token renderer and
preAdapter/postAdapter transforms; update the design by either (A) adding a
dedicated phase string like 'tokenRender' to ArtifactTransform.phase (so
implementations of ArtifactTransform can declare phase: 'tokenRender' and the
execution order 1–6 explicitly references this phase), or (B) document token
rendering as a built-in, non-pluggable pipeline step outside ArtifactTransform
(and update the doc to state that ArtifactTransform implementations only use
'preAdapter'|'postAdapter'); reference ArtifactTransform, its phase field, and
the token-render step in GenerationContext/docs so task 3.3 implementers know
the correct placement and ordering.

In `@openspec/changes/unify-template-generation-pipeline/tasks.md`:
- Line 15: Task 2.6 is in the wrong section: move the item about replacing
hardcoded detection arrays (e.g., SKILL_NAMES) out of the Tool Profile Layer and
into the Manifest Foundation (Section 1) because SKILL_NAMES is a
manifest-derived projection; update the tasks list so the checklist entry "- [ ]
2.6 Replace hardcoded detection arrays (for example `SKILL_NAMES`) with
manifest-derived values" is relocated to Section 1 and add a short note linking
it to the redesign step that rewires getSkillTemplates/getCommandContents to
derive values from the manifest.

Comment on lines +47 to +51
// Examples in canonical workflow text:
// - {{cmd.apply}}
// - {{cmd.continue.withArg}}
// - {{term.change}}
// - {{term.workflow}}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

{{cmd.continue.withArg}} token format is undefined — implementation ambiguity.

The two-level tokens {{cmd.apply}} and {{term.change}} are self-explanatory, but {{cmd.continue.withArg}} introduces a three-level path (namespace · command name · modifier) without defining:

  • What .withArg means (a variant that includes an argument placeholder? a conditional rendering flag?)
  • Whether additional modifiers are valid (e.g., {{cmd.apply.withArg}})
  • How the token renderer resolves the third level

This will create inconsistent implementations across tasks 3.3 and 5.3. Recommend either defining the modifier contract inline here, or simplifying the example to a form that matches what the resolver will actually support (e.g., {{cmd.continue}}).

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

In `@openspec/changes/unify-template-generation-pipeline/design.md` around lines
47 - 51, The example token {{cmd.continue.withArg}} is ambiguous; either define
the third-level modifier contract here or replace the example with a supported
two-level token like {{cmd.continue}}. Update the "Examples in canonical
workflow text" block to (a) explicitly document what a modifier such as .withArg
means (e.g., produces a placeholder argument, toggles inclusion, or is invalid),
(b) enumerate allowed modifier names and whether multiple modifiers are
permitted, and (c) describe how the renderer resolves a three-part path (lookup
order and fallback behavior). Make these edits so the example tokens
({{cmd.apply}}, {{term.change}}, and the revised {{cmd.continue}} or the newly
specified {{cmd.continue.withArg}} semantics) match the resolver behavior
expected by tasks 3.3 and 5.3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug Report] prompts and skills are not exact compatible with commands like "/opsx-new" in version 1.1.1

1 participant