Skip to content

feat(targets): converge windsurf skills onto .agents/skills/#1802

Open
danielmeppiel wants to merge 5 commits into
mainfrom
danielmeppiel/windsurf-skills-converge-agents-path
Open

feat(targets): converge windsurf skills onto .agents/skills/#1802
danielmeppiel wants to merge 5 commits into
mainfrom
danielmeppiel/windsurf-skills-converge-agents-path

Conversation

@danielmeppiel

@danielmeppiel danielmeppiel commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

TL;DR

Windsurf (now Devin Desktop) was the last active harness still deploying skills to its harness-native .windsurf/skills/ path while five peers — Copilot, Cursor, Codex, Gemini, OpenCode — already converged onto the cross-tool .agents/skills/<name>/SKILL.md path. This PR adds deploy_root=".agents" to windsurf's skills mapping so it joins the converged set. Back-compat is preserved by the existing --legacy-skill-paths escape hatch, which is data-driven and already covers windsurf. Refs #1520.

Warning

This is a breaking path change for windsurf skills. Existing .windsurf/skills/ deployments are orphaned (not auto-migrated) on next apm install. Opt out with --legacy-skill-paths or APM_LEGACY_SKILL_PATHS=1 to keep the old layout — identical to the back-compat contract the five earlier harnesses already shipped.

Problem (WHY)

APM deploys each primitive to per-harness on-disk locations defined by TargetProfiles. The .agents/ directory is the emerging cross-tool convention for agent skills, and APM has been moving harnesses onto it one at a time.

  • Windsurf's skills PrimitiveMapping had no deploy_root, so it fell back to root_dir=".windsurf" and deployed to .windsurf/skills/ — out of step with the converged majority.
  • Five harnesses (copilot, cursor, codex, gemini, opencode) already set deploy_root=".agents". Windsurf and Kiro were the laggards. This issue moves windsurf only; Kiro stays the holdout, out of scope.
  • Windsurf is now Devin Desktop, and Devin's own documentation uses .agents/skills/, so convergence also realigns APM with the harness vendor's own docs.

Approach (WHAT)

Decision Choice
Core change Add deploy_root=".agents" to windsurf's skills mapping (mirrors copilot reference)
Scope Skills primitive only — rules/workflows/hooks/mcp and the .codeium/windsurf/ user dir untouched
pack_prefixes Not added — 4 of 5 converged peers set only deploy_root; only codex adds pack_prefixes. Mirror the 4-peer majority
Back-compat Reuse existing apply_legacy_skill_paths(); no extension needed (it iterates all profiles)
Target rename / devin alias Explicitly not done — separate deferred issue (#1799)

Implementation (HOW)

  • src/apm_cli/integration/targets.py — windsurf skills mapping gains deploy_root=".agents" (multi-line form matching the copilot reference). Comment block updated to state skills now land under .agents/skills/ and that windsurf is now Devin Desktop; rules/workflows/hooks stay under .windsurf/.
  • src/apm_cli/core/target_detection.py — the apm compile -t windsurf output-description string updated from .windsurf/skills/ to .agents/skills/ so the printed path matches reality (no test guarded this string; matches the codex line).
  • tests/unit/integration/test_targets.py — data-driven default-routing assertion extended with "windsurf": ".agents"; legacy-restore list extended to 7 profiles; two new windsurf tests (default .agents/skills/, legacy .windsurf/skills/) using Path.parts comparison.
  • tests/unit/integration/test_windsurf_uninstall_skills.py — partition/cleanup paths migrated to .agents/skills/; added a deploy_root == ".agents" shape assertion. (See trade-off below — this file was an unanticipated but correct consequence of the path move.)
  • tests/unit/integration/{test_data_driven_dispatch,test_scope_integration}.py — stale path comments refreshed.
  • Docs + CHANGELOGskills.md, targets-matrix.md, manifest-schema.md, and a [Unreleased] ### Changed entry.

Diagram

Legend: how a windsurf skill resolves its deploy directory by default (converged) vs under the legacy escape hatch.

flowchart LR
    S["windsurf skills PrimitiveMapping"] --> Q{"deploy_root set?"}
    Q -->|"default: deploy_root='.agents'"| A[".agents/skills/name/SKILL.md"]
    Q -->|"--legacy-skill-paths resets to None"| W[".windsurf/skills/name/SKILL.md"]
    A --> C["converged with copilot, cursor, codex, gemini, opencode"]
    W --> L["per-client back-compat layout"]
Loading

Trade-offs

  • Breaking path change, mitigated by an existing opt-out. Old .windsurf/skills/ lockfile entries no longer route after the change (uninstall partitioning keys off the live deploy_root). This is the same accepted tradeoff the five earlier harnesses shipped; --legacy-skill-paths is the sanctioned remedy.
  • test_windsurf_uninstall_skills.py was migrated, not preserved at the old path. The pre-implementation plan assumed it was unaffected; in fact partition_managed_files builds skill prefixes from the live deploy_root or root_dir, so the test had to move to .agents/skills/. The [BUG] uninstall: skill directories survive cleanup on windsurf target #1481 regression intent (windsurf skill cleanup) is preserved at the new path.
  • lockfile_enrichment.py _CROSS_TARGET_MAPS["windsurf"] left as-is (deferred). This pack-time cross-target map still points at .windsurf/skills/. It was likewise not updated when cursor/opencode converged (they still map to .cursor/skills/ / .opencode/skills/; only codex maps to .agents/skills/). Following that precedent keeps this change bounded; the pack-vs-deploy drift already exists identically for cursor/opencode and belongs in a separate sweep, tracked in Sweep lockfile cross-target skill maps onto .agents/skills/ for converged targets (windsurf/cursor/opencode) #1805.

Benefits

  1. Windsurf skills now resolve to the same .agents/skills/ path as five other harnesses — one fewer special case in the routing matrix.
  2. Realigns APM with Devin Desktop's own documented skills path.
  3. Zero new back-compat code: the change is one deploy_root plus a regression test pinning the legacy restore.
  4. Reduces the convergence backlog to a single remaining holdout (Kiro).

Validation

Lint chain (CI mirror) and the full unit suite are green on the branch tip.

Lint chain — all silent / exit 0
=== ruff check ===
All checks passed!
=== ruff format --check ===
1265 files already formatted
=== pylint R0801 ===
Your code has been rated at 10.00/10
=== auth signals ===
[+] auth-signal lint clean
Full unit suite
17079 passed, 2 skipped, 21 xfailed, 19 warnings, 40 subtests passed in 323.96s

Scenario evidence

User-promise scenario Proven by APM principle
Windsurf skill deploys to the converged .agents/skills/ path by default test_targets.py::TestDefaultSkillRouting::test_windsurf_skill_routing_uses_agents_dir_by_default Cross-tool convergence (consistent deploy locations)
--legacy-skill-paths restores windsurf to .windsurf/skills/ test_targets.py::TestDefaultSkillRouting::test_windsurf_legacy_skill_paths_restores_per_client_routing Back-compat escape hatch (no silent breakage)
Uninstall cleans windsurf skills at the new path test_windsurf_uninstall_skills.py (migrated; #1481 regression intent) Deterministic uninstall (no orphaned managed files)

How to test

  • uv run --extra dev python -m pytest tests/unit/integration/test_targets.py -q — routing + legacy-restore tests pass.
  • uv run --extra dev python -m pytest tests/unit/integration/test_windsurf_uninstall_skills.py -q — migrated uninstall tests pass.
  • In a scratch package with a skill, run apm install --target windsurf and confirm the skill lands at .agents/skills/<name>/SKILL.md.
  • Re-run with APM_LEGACY_SKILL_PATHS=1 apm install --target windsurf and confirm it lands at .windsurf/skills/<name>/SKILL.md.

Refs #1520

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Add deploy_root=".agents" to the windsurf skills PrimitiveMapping so
skills deploy to the cross-tool .agents/skills/<name>/SKILL.md path,
mirroring copilot/cursor/codex/gemini/opencode. Windsurf (now Devin
Desktop) was a laggard still on harness-native .windsurf/skills/.

Back-compat is preserved by the existing apply_legacy_skill_paths()
escape hatch (--legacy-skill-paths flag / APM_LEGACY_SKILL_PATHS=1),
which is data-driven and auto-covers windsurf: legacy mode restores
.windsurf/skills/. Rules, workflows, and hooks stay under .windsurf/.

Scope is bounded to the skills primitive only; no target rename or
devin alias (separate deferred issue).

Tests: data-driven default-routing assertion extended to windsurf,
plus a legacy-restore regression test (both compare Path.parts, not
substrings). Windsurf uninstall/partition tests migrated to the new
path. Docs (skills.md, targets-matrix.md, manifest-schema.md),
target_detection compile-output description, and CHANGELOG updated.

Refs #1520

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 16, 2026 10:12
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI 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.

Pull request overview

This PR updates the windsurf target so the skills primitive deploys to the cross-tool .agents/skills/<name>/SKILL.md path (via deploy_root=".agents"), aligning windsurf/Devin Desktop with the existing multi-target skills convergence and keeping the existing legacy escape hatch.

Changes:

  • Set windsurf skills mapping to deploy under .agents/ (leaving other windsurf primitives under .windsurf/).
  • Update target description output and adjust/unblock uninstall+partition tests to use the new .agents/skills/ location.
  • Refresh docs and add a CHANGELOG entry documenting the breaking path change and opt-out.
Show a summary per file
File Description
tests/unit/integration/test_windsurf_uninstall_skills.py Migrates windsurf uninstall regression tests to validate cleanup under .agents/skills/ and asserts deploy_root=".agents".
tests/unit/integration/test_targets.py Extends convergence/legacy-routing assertions to include windsurf; adds windsurf-specific routing tests.
tests/unit/integration/test_scope_integration.py Updates a windsurf comment to reflect the converged .agents/skills/ location.
tests/unit/integration/test_data_driven_dispatch.py Updates a comment describing windsurf’s skills deploy path.
src/apm_cli/integration/targets.py Adds deploy_root=".agents" to windsurf’s skills mapping and updates the surrounding target comment block.
src/apm_cli/core/target_detection.py Updates the windsurf target description string to mention .agents/skills/.
docs/src/content/docs/reference/targets-matrix.md Documents windsurf as using both .windsurf/ and .agents/ and updates the skills path.
docs/src/content/docs/reference/manifest-schema.md Updates the windsurf row to list .agents/skills/ instead of .windsurf/skills/.
docs/src/content/docs/producer/author-primitives/skills.md Updates the skills routing table and narrative to include windsurf in .agents/skills/ convergence.
CHANGELOG.md Adds an Unreleased entry documenting the windsurf skills deploy path change and legacy opt-out.

Copilot's findings

  • Files reviewed: 10/10 changed files
  • Comments generated: 1


- **Detection.** `.windsurf/` directory.
- **Deploy directory.** `.windsurf/` at project scope; `~/.codeium/windsurf/` at user scope.
- **Deploy directory.** `.windsurf/` plus `.agents/` for skills at project scope; `~/.codeium/windsurf/` at user scope.
…both scopes

Fold of copilot-pull-request-reviewer[bot] finding on PR #1802: the
windsurf Deploy-directory bullet attached ".agents/ for skills" to
"at project scope", wrongly implying the skills convergence was
project-scope-only. At user scope deploy_root=".agents" resolves to
~/.agents/skills/ (get_deploy_root(USER) -> Path.home(); skill_integrator
effective_root = deploy_root or root_dir). Restate so skills converge on
.agents/skills/ at both scopes while native primitives stay under
.windsurf/ (project) / ~/.codeium/windsurf/ (user).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@danielmeppiel

Copy link
Copy Markdown
Collaborator Author

shepherd-driver advisory (PR #1802)

Advisory only -- no verdict label applied, no merge gating. Surfaced for the maintainer to weigh.

Folded in this run

  • (Copilot) Windsurf "Deploy directory" bullet in targets-matrix.md attached .agents/ (for skills) to "at project scope", wrongly implying skills convergence was project-scope-only -- resolved in 2885fa75.

Copilot signals reviewed

  • docs/src/content/docs/reference/targets-matrix.md:167 -- LEGIT: with deploy_root=".agents", user-scope windsurf skills resolve to ~/.agents/skills/ (get_deploy_root(USER) -> Path.home(); skill_integrator effective_root = deploy_root or root_dir), so skills converge on .agents/skills/ at BOTH scopes. Line restated accordingly (resolved in 2885fa75).

Copilot round 2 (post-fold head 2885fa75): no new inline findings -- drained.

Deferred (out-of-scope follow-ups)

  • (self) _CROSS_TARGET_MAPS["windsurf"] pack-time remap in bundle/lockfile_enrichment.py left as-is -- scope boundary: this is pack-time cross-target enrichment, not deploy routing; the same pack-vs-deploy drift already exists identically for cursor/opencode (only codex maps to .agents/skills/). A combined windsurf+cursor+opencode enrichment sweep is the right home.
  • (Copilot, generalized) Peer matrix sections (copilot/opencode/codex) share the same undocumented user-scope ~/.agents/skills/ nuance their "Deploy directory" bullets omit -- scope boundary: those rows are outside this PR's diff; a separate doc-consistency sweep.

Test coverage

Proportionate review for a one-line deploy_root routing change: test-coverage lens only (full 9-persona panel reserved for non-trivial PRs). Verdict: coverage excellent, no blocking gaps. The default-routing assertion and the --legacy-skill-paths regression both pin windsurf via Path.parts (no substring matching), and the windsurf uninstall suite was migrated to .agents/skills/.

Lint contract

uv run --extra dev ruff check src/ tests/ and uv run --extra dev ruff format --check src/ tests/ both silent. pylint R0801 10.00/10; scripts/lint-auth-signals.sh clean; ci.yml grep guards pass; ASCII-only.

CI

All required checks green (Lint, Build & Test Shard 1 + 2, Coverage Combine, APM Self-Check, Spec conformance gate, CodeQL, PR Binary Smoke, NOTICE Drift, build, gate, license/cla); deploy skipped as expected. 0 CI fix iterations.

Mergeability status

PR head SHA review lens iters folds defers Copilot rounds CI mergeable mergeStateStatus
#1802 2885fa75 coverage (proportionate) 1 1 2 2 green MERGEABLE BLOCKED (awaiting required review)

Convergence

1 outer iteration; 2 Copilot rounds (round 2 drained). Ready for maintainer review.

danielmeppiel and others added 2 commits June 16, 2026 13:12
… entry

The windsurf skills convergence intentionally leaves
_CROSS_TARGET_MAPS["windsurf"] pointing at .windsurf/skills/ (matching
the cursor/opencode precedent). Note the tracked follow-up #1805 so the
pack-vs-deploy drift reads as a sequenced, deliberate defer rather than
an oversight.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…f-skills-converge-agents-path

# Conflicts:
#	CHANGELOG.md
#	docs/src/content/docs/reference/manifest-schema.md
#	docs/src/content/docs/reference/targets-matrix.md
#	src/apm_cli/core/target_detection.py
@danielmeppiel

Copy link
Copy Markdown
Collaborator Author

Conflict resolution: merged current main

Merged current main into the branch at 788a09a9 -> e1cb936b
(no rebase; head-branch authorship preserved).

origin/main advanced with #1820 (SBOM export + the new antigravity
target), which touched the same lines/tables as this PR's windsurf
change. Resolved 4 conflicts as a faithful merge of BOTH intents --
main's antigravity additions kept, this PR's windsurf
.agents/skills/ convergence applied -- no intent dropped from either
side:

  • CHANGELOG.md -- ordered main's new ### Added block first, then
    this PR's ### Changed windsurf entry (Keep a Changelog ordering).
  • src/apm_cli/core/target_detection.py -- kept main's antigravity
    compile-output line; windsurf line now reads .agents/skills/.
  • docs/.../reference/manifest-schema.md -- antigravity row added by
    main retained; windsurf row shows .agents/skills/.
  • docs/.../reference/targets-matrix.md -- convergence prose lists both
    Antigravity (main) and Windsurf (this PR) as converged; Claude + Kiro
    remain the target-native holdouts.

Lint contract silent post-merge (full CI-mirror chain: ruff check,
ruff format --check, pylint R0801 --min-similarity-lines=10,
scripts/lint-auth-signals.sh -- all exit 0). Targeted + core/
integration suites green (2680 passed). Push used git push (merge
commit on the head branch; no force).

Post-push mergeability: gh pr view --json mergeStateStatus,mergeable
reports BLOCKED / MERGEABLE (BLOCKED = awaiting required review, not
a conflict). All CI checks green on e1cb936b.

Ready for maintainer review.

@danielmeppiel danielmeppiel added the panel-review Trigger the apm-review-panel gh-aw workflow label Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

panel-review Trigger the apm-review-panel gh-aw workflow

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants