Skip to content

feat(adapters): follow the Windsurf rename to Devin Desktop - #1167

Merged
clay-good merged 23 commits into
Fission-AI:mainfrom
mehdishahdoost:feat/add-devin-desktop-support
Jul 28, 2026
Merged

feat(adapters): follow the Windsurf rename to Devin Desktop#1167
clay-good merged 23 commits into
Fission-AI:mainfrom
mehdishahdoost:feat/add-devin-desktop-support

Conversation

@mehdishahdoost

@mehdishahdoost mehdishahdoost commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Status

Approved and ready to merge. This is @mehdishahdoost's PR, brought up to date with main and extended to cover the upgrade path for people who already use Windsurf. The original goal is unchanged and fully delivered: OpenSpec supports Devin Desktop. The scope grew for one reason, explained below.

What was missing

OpenSpec supported Windsurf but not Devin Desktop. Windsurf was rebranded to Devin Desktop on June 2, 2026, and the rebrand moved the config directory: .devin/ is now the preferred read + write location and .windsurf/ a legacy read-only fallback. Every Devin user running openspec init was landing in the deprecated path.

Why this PR changed shape

The original PR added devin as a second tool alongside windsurf. Reviewing the upgrade path showed that leaves existing users worse off, not better:

  • Devin Desktop creates .devin/ itself, so anyone who took the rebrand ends up with both directories. OpenSpec then said "Detected new tool: Devin Desktop. Run 'openspec init' to add it." It is not a new tool — it is the tool they already had, renamed. Following that advice produced 24 OpenSpec files across two directories for one editor, and two contradictory getting-started lines.
  • Anyone who didn't follow it got a silent no-op from openspec update and stayed on the deprecated path forever.
  • Devin Local doesn't read .windsurf/ at all. So an existing Windsurf user's OpenSpec files are completely invisible to that agent. The "legacy fallback" only helps on Devin Desktop.
  • The picker would list one product twice, and the "30+ AI assistants" count would double-count it.

So this follows the rename instead of duplicating the tool — exactly what OpenSpec already did for Kimi CLI → Kimi Code (.kimi.kimi-code).

What it does

For a new Devin user: openspec init --tools devin writes .devin/workflows/opsx-<id>.md and .devin/skills/openspec-*/SKILL.md. Nothing else to know.

For an existing Windsurf user: openspec update explains the rebrand and offers to move your OpenSpec files to .devin/.

Windsurf is now Devin Desktop, and its config directory moved from .windsurf/ to
.devin/. Devin Desktop reads .windsurf/ only as a fallback, and Devin Local does
not read it at all.
? Move 6 skills and 6 commands from .windsurf/ to .devin/? (Y/n)
  • Say no and nothing moves. Not one file. You're asked again next time. This matters because a pre-rebrand Windsurf build reads only .windsurf/, and nothing on disk tells that user apart from someone who took the rebrand — so the move is offered, never assumed.
  • --force and non-interactive runs (CI) take the move without asking.
  • Only the files OpenSpec generates move — each skill's SKILL.md and commands named opsx-*. A hand-written Cascade workflow, a reference file you keep beside a SKILL.md, and .devin/rules/ are all left exactly where they are.
  • A legacy file that differs is never overwritten or deleted. It stays put and is reported, so you can compare the two copies and decide:
    Left 2 files in .windsurf/ that differ from the copy in .devin/. Nothing was
    overwritten — compare the two and delete the .windsurf/ copy once you have kept
    anything you customized.
    

--tools windsurf still works. A retired-id alias resolves it to devin, so existing setup scripts and CI don't break. It now configures .devin/.

Two agents, two syntaxes. Devin Desktop reads workflows; Devin Local does not. So workflow bodies say /opsx-<id> (the name a workflow filename registers) while skill bodies and the getting-started hint say /openspec-*, which works on both agents. Under commands-only delivery no skills exist, so both fall back to /opsx-*.

Proof it works

Full suite: 3365/3365 passing, run under CI-equivalent conditions (empty XDG_CONFIG_HOME + VITEST_MAX_WORKERS=4). Typecheck and eslint src/ clean. openspec validate add-devin-desktop-support --strict passes, and archiving on a scratch copy removes exactly the six intended retired-Windsurf lines.

30 behaviors verified by running the CLI against the built binary, not just tests:

Area Verified
Core journeys New Devin user; legacy .windsurf upgrade; repeat update is idempotent; --tools windsurf and experimental --tool windsurf both write .devin/ only
Data safety Symlinked roots in both directions; user file beside a SKILL.md under commands-only delivery and under a deselected workflow
Both-populated .devin wins; identical duplicate removed; edited skill and edited command both kept, symmetrically; all-divergent case reported with no phantom migration line
Invocation Zero /opsx: leakage in .devin/; workflows /opsx-*, skills /openspec-*; correct hint in all three delivery modes
No regressions Kimi .kimi.kimi-code intact with user files preserved; multi-tool init clean; IDE-written .devin/rules/ preserved; no bogus "new tool" notice
Decline path Exit 0, zero files touched, accurate guidance on how to resume

The migration got five review rounds because it deletes files. Four data-loss defects were found and fixed along the way — symlinked roots deleting the only copy; rm -rf on a skill directory taking a user's notes; a whole-directory rename carrying a user file into a directory OpenSpec later prunes; and the skill path deleting an edited SKILL.md while the command path compared bytes. Each fix has a regression test that was mutation-checked (it fails against the previous code).

Notes

  • The windsurf tool id is retired. This is the one product decision here, and the reason the diff is larger than the original. The alias means nothing breaks, but OpenSpec now lists this editor once, as Devin Desktop.
  • Not changed, deliberately: OpenSpec regenerating and pruning the files it owns is long-standing behavior for every tool, not something this PR introduces. Verified against main — an edited SKILL.md under a deselected workflow is destroyed by openspec update on 9a937cb too. Out of scope here.
  • Two spec scenarios named "Windsurf paths defined" and "Windsurf adapter formatting" remain, rewritten to record the retirement and prefixed RETIRED —. They can't be renamed: OpenSpec's own archive engine refuses to drop scenarios from a MODIFIED block, which is the correct guard.
  • The merge with main had to adapt to fix(adapters): reference slash commands by the names each tool registers #1471, which replaced the per-tool list in getTransformerForTool with a derived invocation and moved command-body rewriting into the generator. The Devin adapter is now a pure formatter like every other one.
  • .devin/ also covers rules/ and plans/. OpenSpec writes neither; untouched.
  • Worth watching after release: this ships a prompt that moves files in users' repos. Silence isn't proof of success — the first issue reports are the real signal.

- 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.
@mehdishahdoost
mehdishahdoost requested a review from TabishB as a code owner June 4, 2026 10:12
@coderabbitai

coderabbitai Bot commented Jun 4, 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

Adds Devin Desktop support by introducing a new Devin adapter, registering and exporting it, adding devin to tool configuration, and updating docs, OpenSpec specs, tasks, and tests for init/update and registry behavior.

Changes

Devin Desktop Tool Support

Layer / File(s) Summary
Devin adapter implementation
src/core/command-generation/adapters/devin.ts, test/core/command-generation/adapters.test.ts
Implements devinAdapter with YAML escaping, tag serialization, .devin/workflows/opsx-<id>.md path generation, and /opsx: to /opsx- rewriting, with adapter-focused tests.
Adapter registration, exports, and config
src/core/command-generation/adapters/index.ts, src/core/command-generation/registry.ts, src/core/config.ts, test/core/available-tools.test.ts, test/core/command-generation/registry.test.ts, test/core/init.test.ts, test/core/update.test.ts
Re-exports and registers devinAdapter, adds devin to AI_TOOLS, and extends available-tools, registry, init, and update tests for Devin Desktop.
Docs, OpenSpec metadata, proposal, specs, and tasks
docs/supported-tools.md, openspec/changes/add-devin-desktop-support/*
Adds Devin to supported-tools docs, creates OpenSpec metadata, proposal, cli-init and cli-update specs, and implementation tasks.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: alfred-openspec, TabishB

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 reflects the main change: adding Devin Desktop support as a Windsurf rename/migration.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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

🧹 Nitpick comments (2)
openspec/changes/add-devin-desktop-support/specs/cli-update/spec.md (1)

3-19: ⚡ Quick win

Rename requirement to “Workflow Updates” for consistency with the actual behavior.

This requirement describes refreshing workflow files, not slash commands. Tightening the label avoids ambiguous interpretation in future diffs/tests.

Based on learnings, slash commands and workflow artifacts are distinct concepts in OpenSpec.

🤖 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 `@openspec/changes/add-devin-desktop-support/specs/cli-update/spec.md` around
lines 3 - 19, Rename the requirement heading "Requirement: Slash Command
Updates" to "Requirement: Workflow Updates" to match the described behavior;
update any occurrences of that heading text in this spec file (the top-level
requirement title and any references) so tests and readers clearly understand
this applies to workflow files (see the heading string "Requirement: Slash
Command Updates" and the scenarios mentioning workflows for Devin Desktop and
Windsurf).
openspec/changes/add-devin-desktop-support/specs/cli-init/spec.md (1)

20-29: ⚡ Quick win

Use “workflow” terminology instead of “slash command” for Devin/Windsurf scenarios.

The section header conflicts with the scenarios beneath it (which are workflow files). Renaming to “Workflow Configuration” will keep spec language precise.

Based on learnings, in this repo slash commands are agent-interface commands and should be distinguished from workflow-file generation.

🤖 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 `@openspec/changes/add-devin-desktop-support/specs/cli-init/spec.md` around
lines 20 - 29, The header "Requirement: Slash Command Configuration" is
inconsistent with the scenarios that generate Devin workflow files; rename that
header to "Requirement: Workflow Configuration" (or simply "Workflow
Configuration") and update any references in the same spec section so scenarios
like "Generating workflows for Devin Desktop" and file names
`.devin/workflows/opsx-propose.md`, `.devin/workflows/opsx-apply.md`,
`.devin/workflows/opsx-archive.md` are described as workflow-file generation
(not agent slash commands), and ensure wording mentions using shared templates
wrapped in OpenSpec markers and the Windsurf-style frontmatter (name,
description, category, tags).
🤖 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`:
- Line 33: Docs list 'devin' as a valid tool ID but
parseWorkspaceSkillToolsValue()/getWorkspaceSkillToolIds() derive valid IDs from
AI_TOOLS, which lacks a value 'devin', causing validation to fail; fix by adding
a matching value 'devin' to the corresponding AI_TOOLS entry (the object that
represents Devin Desktop) so getToolsWithSkillsDir()/getWorkspaceSkillToolIds()
will include it, or alternatively update
getToolsWithSkillsDir()/parseWorkspaceSkillToolsValue() to accept the documented
alias; reference AI_TOOLS, getToolsWithSkillsDir(), getWorkspaceSkillToolIds(),
and parseWorkspaceSkillToolsValue() when making the change.

In `@openspec/changes/add-devin-desktop-support/tasks.md`:
- Around line 45-47: The `.devin/skills/openspec-*/SKILL.md` entry appears
inconsistent with the introduced Devin workflow location; open the
"Non-Interactive Setup" section and either correct or remove that skills path so
it matches the actual Devin artifact layout (e.g., if skills live elsewhere,
replace `.devin/skills/openspec-*/SKILL.md` with the correct path, or drop it if
no skills directory is used), and ensure the tools list still includes `devin`
only where appropriate (referencing the `opsx-<id>.md` workflow entry and the
`devin` tool ID).

In `@src/core/command-generation/adapters/devin.ts`:
- Around line 15-23: escapeYamlValue currently leaves implicit-typed YAML
scalars (e.g., true/false, null, yes/no, on/off, plain numbers, hex/exponential)
unquoted which lets parsers coerce them; update escapeYamlValue to detect these
scalar patterns in addition to special chars and force quoting: add a regex
check for booleans (true|false|yes|no|on|off), null variants (null|~), numeric
forms (integers, floats, exponentials, hex/octal), and standalone dot/minus
forms, and if matched, perform the existing escaping (backslashes, quotes,
newlines) and return the value wrapped in double quotes so the result always
remains a string in YAML frontmatter while preserving current escaping logic.

---

Nitpick comments:
In `@openspec/changes/add-devin-desktop-support/specs/cli-init/spec.md`:
- Around line 20-29: The header "Requirement: Slash Command Configuration" is
inconsistent with the scenarios that generate Devin workflow files; rename that
header to "Requirement: Workflow Configuration" (or simply "Workflow
Configuration") and update any references in the same spec section so scenarios
like "Generating workflows for Devin Desktop" and file names
`.devin/workflows/opsx-propose.md`, `.devin/workflows/opsx-apply.md`,
`.devin/workflows/opsx-archive.md` are described as workflow-file generation
(not agent slash commands), and ensure wording mentions using shared templates
wrapped in OpenSpec markers and the Windsurf-style frontmatter (name,
description, category, tags).

In `@openspec/changes/add-devin-desktop-support/specs/cli-update/spec.md`:
- Around line 3-19: Rename the requirement heading "Requirement: Slash Command
Updates" to "Requirement: Workflow Updates" to match the described behavior;
update any occurrences of that heading text in this spec file (the top-level
requirement title and any references) so tests and readers clearly understand
this applies to workflow files (see the heading string "Requirement: Slash
Command Updates" and the scenarios mentioning workflows for Devin Desktop and
Windsurf).
🪄 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

Run ID: f1310b76-ad6a-4ea0-bb86-f47e4f61ad1a

📥 Commits

Reviewing files that changed from the base of the PR and between 1b06fdd and a0a3fb6.

📒 Files selected for processing (9)
  • docs/supported-tools.md
  • openspec/changes/add-devin-desktop-support/.openspec.yaml
  • openspec/changes/add-devin-desktop-support/proposal.md
  • openspec/changes/add-devin-desktop-support/specs/cli-init/spec.md
  • openspec/changes/add-devin-desktop-support/specs/cli-update/spec.md
  • openspec/changes/add-devin-desktop-support/tasks.md
  • src/core/command-generation/adapters/devin.ts
  • src/core/command-generation/adapters/index.ts
  • src/core/command-generation/registry.ts

Comment thread docs/supported-tools.md Outdated
Comment thread openspec/changes/add-devin-desktop-support/tasks.md Outdated
Comment thread src/core/command-generation/adapters/devin.ts Outdated
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.
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.

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

Thanks for adding this. The registry/config wiring is mostly in the right shape, and I verified openspec init --tools devin generates .devin/skills/* plus .devin/workflows/opsx-*.md successfully.

I’m going to request changes before merge for two reasons:

  1. Devin workflows are flat slash workflows, but the generated bodies still point users at colon commands like /opsx:apply and describe input after /opsx:propose. Devin Desktop discovers .devin/workflows/opsx-apply.md as /opsx-apply, same as the existing Windsurf shape. Please either run the body through the hyphen command-reference transformer for the Devin adapter, or make the adapter explicitly follow whatever invocation syntax Devin actually accepts with a citation/test. Otherwise the first workflow will tell users to call a command that does not exist.

  2. The PR does not add focused test coverage despite adding a new adapter and tool ID. Please add at least adapter/registry coverage for devin, plus a focused init/update test that proves .devin/workflows/opsx-*.md are generated/refreshed and the tool is detected from .devin. That would have caught the earlier CodeRabbit wiring issue and will protect this adapter as the tool list keeps growing.

What I ran locally on head fd974e1:

  • pnpm install --frozen-lockfile (build passed via prepare)
  • pnpm vitest run test/core/command-generation/adapters.test.ts test/core/command-generation/registry.test.ts test/core/init.test.ts test/core/update.test.ts test/core/available-tools.test.ts (219 tests passed)
  • Manual isolated node bin/openspec.js init --tools devin --force, which generated the expected .devin/ files but confirmed the body still contains /opsx:apply style references.

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
@mehdishahdoost

Copy link
Copy Markdown
Contributor Author

Hey @alfred-openspec,

Thanks a lot for your review. I've addressed all of your comments and applied the requested changes. Could you please take another look at the PR when you have a chance?

Thanks!

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/core/command-generation/adapters.test.ts (1)

747-779: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add cross-platform path test for Devin adapter.

The Devin adapter is missing from the cross-platform path verification tests. Major adapters (Claude, Cursor, Windsurf) have individual path.join verification tests in this section, but Devin does not.

As per coding guidelines, when touching path behavior, add coverage that would fail on Windows path separators.

🧪 Proposed test to add after line 763
   it('Windsurf adapter uses path.join for paths', () => {
     const filePath = windsurfAdapter.getFilePath('test');
     expect(filePath.split(path.sep)).toEqual(['.windsurf', 'workflows', 'opsx-test.md']);
   });
+
+  it('Devin adapter uses path.join for paths', () => {
+    const filePath = devinAdapter.getFilePath('test');
+    expect(filePath.split(path.sep)).toEqual(['.devin', 'workflows', 'opsx-test.md']);
+  });
 
   it('All adapters use path.join for paths', () => {
🤖 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/command-generation/adapters.test.ts` around lines 747 - 779, The
Devin adapter is missing from the cross-platform path tests; add a test that
calls devinAdapter.getFilePath('test') and asserts the path is constructed via
path.join semantics (e.g., split by path.sep yields the expected segments like
['.devin','commands','opsx-test.md'] or otherwise verify it contains path.sep),
similar to the existing Claude/Cursor/Windsurf tests—locate the test block in
adapters.test.ts and insert the new "Devin adapter uses path.join for paths"
it() that references devinAdapter and its getFilePath method.

Source: Coding guidelines

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

Outside diff comments:
In `@test/core/command-generation/adapters.test.ts`:
- Around line 747-779: The Devin adapter is missing from the cross-platform path
tests; add a test that calls devinAdapter.getFilePath('test') and asserts the
path is constructed via path.join semantics (e.g., split by path.sep yields the
expected segments like ['.devin','commands','opsx-test.md'] or otherwise verify
it contains path.sep), similar to the existing Claude/Cursor/Windsurf
tests—locate the test block in adapters.test.ts and insert the new "Devin
adapter uses path.join for paths" it() that references devinAdapter and its
getFilePath method.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9b243bd9-9b9a-40b1-92ba-482a3ace432a

📥 Commits

Reviewing files that changed from the base of the PR and between fd974e1 and 9a8befa.

📒 Files selected for processing (6)
  • src/core/command-generation/adapters/devin.ts
  • test/core/available-tools.test.ts
  • test/core/command-generation/adapters.test.ts
  • test/core/command-generation/registry.test.ts
  • test/core/init.test.ts
  • test/core/update.test.ts

alfred-openspec
alfred-openspec previously approved these changes Jun 7, 2026

@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 head cbb8301 after the follow-up commits. The blockers from my prior review are addressed: Devin workflow bodies now rewrite /opsx:* references to /opsx-*, .devin/workflows/ generation/update is covered, AI_TOOLS/registry/detection wiring is present, and the CodeRabbit path-test nit is covered.

Verified locally:

  • pnpm install --frozen-lockfile
  • pnpm vitest run test/core/command-generation/adapters.test.ts test/core/command-generation/registry.test.ts test/core/init.test.ts test/core/update.test.ts test/core/available-tools.test.ts (232 passed)
  • pnpm build
  • node bin/openspec.js validate add-devin-desktop-support --strict
  • Manual node bin/openspec.js init --tools devin --force <tmp> confirmed .devin/skills/* and .devin/workflows/opsx-*.md are generated with no remaining /opsx: references.

@mehdishahdoost

Copy link
Copy Markdown
Contributor Author

Hi @TabishB and @alfred-openspec, Could this PR be included in the next release? Since Devin acquired Windsurf and dropped support for the old structure, our team needs this update to transition smoothly from OpenSpec/Windsurf to Devin.

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

I want to include Devin Desktop support in the next release, but this rebased head needs two fixes before it is release-ready:

  1. Refresh the change deltas against the current canonical specs. On exact head 254fec2, node bin/openspec.js archive add-devin-desktop-support --yes aborts because the modified AI Tool Configuration block omits the current Selecting tools to configure scenario. validate --strict passes, so please use the real archive path as the regression check.
  2. Make the Devin skill path usable by Devin Local. Cognition's current docs say Devin Local does not support Workflows and recommends Skills instead; a clean openspec init --tools devin --force generates six .devin/skills/* files, but four retain /opsx:* workflow references and the getting-started hint prints /opsx:propose. Keep .devin/workflows/opsx-*.md with hyphen references for Cascade, but make Devin skill bodies and onboarding use /openspec-*, with focused tests for both surfaces.

The implementation builds, lints, and passes the full suite (2266/2266). Once those two blockers are fixed, re-request review and we can target the next release.

@clay-good
clay-good requested a review from a team as a code owner July 28, 2026 14:30
clay-good and others added 2 commits July 28, 2026 09:42
…t 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>
…faces

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>
@clay-good clay-good changed the title proposal: Add devin desktop support feat(adapters): add Devin Desktop support Jul 28, 2026
@clay-good

Copy link
Copy Markdown
Collaborator

Addressing every open review comment. Pushed as e310b41 + 872e34c.

@alfred-openspec (2026-07-27)

1. Refresh the change deltas — archive aborted. Fixed. All deltas regenerated from the current canonical specs, each MODIFIED block restating every existing scenario, plus a new ai-tool-paths delta for skillsDir: '.devin'. archive --yes now merges + 0, ~ 4, - 0 and I verified the resulting spec diff is purely additive before reverting it. Used the real archive path as the regression check, as you asked.

2. Devin skill bodies and onboarding should use /openspec-*. Fixed, and your reading of Cognition's docs checks out — Devin Local's limitations say verbatim that workflows are unavailable and to migrate them to skills. getTransformerForTool now gives devin the skill-reference transformer, which covers skill bodies and the getting-started hint in one place. .devin/workflows/opsx-*.md keep hyphen references for Cascade. Focused tests on both surfaces in init.test.ts, update.test.ts, and command-references.test.ts.

One wrinkle worth flagging: under commands-only delivery .devin/skills/ is deleted, so an unconditional /openspec-* hint would name a file that isn't there. Devin falls back to /opsx-* in that mode. The cli-init delta records it and the transformer test pins all three modes.

@coderabbitai

  • --tools devin contract mismatch — already fixed in 514699e; AI_TOOLS has the devin row and detection/validation derive from it. Negative-case test added.
  • Quote YAML implicit-typed scalars — resolved differently than suggested. The adapter no longer has its own escaper; it uses the shared command-generation/yaml.ts helpers from fix(adapters): escape YAML frontmatter values consistently across all command adapters #1447, which quote unconditionally and also escape \r and C0/C1 controls. The hand-rolled version this PR had was failing the registry-derived escaping matrix on 077, .inf, .nan, and carriage returns.
  • tasks.md .devin/skills/ path looks inconsistent — the path is correct (.devin/skills/<name>/SKILL.md is a documented skill location); devin genuinely has both surfaces. tasks.md has been rewritten to match what shipped.
  • Rename "Slash Command Configuration"/"Slash Command Updates" to "Workflow ..." — not applied. Those headers are the canonical requirement names in openspec/specs/cli-init and cli-update; a delta has to match the header it modifies or archive can't find it. The stale delta that invented Slash Command Configuration is gone.

Also fixed, from an adversarial review pass

  • devin was missing from the --tools list in docs/cli.md and from the command-syntax tables in docs/commands.md and docs/how-commands-work.md.
  • supported-tools.md gains a footnote citing the .windsurf/.devin/ move and the Devin Local workflow gap.
  • Reverted an unrelated zcodeAdapter export and lingma reorder that a merge resolution had pulled into adapters/index.ts.

Full suite green: 3309/3309.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== PR changes =='
git diff --name-status origin/main...HEAD

echo '== Command-generation structure =='
ast-grep outline src/core/command-generation --items all --type function,variable 2>/dev/null || true

echo '== Devin-related implementation and tests =='
rg -n -C 3 --glob '*.ts' \
  '(devinAdapter|toolId:\s*[\x27"]devin[\x27"]|getTransformerForTool|skill-reference|openspec-|opsx-|yaml)' \
  src/core test/core

echo '== Tool config and documentation mentions =='
rg -n -C 2 '(Devin|devin|--tools)' \
  src/core/config.ts docs/cli.md docs/commands.md docs/how-commands-work.md docs/supported-tools.md

echo '== Change delta headings and Devin-related requirements =='
rg -n -C 3 '(MODIFIED Requirements|REQUIREMENT:|Devin|devin|Slash Command Configuration|Slash Command Updates|ai-tool-paths)' \
  openspec/changes/add-devin-desktop-support openspec/specs/cli-init openspec/specs/cli-update openspec/specs/ai-tool-paths

Length of output: 191730


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Changed files and commits =='
git diff --name-status origin/main...HEAD
git log --oneline origin/main..HEAD --max-count=20

echo '== Adapter, shared YAML helper, registry =='
for f in \
  src/core/command-generation/adapters/devin.ts \
  src/core/command-generation/yaml.ts \
  src/core/command-generation/registry.ts \
  src/core/command-generation/adapters/index.ts \
  src/utils/command-references.ts
do
  echo "--- $f"
  cat -n "$f"
done

echo '== Focused test locations =='
rg -n -C 8 --glob '*.ts' \
  '(Devin|devin|commands-only|skill-reference|implicit|077|\.inf|\.nan|carriage|negative)' \
  test/core/init.test.ts \
  test/core/update.test.ts \
  test/core/command-references.test.ts \
  test/core/available-tools.test.ts \
  test/core/command-generation/adapters.test.ts \
  test/core/command-generation/registry.test.ts

echo '== Exact requirement delta comparison =='
for spec in cli-init cli-update ai-tool-paths; do
  echo "--- canonical $spec"
  cat "openspec/specs/$spec/spec.md"
  echo "--- delta $spec"
  cat "openspec/changes/add-devin-desktop-support/specs/$spec/spec.md"
done

Length of output: 58083


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Devin transformer and tool validation tests =='
rg -n -C 12 \
  '(getTransformerForTool|devin|commands-only|commands delivery|invalid.*tool|unknown.*tool|--tools)' \
  test/utils/command-references.test.ts test/core/init.test.ts

echo '== YAML matrix registration and assertions =='
sed -n '1035,1175p' test/core/command-generation/adapters.test.ts

echo '== Complete canonical and delta specifications =='
for spec in cli-init cli-update ai-tool-paths; do
  echo "--- canonical: openspec/specs/$spec/spec.md"
  cat -n "openspec/specs/$spec/spec.md"
  echo "--- delta: openspec/changes/add-devin-desktop-support/specs/$spec/spec.md"
  cat -n "openspec/changes/add-devin-desktop-support/specs/$spec/spec.md"
done

echo '== Direct changes for reviewed implementation files =='
git diff --unified=5 origin/main...HEAD -- \
  src/core/config.ts \
  src/core/command-generation/adapters/devin.ts \
  src/core/command-generation/yaml.ts \
  src/utils/command-references.ts \
  test/utils/command-references.test.ts \
  test/core/init.test.ts \
  test/core/update.test.ts

Length of output: 84086


@clay-good Verified the follow-up changes. The previously raised concerns are addressed:

  • devin is now in AI_TOOLS, so --tools validation, picker availability, and detection derive from the canonical tool configuration.
  • The Devin adapter now uses the shared YAML helper. The registry-wide YAML matrix covers implicit scalars (077, .inf, .nan), CR, and control characters.
  • Devin workflows consistently use /opsx-*, while Devin skills and onboarding use /openspec-* when skills are delivered; commands-only correctly falls back to /opsx-*.
  • The regenerated deltas preserve the canonical requirements and add the Devin-specific scenarios, including ai-tool-paths.
  • Documentation and focused init/update/transformer tests cover both Devin surfaces.

✏️ Learnings added
Learnt from: clay-good
URL: https://github.com/Fission-AI/OpenSpec/pull/1167

Timestamp: 2026-07-28T14:56:02.158Z
Learning: For Devin Desktop support, OpenSpec writes workflow files to `.devin/workflows/opsx-<id>.md` with `/opsx-<id>` references because Cascade invokes workflows by filename. Devin skill files use `.devin/skills/openspec-*/SKILL.md` and must reference `/openspec-*` skills because Devin Local does not support workflows. In commands-only delivery, no Devin skills exist, so onboarding falls back to `/opsx-*`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

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>
@clay-good

Copy link
Copy Markdown
Collaborator

CI note: Test (macos-bash) failed once on 44258ce with a 10s timeout in test/commands/workset.test.ts > remove > cleans up a generated file and tolerates its absence. Runner flakiness, not this change — the test references none of the modules this PR touches, passed 3/3 locally on macOS, and the run took 251s against 116s locally. Re-ran the job and it passed in 1m47s. All checks green on 44258ce.

clay-good and others added 4 commits July 28, 2026 12:45
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>
…ration

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>
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>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@clay-good
clay-good dismissed stale reviews from alfred-openspec and themself via d94af8b July 28, 2026 19:13
@clay-good clay-good changed the title feat(adapters): add Devin Desktop support feat(adapters): follow the Windsurf rename to Devin Desktop Jul 28, 2026
@clay-good

Copy link
Copy Markdown
Collaborator

@mehdishahdoost — thanks for this, and sorry it sat for a while. I've pushed to your branch to resolve the conflicts with main and to handle one review point, and I want to be upfront that the scope grew, so you're not surprised by the diff.

Your original goal is intact and shipped: OpenSpec supports Devin Desktop, .devin/workflows/ + .devin/skills/, with the two-agent invocation split you worked out (workflows /opsx-*, skills /openspec-*). That analysis was right and I kept it as-is — including the detail that Devin Local has no workflows, which is easy to miss.

What changed and why. The review comment was that migration logic was needed. Digging into it, adding devin as a second tool beside windsurf turned out to leave existing users worse off:

  • Devin Desktop creates .devin/ on its own, so anyone who took the rebrand has both directories. OpenSpec then told them "Detected new tool: Devin Desktop. Run openspec init" — which produced two parallel installs of one editor, with two conflicting getting-started lines.
  • Devin Local doesn't read .windsurf/ at all, so existing Windsurf users' files are invisible to it. The legacy fallback only helps on Devin Desktop.

So it now follows the rename rather than duplicating the tool — windsurf is retired as an id, devin replaces it and is detected from either directory, and --tools windsurf still resolves so nobody's scripts break. That's the same shape OpenSpec already used for Kimi CLI → Kimi Code. Existing .windsurf/ users get offered the move on openspec update (declining touches nothing), because a pre-rebrand Windsurf build reads only .windsurf/ and there's no way to tell those users apart.

Three things worth flagging from the merge, since they're behavior changes to code you wrote:

  1. main landed fix(adapters): reference slash commands by the names each tool registers #1471 while this was open. It deleted transformToHyphenCommands and moved command-body rewriting into the generator, so the Devin adapter is now a pure formatter — the /opsx-* rewrite still happens, just centrally, like every other flat tool.
  2. Your MODIFIED spec deltas were written against an older cli-update; archiving them would have quietly reverted a fix(adapters): reference slash commands by the names each tool registers #1471 spec change. Refreshed against the current specs.
  3. An adversarial pass found the migration I added could destroy files (a symlinked root deleting the only copy; rm -rf on a skill directory taking a user's notes). Both fixed with regression tests — worth knowing about if you review the migration code.

Full details in the PR description. Happy to walk through any of it, and if you'd rather take a different approach on the rename, say so — it's your PR and the design call is worth discussing.

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

One data-loss path remains on d94af8b: when .windsurf/skills/openspec-*/ contains a user file beside SKILL.md, migration renames the whole directory into .devin/, then commands-only delivery or a deselected workflow recursively removes it. A focused commands-only regression loses reference.md with ENOENT; please move only SKILL.md when sibling files exist and cover the commands-only/deselected cases, then I can approve.

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>
@clay-good

Copy link
Copy Markdown
Collaborator

@alfred-openspec — confirmed and fixed in 2a9c0e93. Good catch; I'd fixed the destination-exists branch and left the other one wrong.

Reproduced first, on d94af8b with delivery: commands:

--- before ---
.windsurf/skills/openspec-explore/reference.md
.windsurf/skills/openspec-explore/SKILL.md
Migrated 1 skill: .windsurf → .devin
--- after: does reference.md still exist anywhere? ---
*** LOST ***

The mechanism is exactly as you described: when the destination did not yet exist, migration renameSync'd the whole legacy skill directory into .devin/, carrying the user's file into a directory OpenSpec owns — and then commands-only delivery (or a workflow outside the active profile) removed that directory recursively.

Fix: only SKILL.md ever crosses, in both branches. Anything the user left beside it stays under the legacy root, and the legacy directory is still removed when the move leaves it empty, so the ordinary case doesn't leave empty scaffolding behind.

Verified across all three delivery modes and the deselected-workflow case:

Case reference.md
delivery: commands kept under .windsurf/
delivery: skills kept under .windsurf/
delivery: both kept under .windsurf/
deselected workflow (openspec-new-change, core profile) kept under .windsurf/
skill dir with only SKILL.md .windsurf/ fully removed, skill lands in .devin/

Three regression tests added covering the commands-only and deselected-workflow cases plus the clean-vacate case. I mutation-checked them: reverting to the whole-directory rename fails the two new data-loss tests, so they aren't vacuous.

Spec delta and changeset updated to say "each skill's SKILL.md" rather than "skill directories". Full suite 3363 passing, typecheck and lint clean, validate --strict passes, and archiving on a scratch copy is still purely intentional.

@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 2a9c0e9. The user-file preservation blocker is fixed for both commands-only delivery and deselected workflows; build, lint, 1,527 focused tests, strict validation, a clean archive, all 36 archived specs, and hosted CI/security passed.

@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 2a9c0e93. The migration now moves only generated SKILL.md files, preserves sibling user files under the legacy root, and covers commands-only and deselected-workflow cleanup; clean build, lint, focused tests, and strict change validation all pass.

…y 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>

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

One reporting path is still dropped on 90eaee1: collectLegacyToolMigrations only returns a migration when moved > 0, so when every legacy file differs from its .devin counterpart, keptInPlace > 0 but update neither prompts nor emits keptInPlaceNotice, silently leaving two divergent copies. Please retain and report kept-only results with a focused all-edited regression, while avoiding an empty or misleading “Move …” prompt when nothing is actually movable.

@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 preservation behavior is fixed, but the promised warning disappears when every candidate differs: collectLegacyToolMigrations() only returns a migration when skills.moved or commands.moved is nonzero, so an all-keep run never reaches keptInPlaceNotice(). Please include skills.kept || commands.kept in that predicate and assert the edited-skill/edited-command test prints the notice; the focused regression currently fails with both files preserved but no warning.

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>
@clay-good

Copy link
Copy Markdown
Collaborator

@alfred-openspec — confirmed and fixed in 37bff11c. You're right, and it was a gap I'd reasoned about and then punted on for the wrong reason: I avoided a "Move 0 files" prompt by dropping the record entirely, which threw away the report along with the prompt.

Reproduced first on 90eaee1 — three divergent files, zero output:

=== update output (grep for ANY migration signal) ===
Migrated: custom profile with 6 workflows      ← unrelated profile migration
✓ Updated: Devin Desktop (formerly Windsurf) (v1.6.0)

=== files still divergent and unreported? ===
.windsurf/skills/openspec-explore/SKILL.md
.windsurf/skills/openspec-propose/SKILL.md
.windsurf/workflows/opsx-explore.md

Fix: collectLegacyToolMigrations retains kept-only results, and every caller gates the "Migrated …" line on a new hasMovableContent(). So a kept-only result reports what was left without offering a move of nothing and without claiming a migration that never happened.

=== ALL-EDITED (nothing movable) ===
Left 3 files in .windsurf/ that differ from the copy in .devin/. Nothing was
overwritten — compare the two and delete the .windsurf/ copy once you have kept
anything you customized.
  files untouched: 3 of 3

=== MIXED (some movable, some kept) ===
Migrated 1 skill: .windsurf → .devin
Left 1 file in .windsurf/ that differs from the copy in .devin/. …

Regression test added — the all-edited case, asserting the notice fires, that no Migrated 0/empty-migration line is printed, and that both files are byte-for-byte untouched. Mutation-checked: reverting the condition to moved > 0 fails it.

One thing I changed beyond your ask. The notice used to say "your version was kept rather than overwritten", which asserts a user edit. A legacy file also differs when an older OpenSpec version generated it, so the wording now states only what's true — nothing was overwritten — and leaves the user to compare.

Full suite 3365 passing, typecheck and lint clean, validate --strict passes, archive on a scratch copy still removes exactly the six intended retired-Windsurf lines.

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>
@clay-good

Copy link
Copy Markdown
Collaborator

Follow-up on 37bff11c: CI went red on all three platforms and I've fixed it in c164701f. It was my new test, not the migration code.

What broke. The kept-only regression asserted that no log line matched /Migrated\s*:/. That also matches OpenSpec's profile migration message — "Migrated: custom profile with N workflows" — which only prints when the global config has no profile yet. Fresh CI runner: prints. Developer machine that has run OpenSpec before: doesn't. So it passed locally and failed everywhere else.

Root-caused rather than guessed. getGlobalConfigDir() honors XDG_CONFIG_HOME, so I reproduced CI exactly with an empty one:

=== A) buggy assertion, fresh config home ===
   × should report divergent files even when nothing is movable
=== B) fixed assertion, same fresh config home ===
      Tests  1 passed

Fix: match on the directory arrow ".windsurf → .devin", which is specific to a migration report and independent of config state.

Then re-ran the whole suite under CI-equivalent conditions (XDG_CONFIG_HOME empty + VITEST_MAX_WORKERS=4) rather than just the one test, since every earlier local run had used my real config and any other test I added could have carried the same blind spot: 115 files, 3365 tests, all passing.

All 12 checks green on c164701f, including windows-pwsh. No production code changed in this commit.

@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 c164701. Kept-only migrations now report divergent files without offering a no-op move or claiming a migration; build, lint, 1,529 focused tests, strict validation, a clean archive, all 36 archived specs, and hosted CI/security passed.

@clay-good

Copy link
Copy Markdown
Collaborator

Thank you, @mehdishahdoost 👑

This is approved and going in. Before it does, credit where it's due — and an honest account of what happened to your PR, because it changed a lot after you opened it.

You found the problem, and you got the hard part right. Every new Devin user was being set up in a deprecated directory, and nobody had noticed. Two things in your PR were genuinely non-obvious and survived unchanged into the merged version:

  • The two-agent invocation split. Devin Desktop reads workflows, Devin Local doesn't. So workflow bodies use /opsx-<id> while skills and the getting-started hint use /openspec-*, which works on both. That's easy to miss and it's the reason Devin support actually works for both agents rather than half of them.
  • .devin/workflows/ being real. Cognition's Cascade workflows page is stale and still documents only .windsurf/workflows/. During review an adversarial pass trusted that page, concluded the path was fabricated, and recommended deleting the adapter outright. The FAQ directory table you'd cited is the authoritative source and it backed you up. Your citation is what saved the feature.

What changed and why. The review asked for migration logic. Once we dug in, adding devin as a second tool beside windsurf turned out to make things worse for existing users: Devin Desktop creates .devin/ on its own, so OpenSpec was telling rebranded users "Detected new tool: Devin Desktop — run openspec init", which gave them two parallel installs of one editor. And Devin Local doesn't read .windsurf/ at all, so existing Windsurf users' files were invisible to it.

So it became a rename rather than an addition — windsurf retired, devin in its place, detected from either directory, with --tools windsurf kept as an alias so nobody's scripts break. Same shape OpenSpec already used for Kimi CLI → Kimi Code.

And it took a while. The migration moves and deletes files in people's repos, so it got five review rounds and turned up four separate data-loss bugs before it was safe — including two that we introduced while fixing the previous one. None of that is a reflection on your PR; it's what that particular corner of the codebase demanded once we decided to touch existing users' files. Your adapter, your invocation analysis, and your docs research are all still in here.

Sorry it sat as long as it did, and thanks for your patience through the rework and for the docs digging that made the whole thing possible. Genuinely good contribution — please send more. 🙏

@clay-good
clay-good added this pull request to the merge queue Jul 28, 2026
Merged via the queue into Fission-AI:main with commit 1637856 Jul 28, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants