fix(adapters): reference slash commands by the names each tool registers - #1471
Conversation
Generated command bodies, skills and the post-setup hints all advertised /opsx:<id>, but only 7 of 28 adapter-backed tools register that name. The other 21 write .../opsx-<id>.md, where the filename is the command, so their users were told to type a command their palette never had. Codex, which registers no slash commands at all, was told to type them too. The invocation style is now derived from the command file each adapter writes rather than a hand-maintained tool list, so every tool-specific surface - command bodies, SKILL.md cross-references, and the init, update and migration hints - names the command that tool answers to. Closes #1307 Closes #727 Closes #1379 Closes #1110 Refs #1129 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughCommand generation now derives flat or namespaced invocation styles from tool paths, applies reference rewriting centrally, and updates initialization, migration, and update flows to emit tool-specific command syntax. Documentation, specifications, release notes, and tests cover slash, hyphenated, and Codex skill invocations. ChangesCommand invocation and reference formatting
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Deploying openspec-docs with
|
| Latest commit: |
bc270ea
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://4df0963b.openspec-docs.pages.dev |
| Branch Preview URL: | https://fix-command-invocation-parit.openspec-docs.pages.dev |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/utils/command-references.ts (1)
139-152: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winSound refactor of transformer selection. Generalizing from a hard-coded tool list to capability + invocation-style-derived logic is a solid fix for the drift problem described in the comment. Verified all branches against the accompanying test file's expectations.
One note: making
invocationStylea required (non-optional) parameter is intentional per the doc comment, but see the companion comment intest/utils/command-references.test.ts— several existing test call sites were not updated to pass this argument, so the "fails to compile" guarantee isn't actually being exercised for those calls.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/utils/command-references.ts` around lines 139 - 152, Make invocationStyle required in getTransformerForTool, then update every existing call site in command-references tests to pass the appropriate style value. Ensure the tests exercise the intended compile-time contract rather than relying on the current optional parameter.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/supported-tools.md`:
- Around line 21-35: Separate command-file classification from invocation
syntax: in docs/supported-tools.md lines 21-35, narrow the broad adapter-backed
grouping or explicitly include the Amazon Q, Cline, and Kilo Code wrapper/suffix
exceptions in the table; in docs/how-commands-work.md lines 83-88, qualify the
statement that every opsx-<id> command file uses dash syntax so tool-specific
invocation forms remain accurate.
---
Nitpick comments:
In `@src/utils/command-references.ts`:
- Around line 139-152: Make invocationStyle required in getTransformerForTool,
then update every existing call site in command-references tests to pass the
appropriate style value. Ensure the tests exercise the intended compile-time
contract rather than relying on the current optional parameter.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 5e24b382-7dcc-4237-b397-cf4b257f2312
📒 Files selected for processing (23)
docs/commands.mddocs/how-commands-work.mddocs/supported-tools.mdopenspec/specs/cli-init/spec.mdopenspec/specs/command-generation/spec.mdsrc/core/command-generation/adapters/bob.tssrc/core/command-generation/adapters/oh-my-pi.tssrc/core/command-generation/adapters/opencode.tssrc/core/command-generation/adapters/pi.tssrc/core/command-generation/adapters/qwen.tssrc/core/command-generation/generator.tssrc/core/command-generation/invocation.tssrc/core/command-surface.tssrc/core/init.tssrc/core/migration.tssrc/core/update.tssrc/utils/command-references.tstest/core/command-generation/adapters.test.tstest/core/command-generation/invocation.test.tstest/core/init.test.tstest/core/migration.test.tstest/core/update.test.tstest/utils/command-references.test.ts
Review follow-up: the "every other adapter-backed tool" row swept Amazon Q, Cline and Kilo Code into the plain /opsx-<id> form. Each is now its own row with the wrapper it actually uses, and the command-references tests pass the now-required invocation style explicitly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/supported-tools.md`:
- Around line 21-24: Update the command-path patterns in the table rows for
namespaced and filename-based commands to avoid incorrectly requiring `.md`; use
an extension-neutral pattern or split the entries by each documented extension,
including `.toml`, `.prompt`, and `.prompt.md`, while preserving the existing
tool mappings and command examples.
- Around line 34-36: Update the exception summary following the table to
accurately cover all exception rows: remove the “last four” and universal
`opsx-<id>` claims, distinguish wrapper and extension variations, note that
skills-only tools generate no command files, and identify Codex’s
`openspec-<id>` stem separately.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: e4629f56-4b69-4781-a3d2-1c8dfe560d17
📒 Files selected for processing (3)
docs/how-commands-work.mddocs/supported-tools.mdtest/utils/command-references.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- docs/how-commands-work.md
- test/utils/command-references.test.ts
Review follow-up across the docs, the living specs and two hardcoded strings: - supported-tools: the How To Invoke section no longer splits the "How It Works" profile paragraph from its heading, keys rows on the file shape rather than a `.md` extension the Gemini/Continue/Copilot/Kiro adapters do not use, and drops the Cline/Kilo Code/Amazon Q rows. Kilo Code's docs say the current format drops the `.md` suffix, and the Cline and Amazon Q forms could not be confirmed - a wrong exception row is worse than none, so the caveat now describes the shape without asserting a spelling OpenSpec does not generate. - commands, how-commands-work: the two partial nine-row tables that drifted into #727/#1307 now key on the same file shape and defer to the authoritative table; both note that skill rows carry skill names, which are not command ids. - faq, troubleshooting, installation, README: stop telling skills-only users they have no slash command, stop offering "/opsx autocompletes" as a health check on tools where it never will, and name Hermes with the other adapterless tools. - specs: cli-init no longer claims every tool gets `commands/opsx/`, cli-update no longer frames the hyphen rewrite as OpenCode-specific, and command-generation describes the classifier the code implements. - the legacy-cleanup summary and the pre-selection welcome banner no longer print `/opsx:*` at users whose tool never registers it. - adds the missing changeset; it supersedes the Codex sentence in the pending adapterless-skill-references note. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/faq.md`:
- Line 39: Update the command-recovery guidance in the FAQ to recommend
`openspec init` when command files are missing or the project has not been
initialized, and reserve `openspec update` for refreshing existing command
files. Preserve the surrounding restart and “Getting started” instructions.
In `@docs/installation.md`:
- Around line 69-74: Update the OpenSpec invocation guidance in the installation
prompt to document Kimi Code’s required /skill:openspec-propose form, or
instruct users to follow the exact invocation emitted by openspec init. Preserve
the existing tool-specific forms for other integrations and ensure Kimi is not
directed to /openspec-propose.
In `@src/ui/welcome-screen.ts`:
- Around line 40-42: Update the welcome-screen bullet text around the setup
summary so it does not promise opsx slash commands for skills-only tools; use
wording that accurately covers skills and commands, or render the commands
portion conditionally based on the selected tools while preserving the existing
chalk styling.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 18ad0f24-416b-4f7e-aac4-5c8d2c12d42c
📒 Files selected for processing (18)
.changeset/command-invocation-parity.mdREADME.mddocs/commands.mddocs/faq.mddocs/how-commands-work.mddocs/installation.mddocs/supported-tools.mddocs/troubleshooting.mdopenspec/specs/cli-init/spec.mdopenspec/specs/cli-update/spec.mdopenspec/specs/command-generation/spec.mdopenspec/specs/legacy-cleanup/spec.mdsrc/core/command-generation/adapters/bob.tssrc/core/command-generation/adapters/oh-my-pi.tssrc/core/command-generation/adapters/pi.tssrc/core/legacy-cleanup.tssrc/ui/welcome-screen.tstest/core/legacy-cleanup.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- docs/supported-tools.md
- openspec/specs/command-generation/spec.md
- src/core/command-generation/adapters/oh-my-pi.ts
- docs/commands.md
- src/core/command-generation/adapters/bob.ts
…rded Mutation testing showed five ways to delete parts of this change without failing a single test. All five now fail: - `openspec update` had no flat-tool coverage at all, so the headline upgrade path - an existing Cursor project still carrying `/opsx:` references - was asserted nowhere. Two tests now cover it: one heals a project seeded with stale references, one runs claude+qwen together and pins each to its own form. - the legacy-upgrade getting-started menu is covered for a newly configured Cursor project, so passing the wrong invocation style there is caught. - migration.ts had no flat-tool case: reverting it to a hard-coded `/opsx:propose` passed the whole suite. A qwen-only migration and a claude+qwen disagreement now pin the message. - the unknown-command-id guard in `transformToHyphenCommands` was new behaviour with no test; removing it was invisible. Also tightened assertions the same run showed were weak: the `resolveCommandInvocationStyle` loop compared the implementation against itself, the per-id consistency check asserted only that a style was uniform rather than which one, and the init test's `/opsx-` assertion was satisfied by frontmatter rather than a body reference. The adapter tests that moved to `generateCommand` are renamed after their real subject, and a new case pins the contract those five adapters now rely on: they stay pure formatters and do not rewrite the body themselves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
test/core/update.test.ts (1)
326-327: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAssert a rewritten hyphen reference in each skill.
These negative-only assertions pass if the update removes every
/opsx:*reference rather than rewriting it. Assert a known/opsx-...reference (and, in the stale fixture, verify the replacement seeded a colon reference).
test/core/update.test.ts#L326-L327: assert the refreshed Cursor skill contains its expected hyphenated invocation.test/core/update.test.ts#L350-L354: assert the refreshed Qwen skill contains its expected hyphenated invocation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/core/update.test.ts` around lines 326 - 327, Strengthen the refreshed-skill assertions in test/core/update.test.ts at lines 326-327 and 350-354: in each Cursor and Qwen skill test, assert the expected /opsx-... invocation is present instead of relying only on the absence of /opsx:. For the stale fixture, also verify that the rewrite seeded the expected colon reference, while preserving the existing negative assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@test/core/update.test.ts`:
- Around line 326-327: Strengthen the refreshed-skill assertions in
test/core/update.test.ts at lines 326-327 and 350-354: in each Cursor and Qwen
skill test, assert the expected /opsx-... invocation is present instead of
relying only on the absence of /opsx:. For the stale fixture, also verify that
the rewrite seeded the expected colon reference, while preserving the existing
negative assertion.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: dab321c4-866d-4834-ab35-fb1f11c5d015
📒 Files selected for processing (7)
src/core/command-generation/invocation.tstest/core/command-generation/adapters.test.tstest/core/command-generation/invocation.test.tstest/core/init.test.tstest/core/migration.test.tstest/core/update.test.tstest/utils/command-references.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- src/core/command-generation/invocation.ts
- test/core/command-generation/adapters.test.ts
- test/core/command-generation/invocation.test.ts
- test/core/init.test.ts
Review follow-up: the refreshed-skill checks were negative-only, so a regression that dropped every command reference rather than rewriting it would have passed. Each now pins the invocation its tool registers, the stale fixture asserts it really seeded a colon reference into the skill, and the claude+qwen case pins Claude's namespaced skill alongside Qwen's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
alfred-openspec
left a comment
There was a problem hiding this comment.
The central rewrite is solid, but the current invocation model still treats every flat filename as /opsx-. Amazon Q's prompt library invokes these as @, so its generated command bodies, skills, onboarding hint, and the universal table row are still wrong; please represent that wrapper in the invocation metadata instead of deriving only colon versus hyphen. Also fix the three verified current-head copy issues: use openspec init when commands are missing or the tool was never initialized, include Kimi's /skill:openspec-propose form in the installation prompt, and do not promise opsx commands in the welcome screen for skills-only tools.
The invocation model derived the whole command name from the file an adapter writes, which covers `/opsx:<id>` versus `/opsx-<id>` but not the wrapper around it. Amazon Q loads `.amazonq/prompts/opsx-<id>.md` into its prompt library, invoked as `@opsx-propose`; it registers no slash command, so its command bodies, skills, and the "Getting started" hint all named something the tool never answers to. The name still comes from the file path. The prefix is now adapter metadata (`invocationPrefix`, defaulting to `/`), so it cannot be guessed wrong and a new adapter has to declare it deliberately — invocation.test.ts fails if one appears undeclared. Also fixes three copy issues: - The FAQ told users to run `openspec update` when command files are missing; update only refreshes files for already-configured tools, so a tool that was never initialized needs `openspec init`. - The installation prompt omitted Kimi Code's `/skill:openspec-propose`. - The welcome screen promised "opsx slash commands" before tool selection, which is wrong for skills-only tools that correctly get no command files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two spots still promised a slash command to users who get none: - The welcome screen's quick start shows canonical names (/opsx:propose), but renders one prompt before tools are picked — an Amazon Q user types @opsx-propose and a Codex user $openspec-propose. It now says the spelling varies by tool, so the canonical form stops reading as the literal thing to type. "Getting started" still prints the real form. - The post-setup restart line said "slash commands to take effect" whenever commands were generated. Amazon Q's generated files are prompt library entries, not slash commands, so it now says "the new commands". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The README's one-line exception list and the troubleshooting checklist both enumerated the per-tool spellings and skipped Amazon Q. The troubleshooting entry was actively misleading: it explains that /opsx never autocompletes for tools without command files, and Amazon Q is not one of those — it has command files, they just land in the prompt library. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
All four points from the review are addressed, plus three more instances of the same class found by sweeping for them. CI is green on 1. Amazon Q's
|
| Where | Was | Now |
|---|---|---|
| Welcome quick-start | literal /opsx:propose |
marked "spelling varies by tool" |
| Post-setup restart line | "slash commands to take effect" | "the new commands" |
| README + troubleshooting | exception lists skipped Amazon Q | @opsx-propose named |
The troubleshooting one was actively misleading: it explained that /opsx never autocompletes for tools without command files, and Amazon Q isn't one of those — it has command files, they just land in the prompt library rather than the slash menu.
On CodeRabbit's Cline and Kilo Code claim
Checked and not reproduced — no exception needed. Cline's workflow docs say a workflow is invoked by filename without the extension, and Kilo Code's docs say the current format drops the .md suffix. Both are plain /opsx-<id>, which is what the flat rule already produces. Its update.test.ts nitpick was already handled in e4db819.
Verification
npm run build, npm run lint, npx tsc --noEmit, npx vitest run — 3,334 tests / 115 files, all pass. openspec validate --all is 52/53; the one failure (change/schema-alias-support) is pre-existing on main and unrelated.
alfred-openspec
left a comment
There was a problem hiding this comment.
Verified exact head bc270ea: Amazon Q now uses @opsx-*, Codex/Kimi and other skills-only hints use their real forms, and init/update/migration stay consistent. Build, lint, 1,304 focused tests, and all hosted checks are green.
The migration hint resolves its propose reference through the same transformer as init and update, but no case exercised a non-slash prefix there. The second test is the one that matters: @opsx-propose and /opsx-propose are both "flat", so a style-only model would treat Amazon Q and Qwen as agreeing and advertise one form to both. Reverting the prefix to a constant '/' fails 5 tests, so neither assertion is a tautology. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
alfred-openspec
left a comment
There was a problem hiding this comment.
Re-reviewed exact head 8fea739. The added migration regression proves Amazon Q keeps its @opsx-* form and falls back safely when mixed with slash-command tools; the prior init, update, docs, Kimi, Codex, and skills-only fixes remain intact. Build, lint, 1,407 focused tests, and all hosted checks pass.
The delta restates the whole 'Slash Command Updates' requirement, and its copy of the OpenCode scenario predated Fission-AI#1471 — archiving it would have quietly reverted the spec to calling the hyphen rewrite an OpenCode special case, the hand-maintained framing Fission-AI#1471 removed. Archive on a scratch copy is now purely additive. Also point tasks.md at the generator rather than the deleted transformToHyphenCommands, and enroll devin in the pure-formatter tripwire — it is the one adapter whose private body transform was just removed, so it is the likeliest to have it re-added. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…AI#1167) * proposal: add devin desktop support * feat(adapters): add devin desktop command adapter - Create new Devin Desktop adapter for .devin/workflows/opsx-<id>.md - Register adapter in CommandAdapterRegistry - Export adapter from adapters index - Update docs/supported-tools.md with Devin Desktop entry - Add 'devin' to available tool IDs list Devin Desktop uses the same Cascade workflow system as Windsurf, making it a natural migration path for existing users. * fix(config): add devin desktop to AI_TOOLS Add Devin Desktop entry to AI_TOOLS configuration so that: - getToolsWithSkillsDir() includes 'devin' as a valid tool ID - getWorkspaceSkillToolIds() returns 'devin' in the list - parseWorkspaceSkillToolsValue() accepts 'devin' as valid input - openspec init --tools devin works correctly This fixes validation failures where 'devin' was documented in docs/supported-tools.md but not recognized by validation functions that derive valid IDs from AI_TOOLS. * fix(devin-adapter): escape implicit YAML scalars in frontmatter Update escapeYamlValue to detect and quote implicit YAML scalars that would be coerced by parsers: - Booleans: true, false, yes, no, on, off - Null variants: null, ~ - Numbers: integers, floats, exponentials, hex (0x), octal (0o) - Edge cases: standalone dash (-) and dot (.) This ensures values like 'true', '123', 'null' remain strings in YAML frontmatter instead of being interpreted as booleans, numbers, or nulls. Preserves existing escaping logic for special characters and newlines. * test(devin-adapter): add comprehensive tests for Devin Desktop adapter Add test coverage for the Devin Desktop adapter including: - Command reference transformation from colon to hyphen syntax - YAML frontmatter escaping for special characters and implicit scalars - File path generation for workflows - Integration with available tools detection - Init and update command workflows * Add cross-platform testcase. * fix(devin): refresh deltas against canonical specs and point skills at skills Addresses the two release blockers on this PR. Archive: the change's MODIFIED blocks were written against an older canonical `cli-init`, so `openspec archive add-devin-desktop-support` aborted rather than merging. The deltas are regenerated from the current canonical specs (cli-init `Skill Generation` + `Slash Command Generation`, cli-update `Slash Command Updates`, and a new `ai-tool-paths` delta for the `.devin` skillsDir), each restating every existing scenario so archive is purely additive. Invocation syntax: only Devin Desktop reads `.devin/workflows/`, so a `/opsx-*` workflow reference is dead text on Devin Local, which supports skills only. Devin now takes the skill-reference transformer, so skill bodies and the getting-started hint say `/openspec-*`. Workflow bodies keep hyphen references, applied by devinAdapter itself. The adapter also drops its private copy of escapeYamlValue / formatTagsArray in favor of the shared helpers main centralized in Fission-AI#1447, which quote unconditionally and escape control characters. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(devin): correct commands-only hint, fill doc gaps, cover both surfaces Follow-up from adversarial review of the previous commit. The devin special case in getTransformerForTool was unconditional, so under commands-only delivery — where `.devin/skills/` is deleted — the getting-started hint named `/openspec-propose`, a skill that is not on disk. Devin now takes the skill transformer only when skills are generated, and the hyphen form otherwise. The cli-init delta records the fallback, and a unit test pins all three delivery modes. Docs: `devin` was missing from the `--tools` list in docs/cli.md (which mirrors the list supported-tools.md already had) and from the command-syntax tables in docs/commands.md and docs/how-commands-work.md. The supported-tools row gains a footnote citing Cognition's docs for the `.windsurf/` -> `.devin/` move and the Devin Local workflow gap. Tests: init and update now assert both surfaces — workflows carry `/opsx-*`, skills carry `/openspec-*`, neither carries `/opsx:` — and update checks the seeded skill was actually refreshed. Adds the negative detection case. Drops three devin-only YAML assertions that duplicated, less rigorously, the registry-derived escaping matrix that now enrolls devin automatically. Also reverts an unrelated zcode export and lingma reorder that a merge resolution had pulled into adapters/index.ts. zcodeAdapter is registered but missing from that barrel on main; that is a pre-existing gap and belongs in its own change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(devin): name the right command in the profile migration notice The profile-migration notice printed by both `init` and `update` hardcoded `/opsx:propose` for every adapter-backed tool. Devin registers no such command on any surface — its workflows answer to `/opsx-propose` and its skills to `/openspec-propose` — so an upgrading Devin user was told to run something that does not exist: Migrated: custom profile with 6 workflows New in this version: /opsx:propose. The reference now goes through getTransformerForTool, the same call init.ts already makes for the getting-started hint. Devin prints `/openspec-propose`; opencode and the other filename-invoked tools are corrected to `/opsx-propose` as a side effect; claude is unchanged. Also corrects two inherited false claims in the cli-update delta — Devin workflows carry no OpenSpec markers, and update writes every profile workflow rather than only refreshing files that already exist, which the PR's own test demonstrates. Qualifies the supported-tools footnote for commands-only delivery, and strips trailing whitespace. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(devin): keep the cli-update delta in step with the canonical spec The delta restates the whole 'Slash Command Updates' requirement, and its copy of the OpenCode scenario predated Fission-AI#1471 — archiving it would have quietly reverted the spec to calling the hyphen rewrite an OpenCode special case, the hand-maintained framing Fission-AI#1471 removed. Archive on a scratch copy is now purely additive. Also point tasks.md at the generator rather than the deleted transformToHyphenCommands, and enroll devin in the pure-formatter tripwire — it is the one adapter whose private body transform was just removed, so it is the likeliest to have it re-added. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(adapters): follow the Windsurf rename to Devin Desktop, with migration Windsurf was rebranded to Devin Desktop on 2026-06-02 and its config directory moved: `.devin/` is the preferred read+write location, `.windsurf/` a legacy read-only fallback. Devin Local does not read `.windsurf/` at all, so an existing Windsurf user's OpenSpec files are invisible to it. Carrying `devin` as a second tool id alongside `windsurf` would list one product twice and leave upgraders with two parallel installs — `openspec update` even told them to create the second one ("Detected new tool: Devin Desktop"). This follows the rename instead, as the repo already did for Kimi CLI -> Kimi Code: - `windsurf` is retired as a tool id; `devin` takes its place, with `detectionPaths: ['.devin', '.windsurf']` so pre-rebrand projects are still recognized. The Windsurf adapter is replaced, not duplicated. - `TOOL_ID_ALIASES` keeps `--tools windsurf` resolving, so existing setup scripts and CI keep working; they now configure `.devin/`. - OpenSpec-managed skills (`openspec-*`) and command files (`opsx-*`) under `.windsurf/` move to `.devin/`. The kimi migration handled skills only; command files now move too, deriving the legacy path from the adapter's own getFilePath rather than hard-coding a layout. - The move is offered, not taken: nothing on disk distinguishes a user who took the rebrand from one still on a pre-rebrand Windsurf build that reads only `.windsurf/`. `openspec update` explains the rename and asks; --force and non-interactive runs migrate; declining leaves every file untouched and says what that costs. Files the user wrote are never moved. Also gives Devin its own row in the authoritative invocation table — the catch-all row claimed `/opsx-<id>` for both agents, which is wrong for Devin Local. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(devin): stop the migration from deleting anything it does not own An adversarial pass found two ways the move destroyed files. Symlinked roots wiped the install. `ln -s .devin .windsurf` is a realistic way to straddle the rebrand, and it makes source and destination the same file — so the "destination exists, drop the legacy copy" branch deleted the only copy. Twelve generated files, gone, and not regenerated: the wipe happens before tool detection, so update then reported no configured tools. Both roots are now realpath'd and a self-move is skipped. User content inside an OpenSpec-managed path was deleted. The same branch rm -rf'd the whole legacy skill directory, taking a hand-written reference.md beside SKILL.md with it, and deleted a legacy command file even when the user had edited it. Now only SKILL.md is removed from a skill directory, and a command file is removed only when byte-identical to the one that survives — an edit is left where it is. Also: declining the move stranded the user. `update` then printed "No configured tools found. Run openspec init", which is wrong — the project is configured, just in the directory OpenSpec no longer writes. It now says so and how to resume. A closed stdin during the prompt aborted the whole update; it is treated as a decline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(devin): add a changeset for the Windsurf rename and migration Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(devin): move only SKILL.md, never the skill directory around it alfred caught a data-loss path the earlier fix missed. When the destination did not yet exist, migration renamed the whole legacy skill directory into `.devin/` — carrying any file the user kept beside `SKILL.md` with it. That destination is a directory OpenSpec owns and removes on its own: under commands-only delivery, or for a workflow outside the active profile. So the move handed the user's file to a later rm and it vanished. Reproduced on `d94af8b`: with `delivery: commands`, a `reference.md` beside a legacy `SKILL.md` was gone after `openspec update`. Only `SKILL.md` crosses now, in both branches; anything else stays under the legacy root, and the legacy directory is still removed when the move leaves it empty. Regression tests cover the commands-only and deselected-workflow cases and both fail against the previous code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(devin): treat an edited skill the way an edited command is already treated A final adversarial pass found the two paths disagreeing. When both roots held the same file with different content, the command path compared bytes and kept the user's version; the skill path deleted it with no comparison — so one `openspec update` destroyed an edited SKILL.md while preserving an edited opsx-*.md in the same project. Both now share one `classifyManagedFile` rule: move when the destination is empty, drop the legacy copy only when byte-identical, otherwise leave it. Anything left behind is reported, so a user who customized a file knows two copies exist rather than discovering it later. Note on the other finding from that pass: OpenSpec regenerating or pruning the files it owns is long-standing behavior, not something this PR introduces. Verified against main — an edited SKILL.md under a deselected workflow, and an edited selected skill and command, are all destroyed by `openspec update` on 9a937cb too. No regression, so left alone here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(devin): report divergent legacy files even when nothing is movable collectLegacyToolMigrations only returned a result when something moved, so a project where EVERY legacy file differs from its counterpart produced no output at all — two divergent copies and not a word about them. That is the one case where the report matters most, since it is entirely made of files the migration deliberately refused to touch. Kept-only results are retained now. Callers gate on hasMovableContent(), so a kept-only result reports what was left without offering to move nothing and without claiming a migration that did not happen. Also reworded the notice. A legacy file can differ because the user edited it or simply because an older OpenSpec generated it, so it no longer asserts an edit — it states that nothing was overwritten and leaves the user to compare the two copies. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(devin): stop matching the unrelated profile-migration line The kept-only regression asserted no line matched /Migrated\s*:/, which also matches OpenSpec's profile migration message, "Migrated: custom profile with N workflows". That line only prints when the global config has no profile yet — true on a fresh CI runner, false on a developer machine that has run OpenSpec before — so the test passed locally and failed on all three CI platforms. Now matched on the directory arrow, ".windsurf → .devin", which is specific to a migration report and unaffected by config state. Reproduced both ways with an empty XDG_CONFIG_HOME: the old assertion fails there, the new one passes, and the full suite is green under CI's XDG_CONFIG_HOME + VITEST_MAX_WORKERS=4. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Clay Good <hi@claygood.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Hermes is skills-only (no command adapter), and zcode's namespaced commands register /opsx:<id>, not /opsx-* — the release notes must not reintroduce the invocation-spelling confusion #1471 removed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-audit follow-ups (Fission-AI#1475) * fix(archive): make the scenario-drift check fence-aware parseScenarioBlocks matched #### Scenario: headers on raw lines while the validator's countScenarios masks fenced code blocks (Fission-AI#1151). The drift check (Fission-AI#1391) inherited the raw scan, so a fenced scenario example in the current spec aborted an archive that validate had passed, and a fenced name in the MODIFIED block counted as keeping a scenario the block had actually dropped. Build the shared code-fence mask and skip masked lines in both the header scan and the block-end scan. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(update): tear down the redirected request when the budget expires The overall request budget was armed inside the first send() and its callback closed over that hop's request. After a redirect the timer destroyed the already-dead first request, so a redirect target that trickled bytes kept resetting its idle timeout and held the socket open until the body-size cap. Track the in-flight request and have the budget timer destroy whichever one is open. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(release): add changesets for user-facing changes missing from the 1.7.0 notes 18 feat/fix commits merged since v1.6.0 without a changeset, so the pending Version Packages PR would have released them silently: five tool integrations (ZCode, Hermes, CodeArts, Kimi Code rename, Codex skills-only), skills.sh distribution, symlinked schema dirs, nested spec discovery, drift multiplicity, checkbox markers, Windows welcome input, npx avoidance, doctor store drift, local dates, missing-core-workflows warning, store-aware main specs, open-questions guidance, and spec content guidance. Plus changesets for this branch's two fixes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(adapters): escape TOML-active characters in Gemini command files The gemini adapter interpolated the description into a TOML basic string and the body into a multiline basic string with no escaping. Every current template value happens to be safe; the first description with a double quote or backslash would silently produce invalid TOML for all Gemini command files. Escape both contexts (Fission-AI#1447 fixed the same class for the YAML adapters but scoped itself to YAML). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(update): harden install detection and redirect handling Three follow-ups from the release audit: - A path segment literally named volta (a user or project directory) classified the install as volta-managed and swallowed the upgrade offer. The undotted spelling now requires volta's own tools/image layout, matching how pnpm and yarn already demand corroboration. - The Windows npm-ownership fallback checked that the npm prefix exists, which is true of any X\node_modules\pkg tree, hand-copied ones included. Corroborate with the openspec.cmd shim npm actually writes. - A https registry redirecting to plain http was followed; a MITM on that reply controls the newer-version answer. Refuse the downgrade. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(cli): export zcodeAdapter from the barrel and sync a completion description zcode was registered but missing from the adapters barrel (its test imported the module directly), and the completion registry still carried the pre-Fission-AI#1062 description for the instructions command. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(parser): strip a UTF-8 BOM before parsing specs and deltas A BOM-prefixed delta spec (Windows editors, PowerShell Out-File) failed validate and archive with 'No delta sections found' because the first line never matched '## ADDED Requirements'. Strip the BOM in both normalizers, the same way tool detection already does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cli): reject over-long change names with a validation message A 300-character change name surfaced two raw ENAMETOOLONG errno dumps from stat and mkdir. Bound the name at 200 characters in validateChangeName so the failure is a normal validation error. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(archive): finish the early-sync no-op rules for MODIFIED and RENAMED Two asymmetries left over from the Fission-AI#1376/Fission-AI#1386/Fission-AI#1437 no-op work: - MODIFIED counted every delta as applied even when the block was byte-equal to the main spec, so a fully early-synced change rewrote the file (normalization churn), printed '~ N modified', and reported specsUpdated: true where its ADDED/REMOVED/RENAMED twins print 'Specs already in sync; no files changed.' Count only real replacements. - RENAMED's already-synced skip (source gone, target present) had no near-miss guard: a case/whitespace variant of the source still in the spec means a typo'd header, and REMOVED already hard-aborts on that signal. Apply the same guard, excluding the target itself so a case-only rename still no-ops. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(validate): stop reporting an unreadable specs dir as 'no deltas' The delta-validation loop swallowed every error as 'if no specs dir, treat as no deltas', so an EACCES capability folder produced the misleading 'Change must have at least one delta' while archive let the same error propagate. Tolerate only ENOENT and ENOTDIR (a stray specs file); anything else stays loud, matching discoverSpecFiles' documented fail-loud contract. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(update): say when commands-only delivery leaves a tool with nothing Under delivery: commands, update removed the skills of adapterless skills-only tools (Hermes, Kimi Code, Vibe, CodeArts, ForgeCode) without a word — leaving zero OpenSpec artifacts while the tool's detection dir kept re-suggesting an init that would also generate nothing. Print the same per-tool configuration correction init already prints, pointing at 'openspec config set delivery both'. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(completion): honor $ZSH and $ZSH_CUSTOM for Oh My Zsh installs The installer used a set $ZSH only as an is-installed signal and then wrote to ~/.oh-my-zsh regardless, so a custom OMZ location got a freshly created ~/.oh-my-zsh tree that no shell ever loads — and isInstalled/uninstall looked in the same wrong place. Route every path through the $ZSH/$ZSH_CUSTOM-aware helpers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(init): make the static welcome screen wait for the Enter it asks for The static branch printed 'Press Enter to select tools...' and returned immediately, so the Enter landed in the tool picker and submitted the pre-selected set sight-unseen. Fission-AI#1462 routed reduced-motion, OPENSPEC_NO_ANIMATION, --no-animation, NO_COLOR, and narrow-terminal users onto this path. Wait in a TTY; drop the prompt line when there is no TTY to wait on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(feedback): keep the manual fallback on every gh failure Only missing-gh and unauthenticated flows showed the formatted feedback and pre-filled submission URL; issues-disabled, network, or rate-limit failures printed gh's stderr and discarded the path to submit what the user had already typed. Route those through the same manual fallback, preserving gh's exit code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(update): model the npm shim in the Windows prefix fixture The ownership corroboration now checks for the openspec.cmd shim npm writes beside node_modules; the Homebrew-prefix fixture built the layout without it, so the test failed on windows-pwsh. Write the shim in the fixture and pin the inverse: the same shape with nothing npm wrote (a hand-copied portable tree) is not an npm install. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(update): require volta's full tools/image layout for the undotted spelling The corroboration used has('tools', 'image'), which is some() — volta AND (tools OR image) — so /srv/volta/tools/apps/... still classified as a Volta install and swallowed the upgrade offer. Require both segments, matching the real %LOCALAPPDATA%\Volta\tools\image layout. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(adapters): escape control characters in Gemini multiline prompts escapeTomlMultilineBasicString handled backslashes and quote-triples but not the C0 controls that are as invalid in a multiline basic string as in a single-line one. Reuse TOML_CONTROL_CHARS, applied last so the escapes it introduces are not re-doubled. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(completion): finish the $ZSH_CUSTOM support and isolate it in tests The fpath verification advice still grepped the literal custom/completions, which a relocated $ZSH_CUSTOM need never contain — grep the actual directory instead. The installer tests cleared only $ZSH, so on a machine exporting $ZSH_CUSTOM they would have written into (and deleted from) the developer's real OMZ custom dir — the same leakage class Fission-AI#1400 fixed for $ZSH. Clear/restore both, and pin the custom-location paths with two new tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(release): correct the hermes and zcode changeset wording Hermes is skills-only (no command adapter), and zcode's namespaced commands register /opsx:<id>, not /opsx-* — the release notes must not reintroduce the invocation-spelling confusion Fission-AI#1471 removed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(feedback): pin the manual fallback on a non-label gh failure The new reportGhFailure output (formatted feedback + pre-filled URL) had no coverage; the network-failure test now asserts it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(completion): match fpath entries as literal strings in the OMZ guidance The verification advice interpolated the completions dir into grep "<dir>" where regex metacharacters make the check unreliable and quotes could break the displayed command. Print one fpath entry per line and match with grep -F on a shell-quoted literal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(adapters): never emit a bare carriage return in Gemini TOML prompts A lone CR is illegal in a multiline basic string — Python 3.13 tomllib rejects the file — and the control-char pass deliberately skipped it on the assumption it only appears as CRLF. Normalize CRLF to LF and escape any remaining CR as \r. The escaping guarantee is now parser-backed: smol-toml (new devDependency) round-trips every hostile body in the regression matrix (lone CR, CRLF, CR before a quote run, NUL/VT/FF, trailing backslash, four- and five-quote runs), and the same outputs were verified against Python tomllib. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * build(nix): update the pnpm deps hash for the smol-toml devDependency The lockfile changed, so the fixed-output derivation hash moved; value taken from the CI mismatch report. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Status: LGTM — behavior-preserving for Claude Code, corrective for 22 other tools, 3,334 tests pass.
What was wrong
OpenSpec writes your slash commands, then tells you what to type. For most tools it told you wrong.
Command bodies are authored with
/opsx:apply-style references. Only 7 of the 28 tools with a command adapter actually register that name — the ones whose files live in anopsx/directory (Claude Code, CodeBuddy, Crush, Gemini, Lingma, Qoder, ZCode). The other 21 write.../opsx-apply.md, where the filename is the command, so their palettes register/opsx-apply. Five of those 21 were rewritten to the hyphen form; the remaining 16 — including Cursor, GitHub Copilot, Windsurf, Kilo Code, Factory Droid and Cline — were not.The result, reported in #1307 and #727: the generated command bodies, the generated
SKILL.mdfiles, and the "Getting started" line printed byopenspec initall advertised a command the tool had never heard of.On
main, a single generated Cursor file contradicts itself — the frontmatternames the command
/opsx-apply, the body then tells you to run/opsx:apply:Codex had the same problem in a sharper form: it generates no command files at all, yet its skills said
/opsx:apply. Codex invokes skills as$openspec-apply-change(#1379, #1110).How it was fixed
An invocation has two parts, and only one can be read off the file an adapter writes. The name comes from the path; the prefix is the tool's own and is declared as adapter metadata (
invocationPrefix, defaulting to/):.claude/commands/opsx/apply.md/opsx:apply.cursor/commands/opsx-apply.md/opsx-apply.amazonq/prompts/opsx-apply.md@opsx-apply$openspec-apply-changeAmazon Q is the one tool that registers no slash command at all: it loads those files into its prompt library, invoked with
@. Deriving only colon-versus-hyphen would have handed it/opsx-apply, which is still a name it never answers to — so the prefix is metadata, not a guess. A tripwire test fails if a new adapter grows an undeclared prefix.That model drives every tool-specific surface: command bodies,
SKILL.mdcross-references, and theinit/update/ migration hints. A newly added adapter is classified by its own path, so this cannot drift again — which is how the old list ended up 16 tools behind.Proof it works
Verified end to end across all three delivery modes (
skills,commands,both) and on the upgrade path: a project generated by the old build is healed byopenspec update, with zero stale references left in any tool directory.New tests:
invocation.test.tsasserts every registered adapter is classified from its own file path against an expected table (so a new adapter can't silently pick the wrong form),update.test.tsheals a Cursor project seeded with stale/opsx:references and pins claude+qwen to their own forms in one run, andmigration.test.tscovers a flat-tool upgrade message. Per-adapter reference tests moved to the generator layer, which now owns the rewrite, with a new case pinning that those adapters stay pure formatters.A mutation run over the change found five ways to delete parts of it without failing a test — the whole
update.tsandmigration.tshunks among them. All five now fail, verified by re-applying each mutation.npm run build,npm run lint,npx vitest run(3,334 tests, 115 files) all pass.Is this a breaking change?
No. Command filenames and paths are untouched — every command a user has today keeps working. What changes is the text OpenSpec generates inside those files and prints to the terminal, which moves from a name that did not resolve to one that does. Claude Code output is byte-identical.
Notes
nameto/opsx:<id>while keeping theopsx-<id>.mdfilename. Cursor's docs state commands are stored as.cursor/commands/[command].mdand the reporter's own palette screenshot shows the hyphen form, so the filename wins and the frontmatter change would not have taken effect. fix: use colon syntax for Cursor slash commands #1335 is also currently conflicting.patternfield; deriving fromgetFilePathavoids the drift that caused this bug.commands.mdandhow-commands-work.mdare the same drift that caused this bug, so they now key on the file shape and defer to one authoritative table.faq.md,troubleshooting.md,installation.mdandREADME.mdno longer tell skills-only users they have no slash command, or offer "type/opsxand watch autocomplete" as a health check on tools where it never completes.cli-init,cli-updateandlegacy-cleanupspecs updated to match the code.@form is now confirmed and implemented. An earlier revision dropped this row because I could not confirm the syntax. AWS's prompt-management docs and aws/amazon-q-developer-cli#2243 both confirm@prompt-name, soinvocation.tsnow renders a full name rather than a two-valued style — the follow-up that note anticipated, done here..mdsuffix, and Cline's workflow docs say a workflow is invoked by filename without the extension. Both are plain/opsx-<id>, which is what the flat rule already produces.delivery: commandsusers on a plainopenspec update. Command files carry no version stamp, soneedsUpdatestaysfalsefor commands-only tools andupdatereports "already up to date" without rewriting them (update --forceand re-initboth work today). That is a pre-existing bug onmainwhich fix(update): refresh command files for tools configured without skills #1442 fixes with tests; I have not duplicated it here.transformToHyphenCommandsbecametransformCommandInvocations(text, invocation)now that the prefix varies, and rewrites only known command ids, matchingtransformToSkillReferences. The old unanchored/\/opsx:/gwas harmless with 5 adapters and no longer worth the blast radius at 22./opsx:*).Closes #1307
Closes #727
Closes #1379
Closes #1110
Refs #1129
Summary by CodeRabbit
/opsx:propose) and added tool-specific “How To Invoke” tables with correct per-tool spellings (including Codex$openspec-*forms) and guidance on using exactly what setup/autocomplete shows./opsx:<id>references.