Skip to content

fix(adapters): reference slash commands by the names each tool registers - #1471

Merged
clay-good merged 10 commits into
mainfrom
fix/command-invocation-parity
Jul 28, 2026
Merged

fix(adapters): reference slash commands by the names each tool registers#1471
clay-good merged 10 commits into
mainfrom
fix/command-invocation-parity

Conversation

@clay-good

@clay-good clay-good commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

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 an opsx/ 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.md files, and the "Getting started" line printed by openspec init all advertised a command the tool had never heard of.

On main, a single generated Cursor file contradicts itself — the frontmatter
names the command /opsx-apply, the body then tells you to run /opsx:apply:

$ openspec init --tools cursor                                   # on main
Getting started:
  Start your first change: /opsx:propose "your idea"             # Cursor registers /opsx-propose

$ grep -o '/opsx[:-][a-z-]*' .cursor/commands/opsx-apply.md | sort -u
/opsx-apply                                                      # frontmatter name
/opsx:apply                                                      # body cross-references
/opsx:archive
/opsx:continue

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 /):

Adapter writes Tool registers
.claude/commands/opsx/apply.md /opsx:apply
.cursor/commands/opsx-apply.md /opsx-apply
.amazonq/prompts/opsx-apply.md @opsx-apply
no command file (Codex) $openspec-apply-change

Amazon 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.md cross-references, and the init / 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

$ openspec init --tools claude,cursor,amazon-q,kimi,codex      # after
Getting started:
  Start your first change: /opsx:propose "your idea" (Claude Code)
  Start your first change: /opsx-propose "your idea" (Cursor)
  Start your first change: @opsx-propose "your idea" (Amazon Q Developer)
  Start your first change: /skill:openspec-propose "your idea" (Kimi Code)
  Start your first change: $openspec-propose "your idea" (Codex)

$ grep -o '@opsx-[a-z-]*' .amazonq/prompts/opsx-apply.md | sort -u
@opsx-apply
@opsx-archive
@opsx-continue          # no /opsx: and no /opsx- survive anywhere under .amazonq/

$ grep -o '/opsx[:-][a-z-]*' .cursor/commands/opsx-apply.md | sort -u
/opsx-apply
/opsx-archive
/opsx-continue

$ grep -o '/opsx[:-][a-z-]*' .claude/commands/opsx/apply.md | sort -u
/opsx:apply
/opsx:archive
/opsx:continue          # unchanged

$ grep -o '\$openspec-[a-z-]*' .codex/skills/openspec-apply-change/SKILL.md | sort -u
$openspec-apply-change

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 by openspec update, with zero stale references left in any tool directory.

New tests: invocation.test.ts asserts 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.ts heals a Cursor project seeded with stale /opsx: references and pins claude+qwen to their own forms in one run, and migration.test.ts covers 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.ts and migration.ts hunks 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


Closes #1307
Closes #727
Closes #1379
Closes #1110
Refs #1129

Summary by CodeRabbit

  • Documentation
    • Standardized canonical slash-command naming (/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.
    • Updated Quick Start, FAQ, installation, troubleshooting, and onboarding/cleanup text to reflect tool-specific command vs skill invocation behavior.
  • Bug Fixes
    • Improved per-tool command invocation handling so generated references match how each tool registers commands (flat vs namespaced), including preserving unknown /opsx:<id> references.
  • Tests
    • Added/expanded coverage for invocation-style detection and end-to-end generated command/skill content across tools.

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>
@clay-good
clay-good requested a review from a team as a code owner July 28, 2026 13:06
@clay-good
clay-good requested review from alfred-openspec and removed request for a team July 28, 2026 13:06
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Command 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.

Changes

Command invocation and reference formatting

Layer / File(s) Summary
Invocation-aware command generation
src/core/command-generation/..., test/core/command-generation/...
Command paths determine whether known /opsx:<id> references become /opsx-<id>, while adapters format already-transformed bodies and tests validate both invocation styles.
Tool-specific transformer selection
src/utils/command-references.ts, src/core/command-surface.ts, src/core/{init,migration,update}.ts, test/core/{init,migration,update}.test.ts, test/utils/command-references.test.ts
Transformer selection uses delivery, command-surface capability, and invocation style for generated skills, migration text, onboarding hints, and command references.
Specifications and release metadata
openspec/specs/..., .changeset/command-invocation-parity.md
Specifications and patch release notes describe adapter-path generation and tool-specific command-reference spelling.
Invocation conventions documentation
README.md, docs/*.md
Documentation explains canonical command naming, path-dependent syntax, tool-specific wrappers, Codex skill invocation, and troubleshooting guidance.
User-facing surface wording
src/core/legacy-cleanup.ts, src/ui/welcome-screen.ts, test/core/legacy-cleanup.test.ts
Cleanup and welcome messages refer to OpenSpec skills and commands without the former fixed slash-command wording.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: alfred-openspec, tabishb

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% 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
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: generating slash-command references using each tool’s registered command name.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/command-invocation-parity

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 28, 2026

Copy link
Copy Markdown

Deploying openspec-docs with  Cloudflare Pages  Cloudflare Pages

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

View logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/utils/command-references.ts (1)

139-152: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Sound 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 invocationStyle a required (non-optional) parameter is intentional per the doc comment, but see the companion comment in test/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

📥 Commits

Reviewing files that changed from the base of the PR and between fc886af and bf2b09a.

📒 Files selected for processing (23)
  • docs/commands.md
  • docs/how-commands-work.md
  • docs/supported-tools.md
  • openspec/specs/cli-init/spec.md
  • openspec/specs/command-generation/spec.md
  • src/core/command-generation/adapters/bob.ts
  • src/core/command-generation/adapters/oh-my-pi.ts
  • src/core/command-generation/adapters/opencode.ts
  • src/core/command-generation/adapters/pi.ts
  • src/core/command-generation/adapters/qwen.ts
  • src/core/command-generation/generator.ts
  • src/core/command-generation/invocation.ts
  • src/core/command-surface.ts
  • src/core/init.ts
  • src/core/migration.ts
  • src/core/update.ts
  • src/utils/command-references.ts
  • test/core/command-generation/adapters.test.ts
  • test/core/command-generation/invocation.test.ts
  • test/core/init.test.ts
  • test/core/migration.test.ts
  • test/core/update.test.ts
  • test/utils/command-references.test.ts

Comment thread docs/supported-tools.md Outdated
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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between bf2b09a and 6ce948a.

📒 Files selected for processing (3)
  • docs/how-commands-work.md
  • docs/supported-tools.md
  • test/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

Comment thread docs/supported-tools.md Outdated
Comment thread docs/supported-tools.md Outdated
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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6ce948a and 4e5e358.

📒 Files selected for processing (18)
  • .changeset/command-invocation-parity.md
  • README.md
  • docs/commands.md
  • docs/faq.md
  • docs/how-commands-work.md
  • docs/installation.md
  • docs/supported-tools.md
  • docs/troubleshooting.md
  • openspec/specs/cli-init/spec.md
  • openspec/specs/cli-update/spec.md
  • openspec/specs/command-generation/spec.md
  • openspec/specs/legacy-cleanup/spec.md
  • src/core/command-generation/adapters/bob.ts
  • src/core/command-generation/adapters/oh-my-pi.ts
  • src/core/command-generation/adapters/pi.ts
  • src/core/legacy-cleanup.ts
  • src/ui/welcome-screen.ts
  • test/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

Comment thread docs/faq.md Outdated
Comment thread docs/installation.md Outdated
Comment thread src/ui/welcome-screen.ts Outdated
…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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
test/core/update.test.ts (1)

326-327: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4e5e358 and 101fece.

📒 Files selected for processing (7)
  • 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
  • test/core/migration.test.ts
  • test/core/update.test.ts
  • test/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 alfred-openspec left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

clay-good and others added 4 commits July 28, 2026 10:53
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>
@clay-good

Copy link
Copy Markdown
Collaborator Author

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 bc270ea (14 checks, 0 failures, tests on linux/macOS/Windows).

1. Amazon Q's @ wrapper is now in the invocation metadata

You were right that deriving only colon-versus-hyphen can't represent this. An invocation now has two parts, and only one is derivable:

  • The name still comes from getFilePath — that's the part that drifted before, so it stays derived.
  • The prefix is adapter metadata: invocationPrefix, defaulting to /. Amazon Q's adapter declares '@'.
export interface CommandInvocation {
  style: CommandInvocationStyle;  // from the file the adapter writes
  prefix: string;                 // the tool's own, declared not guessed
}

CommandInvocationStyle alone is no longer passed anywhere; transformToHyphenCommands became transformCommandInvocations(text, invocation), and resolveCommandInvocationStyle became resolveCommandInvocation. That one change covers all four surfaces you listed — command bodies, skills, the onboarding hint, and the docs table — because they all already went through the same transformer.

A tripwire test fails if a new adapter grows an undeclared prefix, so this can't drift the way the old tool list did.

Syntax confirmed, not guessed — an earlier revision of this PR dropped the Amazon Q row precisely because I couldn't confirm it: AWS prompt management docs and aws/amazon-q-developer-cli#2243 both give @prompt-name.

$ openspec init --tools claude,cursor,amazon-q,kimi,codex
Getting started:
  Start your first change: /opsx:propose "your idea" (Claude Code)
  Start your first change: /opsx-propose "your idea" (Cursor)
  Start your first change: @opsx-propose "your idea" (Amazon Q Developer)
  Start your first change: /skill:openspec-propose "your idea" (Kimi Code)
  Start your first change: $openspec-propose "your idea" (Codex)

$ for d in .claude .cursor .amazonq .kimi-code .codex; do ... done
.claude      /opsx:
.cursor      /opsx-
.amazonq     @opsx-
.kimi-code   /skill:openspec-
.codex       $openspec-

No stray /@opsx, @/opsx, or @opsx: anywhere — the rewrite consumes the whole /opsx:. openspec update --force also heals a project generated before this fix, covered by a new test.

2–4. The three copy issues

  • openspec init when commands are missing — fixed in faq.md. Confirmed the behavior first: update iterates getConfiguredToolsForProfileSync, so a tool that was never initialized isn't in the config and update does nothing for it.
  • Kimi's /skill:openspec-propose — added to the installation prompt, along with Amazon Q's @opsx-propose.
  • Welcome screen — the "opsx slash commands" bullet is now "Workflow commands, if supported". It renders one prompt before tool selection (init.ts:192 vs :199), so it can't be made conditional; the honest fix is to stop asserting. Kept to 35 chars to hold the existing 59-column animation budget.

Three more of the same, found by sweeping

Your review named four sites; the same false promise appeared in three others:

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 run3,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 alfred-openspec left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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 alfred-openspec left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

@clay-good
clay-good added this pull request to the merge queue Jul 28, 2026
Merged via the queue into main with commit 9a937cb Jul 28, 2026
16 checks passed
@clay-good
clay-good deleted the fix/command-invocation-parity branch July 28, 2026 17:17
clay-good added a commit to mehdishahdoost/OpenSpec that referenced this pull request Jul 28, 2026
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>
pull Bot pushed a commit to edisplay/OpenSpec that referenced this pull request Jul 28, 2026
…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>
clay-good added a commit that referenced this pull request Jul 28, 2026
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>
birhantprkc pushed a commit to birhantprkc/OpenSpec that referenced this pull request Jul 29, 2026
…-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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants