Skip to content

Fix/windsurf uninstall skills cleanup#1486

Merged
danielmeppiel merged 18 commits into
microsoft:mainfrom
yoelabril:fix/windsurf-uninstall-skills-cleanup
May 27, 2026
Merged

Fix/windsurf uninstall skills cleanup#1486
danielmeppiel merged 18 commits into
microsoft:mainfrom
yoelabril:fix/windsurf-uninstall-skills-cleanup

Conversation

@yoelabril
Copy link
Copy Markdown
Contributor

Description

apm uninstall silently failed to remove deployed skill directories from .windsurf/skills/, leaving stale files behind after a package was removed.

Root cause: the windsurf target declared two overlapping primitives (agents and skills) pointing at the same deploy path, which sent uninstall through a file-only cleanup branch that errored out on a directory and swallowed the exception.

Fix: drop the redundant agents primitive from windsurf so cleanup goes through the directory-aware path. Cascade already auto-invokes skills by their description: frontmatter, so no functionality is lost.

Fixes #1481

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Maintenance / refactor

Testing

  • Tested locally
  • All existing tests pass
  • Added tests for new functionality (if applicable)

jaloncad and others added 6 commits May 26, 2026 17:53
…tall

The windsurf TargetProfile declared both 'agents' and 'skills' primitives
mapped to the same deploy path .windsurf/skills/<name>/SKILL.md. This
caused BaseIntegrator.partition_managed_files to route uninstall paths
to the agents_windsurf bucket, where AgentIntegrator.sync_for_target
called unlink() on what was actually a directory. The IsADirectoryError
was swallowed silently and skill directories survived 'apm uninstall'.

Drop the 'agents' primitive from windsurf entirely. Cascade auto-invokes
any SKILL.md under .windsurf/skills/ based on the description
frontmatter, so a separate agents primitive was redundant and was the
only source of the path collision.

Also remove the now-unreachable _write_windsurf_agent_skill transformer
in AgentIntegrator and its dedicated unit tests, and update the windsurf
scope integration tests to assert the new shape.

Closes microsoft#1481
…tall

The windsurf TargetProfile declared both 'agents' and 'skills' primitives
mapped to the same deploy path .windsurf/skills/<name>/SKILL.md. This
caused BaseIntegrator.partition_managed_files to route uninstall paths
to the agents_windsurf bucket, where AgentIntegrator.sync_for_target
called unlink() on what was actually a directory. The IsADirectoryError
was swallowed silently and skill directories survived 'apm uninstall'.

Drop the 'agents' primitive from windsurf entirely. Cascade auto-invokes
any SKILL.md under .windsurf/skills/ based on the description
frontmatter, so a separate agents primitive was redundant and was the
only source of the path collision.

Also remove the now-unreachable _write_windsurf_agent_skill transformer
in AgentIntegrator and its dedicated unit tests, and update the windsurf
scope integration tests to assert the new shape.

Closes microsoft#1481
Copilot AI review requested due to automatic review settings May 26, 2026 18:50
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Updates the Windsurf/Cascade integration to remove the agents primitive (to avoid uninstall cleanup path collisions with skills) and adds regression tests + documentation updates to lock in the new behavior.

Changes:

  • Removed Windsurf agents primitive mapping and the now-unreachable agent→skill transformer.
  • Added regression tests ensuring .windsurf/skills/<name>/ directories are correctly bucketed and removed on uninstall.
  • Updated existing tests and docs to reflect that Windsurf no longer supports an agents primitive.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/unit/integration/test_windsurf_uninstall_skills.py New regression coverage for Windsurf profile shape, routing, and uninstall directory cleanup.
tests/unit/integration/test_scope_integration.py Updates scope assertions to expect skills and forbid agents for Windsurf.
tests/unit/integration/test_data_driven_dispatch.py Removes agents_windsurf from expected bucket parity list.
tests/unit/integration/test_agent_integrator.py Removes Windsurf agent integration tests; leaves explanatory note.
tests/integration/test_integrators_validation_rules.py Removes validation tests for the deleted Windsurf agent-skill transformer.
tests/integration/test_integrators_validation_phase3b.py Same as above for phase3b validation suite.
src/apm_cli/integration/targets.py Drops Windsurf agents primitive; clarifies intended authoring path in comments.
src/apm_cli/integration/instruction_integrator.py Updates a comment to remove reference to removed Windsurf agent transformer.
src/apm_cli/integration/agent_integrator.py Removes the windsurf_agent_skill conversion path and implementation.
docs/src/content/docs/reference/targets-matrix.md Updates Windsurf capabilities to exclude agents and adds guidance to ship personas as skills.
docs/src/content/docs/producer/author-primitives/instructions-and-agents.md Clarifies that Windsurf/Gemini do not receive .agent.md and recommends skills instead.
docs/src/content/docs/integrations/ide-tool-integration.md Updates Windsurf support summary to reflect rules/skills/workflows/MCP.
docs/src/content/docs/concepts/primitives-and-targets.md Updates Windsurf notes and compatibility table to mark agents unsupported.

Comment thread src/apm_cli/integration/instruction_integrator.py Outdated
Comment thread tests/unit/integration/test_windsurf_uninstall_skills.py Outdated
@danielmeppiel danielmeppiel added the panel-review Trigger the apm-review-panel gh-aw workflow label May 27, 2026
@danielmeppiel
Copy link
Copy Markdown
Collaborator

APM Review Panel: ship_with_followups

Community-contributed fix restores the manifest-to-filesystem cleanup invariant for windsurf skills, removing dead code and pinning the fix with a 132-line regression test.

cc @yoelabril @danielmeppiel @sergio-sisternes-epam

Arbitration

All six active panelists converge on the same conclusion: the root cause (two primitives mapping to one deploy path, sending uninstall through a file-only branch that swallowed the directory error) is correctly eliminated by dropping the overlapping agents primitive and letting SkillIntegrator own cleanup. The net-negative LOC delta and the regression test pinning directory removal confirm this is a clean subtraction, not a risky refactor.

The panel's recommended findings cluster around two themes: (1) communication debt -- a missing CHANGELOG entry and missing migration callout for authors whose .apm/agents/ files previously deployed to .windsurf/skills/ -- and (2) a latent silent-failure surface where the engine does not surface stats['errors'] and does not warn when an agent primitive targets a profile that no longer supports it. Neither theme blocks the cleanup promise this PR delivers, but the CHANGELOG entry is a project policy requirement that should land in the same PR before merge.

Strategically, this PR is a net positive for defensibility: it tightens the TargetProfile contract (one primitive per subdir), removes an unreachable transformer that was accumulating maintenance cost, and demonstrates that the integration layer's test harness is mature enough for an external contributor to self-fix a bug with confidence.

Principle alignment

  • portable_by_manifest -- Uninstall now correctly removes the directory structure declared by the manifest, restoring the invariant that apm install/uninstall round-trips leave no orphan artifacts.
  • pragmatic_as_npm -- apm uninstall behaves like npm uninstall: what was installed is fully cleaned up, no manual rm -rf required.
  • oss_community_driven -- External contributor @yoelabril filed [BUG] uninstall: skill directories survive cleanup on windsurf target #1481, self-fixed with net-negative LOC, added a regression test, and updated four doc pages.

Growth amplification

PR #1486 is an exemplar community contribution: external author reported the bug, self-fixed with surgical dead-code removal, and pinned the promise with a regression test. This pattern -- file, fix, prove -- is exactly the contributor signal APM should celebrate in release notes and use as onboarding proof that the codebase is approachable enough for outside contributors to ship meaningful fixes.

Panel summary

Persona B R N Headline
python-architect 0 0 2 Correct architectural fix; consider invariant guard at the data model layer
cli-logging-expert 0 0 3 Silent-failure class persists at engine boundary; stats['errors'] not surfaced
devx-ux-expert 0 1 1 Agent-targets-windsurf is now a silent no-op with zero user feedback
supply-chain-security-expert 0 0 2 No regressions; symlink resilience test would harden coverage
oss-growth-hacker 0 2 1 CHANGELOG credit owed; targets-matrix [x] -> [ ] reads as regression
auth-expert -- -- -- Inactive -- no auth surface touched
doc-writer 0 2 2 CHANGELOG entry missing; migration callout missing for upgraders
test-coverage-expert 0 1 0 Integration floor met; e2e CLI uninstall test is the gap

Top follow-ups (curated)

  1. Add CHANGELOG.md entry with contributor credit before merge -- doc-writer + oss-growth-hacker. Project policy requires every merged PR to carry a CHANGELOG entry. Suggested line: - Fixed: apm uninstall now correctly removes .windsurf/skills/<pkg>/ directories (#1486 -- by @yoelabril).
  2. Add migration callout in producer/author-primitives/instructions-and-agents.md -- doc-writer. Authors who previously deployed .apm/agents/ to windsurf will see their personas silently stop appearing. A :::caution[Migration] block prevents confused bug reports.
  3. Emit a diagnostic when an agent primitive targets windsurf and produces nothing -- devx-ux-expert. One-line diagnostics.warn in install/services.py:267 closes the feedback gap for upgraders without blocking this fix.
  4. Surface stats['errors'] from sync_integration in the uninstall engine output -- cli-logging-expert. The same silent-failure UX class that caused [BUG] uninstall: skill directories survive cleanup on windsurf target #1481 persists at the engine boundary for permission errors; surface the counter as a warning.
  5. Add an e2e CLI subprocess test exercising apm uninstall against a windsurf workspace -- test-coverage-expert. Integration-with-fixtures floor is met; e2e is tier-above-floor.

Ship recommendation

Stance: ship_with_followups. The headline cleanup promise is correctly fixed: directory-aware uninstall is restored and pinned with a regression test that exercises the real SkillIntegrator path. Ask the author to add the CHANGELOG entry inline (project policy, one-liner) and consider the migration callout in producer docs. The remaining three follow-ups (diagnostic for silent agent no-op, engine error surfacing, e2e test) are good candidates for follow-up PRs and do not need to gate this fix.

Architecture sketch (python-architect)

Class shape post-fix:

classDiagram
    class TargetProfile {
        +name: str
        +primitive_mappings: dict[str, PrimitiveMapping]
    }
    class PrimitiveMapping {
        +subdir: str
        +extension: str
        +transformer: callable
    }
    class BaseIntegrator
    class SkillIntegrator {
        +sync_integration(stats, project_root)
    }
    class AgentIntegrator
    TargetProfile o-- PrimitiveMapping
    BaseIntegrator <|-- SkillIntegrator
    BaseIntegrator <|-- AgentIntegrator
Loading

Uninstall data flow:

flowchart LR
    A[apm uninstall pkg] --> B[partition_managed_files]
    B --> C{primitive type}
    C -->|skill| D[SkillIntegrator.sync_integration]
    D --> E[shutil.rmtree on .windsurf/skills/pkg/]
    E --> F[stats files_removed and errors]
Loading

Per-persona findings

python-architect (0 / 0 / 2)
  • [nit] TargetProfile invariant guard -- consider a __post_init__ asserting no two primitives share (subdir, extension) so future re-introduction of an overlapping mapping fails at import time. src/apm_cli/integration/targets.py:549
  • [nit] Already-deleted directory case -- add a test for the if not target.exists(): continue path in sync_integration to prevent regressions. tests/unit/integration/test_windsurf_uninstall_skills.py
cli-logging-expert (0 / 0 / 3)
  • [nit] sync_integration catches Exception on shutil.rmtree and increments stats['errors'], but the uninstall engine only reads stats['files_removed']. Permission errors during cleanup would re-create the silent-failure UX class. Consider _rich_warning when errors > 0. src/apm_cli/integration/skill_integrator.py:1361
  • [nit] In install/services.py:268, when a package declares agents and the resolved target lacks the agents key, dispatch silently skips. A one-line diagnostics.info would surface the misconfiguration. src/apm_cli/install/services.py:268
  • [nit] New tests assert filesystem state and stats counters only; no assertion verifies diagnostic emission or that a warning IS emitted when cleanup partially fails. tests/unit/integration/test_windsurf_uninstall_skills.py
devx-ux-expert (0 / 1 / 1)
  • [recommended] No diagnostic warns the author when an agent primitive targets windsurf and produces nothing. services.py:267-269 silently skips primitives unsupported by a target. The copilot-cowork target has an analogous guard at services.py:176-205. Suggested: diagnostics.warn("windsurf does not support the agents primitive; ship personas as skills under .apm/skills/<name>/SKILL.md to reach Cascade."). src/apm_cli/install/services.py:267
  • [nit] Targets-matrix column for windsurf agents went [x] -> [ ] with no migration callout; one-line note would aid the transition. docs/src/content/docs/reference/targets-matrix.md:88
supply-chain-security-expert (0 / 0 / 2)
  • [nit] New regression test does not exercise symlink resilience. A parametrized case planting a symlink at .windsurf/skills/evil -> /outside would pin shutil.rmtree's fd-based protection. tests/unit/integration/test_windsurf_uninstall_skills.py
  • [nit] sync_integration line 1349 uses str(target.resolve()).startswith(str(project_root_resolved)), vulnerable to directory-prefix collision (/home/user matching /home/username). Pre-existing; consider Path.is_relative_to() in a follow-up. src/apm_cli/integration/skill_integrator.py:1349
oss-growth-hacker (0 / 2 / 1)
  • [recommended] Add a CHANGELOG entry with contributor credit for @yoelabril. Diff has zero CHANGELOG.md changes. Suggested: - Fixed: apm uninstall now correctly removes .windsurf/skills/<pkg>/ directories (#1486 -- by @yoelabril).
  • [recommended] Targets-matrix [x] -> [ ] reads as a regression. Add a footnote or (ship as skill) inline to neutralize perceived degradation.
  • [nit] Consolidate "how to reach windsurf with personas" into one canonical sentence in targets-matrix and cross-link from the other three pages.
auth-expert (inactive)

Inactive -- PR touches only integration/targets.py, integration/agent_integrator.py, integration/instruction_integrator.py, integration tests, and doc pages; no auth-surface files.

doc-writer (0 / 2 / 2)
  • [recommended] No CHANGELOG.md entry for the windsurf agents-primitive removal, despite a user-visible behavior change. Per .apm/instructions/changelog.instructions.md. CHANGELOG.md
  • [recommended] No migration callout for existing authors whose .apm/agents/ files targeting windsurf will silently stop deploying. Add a :::caution[Migration] block in producer docs and mirror in targets-matrix. docs/src/content/docs/producer/author-primitives/instructions-and-agents.md:202
  • [nit] ide-tool-integration windsurf Notes cell drops hooks from the primitive list; should read "Rules + Skills + Workflows + Hooks + MCP". docs/src/content/docs/integrations/ide-tool-integration.md:21
  • [nit] primitives-and-targets windsurf bullet could note the path-collision rationale to mirror reference/targets-matrix. docs/src/content/docs/concepts/primitives-and-targets.md:93
test-coverage-expert (0 / 1 / 0)
  • [recommended] No e2e CLI test invokes apm uninstall against a windsurf workspace and verifies .windsurf/skills/<pkg>/ removal. Integration-with-fixtures floor IS met. Evidence: outcome=missing, tier=e2e, file=tests/integration/test_uninstall_engine_coverage.py, name=test_uninstall_removes_windsurf_skill_dir_e2e, proves='apm uninstall removes .windsurf/skills// directories end-to-end via the CLI', principles=[devx, portability-by-manifest].

This is an advisory recommendation from the APM Review Panel. It is not a merge gate. The maintainer and author weigh these signals.

@danielmeppiel danielmeppiel removed the panel-review Trigger the apm-review-panel gh-aw workflow label May 27, 2026
yoelabril added 2 commits May 27, 2026 19:34
Windsurf has no native agents primitive, so supports_at_user_scope("agents")
should return False. Update test assertion and clarify comment.
Earlier APM versions compiled `.apm/agents/*.agent.md` to `.windsurf/skills/<name>/SKILL.md` with stripped frontmatter. That mapping has been removed: agents no longer deploy to Windsurf. Add migration caution block advising users to re-author personas as skills under `.apm/skills/<name>/SKILL.md` if they need Windsurf support. Update CHANGELOG.md to document the uninstall cleanup fix for the shared deploy path that caused empty directories to survive.
@yoelabril
Copy link
Copy Markdown
Contributor Author

yoelabril commented May 27, 2026

Panel follow-ups addressed inline:

  • ✅ CHANGELOG.md entry restored under [Unreleased] > Fixed with credit (Fix/windsurf uninstall skills cleanup #1486 -- by @yoelabril)
  • :::caution[Migration] block added in producer/author-primitives/instructions-and-agents.md so authors who previously deployed .apm/agents/ to windsurf know to re-author as .apm/skills/<name>/SKILL.md

danielmeppiel added a commit to yoelabril/apm that referenced this pull request May 27, 2026
- instruction_integrator.py: reflow the yaml.safe_load comment into a
  single clear sentence so it no longer reads as an orphan continuation.
- test_windsurf_uninstall_skills.py: pivot the regression tests to
  assert filesystem outcomes primarily (managed dirs removed, user dir
  preserved). The 'files_removed' stats key is kept as a sanity check
  because it is the established cross-integrator counter convention
  (see base_integrator, hook_integrator, command_integrator,
  prompt_integrator, skill_integrator, copilot_app_workflow_integrator,
  agent_integrator) and is consumed by uninstall/engine.py counters;
  renaming would have repo-wide blast radius for a directory-counting
  edge case that affects only the windsurf skills path.

Co-authored-by: yoelabril <yoelabril@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- instruction_integrator.py: reflow the yaml.safe_load comment into a
  single clear sentence so it no longer reads as an orphan continuation.
- test_windsurf_uninstall_skills.py: pivot the regression tests to
  assert filesystem outcomes primarily (managed dirs removed, user dir
  preserved). The 'files_removed' stats key is kept as a sanity check
  because it is the established cross-integrator counter convention
  (see base_integrator, hook_integrator, command_integrator,
  prompt_integrator, skill_integrator, copilot_app_workflow_integrator,
  agent_integrator) and is consumed by uninstall/engine.py counters;
  renaming would have repo-wide blast radius for a directory-counting
  edge case that affects only the windsurf skills path.

Co-authored-by: yoelabril <abril.yoel@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@danielmeppiel danielmeppiel force-pushed the fix/windsurf-uninstall-skills-cleanup branch from 545302d to 17d9ffd Compare May 27, 2026 18:53
@danielmeppiel
Copy link
Copy Markdown
Collaborator

danielmeppiel commented May 27, 2026

APM Review Panel: ship_now

Fixes silent leftover .windsurf/skills/ directories on uninstall by removing the overlapping agents primitive from windsurf TargetProfile -- a community contributor returning to harden the surface they originally shipped.

cc @yoelabril @sergio-sisternes-epam -- a fresh advisory pass is ready for your review.

All eight panelists converge: the fix is architecturally correct (single-primitive-per-deploy-path invariant restored), supply-chain safe (stale auto-invokable content no longer lingers post-uninstall), well-tested at the right tier (integration-with-fixtures, 7 tests, 1.12s), and documented with a migration caution block. No blocking findings were raised.

The two strongest follow-up signals are (1) the supply-chain-security symlink regression trap -- tagged secure-by-default with outcome:missing evidence -- and (2) the doc-writer accuracy spot-check on manifest-schema.md re: whether AGENTS.md is still emitted for windsurf after the agents primitive removal. The symlink trap is defense-in-depth for a path already guarded by the general containment check at skill_integrator.py:1349, so it does not block merge but deserves a fast follow-up issue. The manifest-schema accuracy question is verifiable in under a minute and could be addressed pre- or post-merge at the maintainer's discretion.

The devx-ux and cli-logging experts both surfaced the same gap (silent no-op when agents exist but target lacks primitive support). They agree it is follow-up scope, not blocking. No dissent exists between panelists on severity or routing.

Aligned with: secure-by-default (eliminates stale Cascade-auto-invokable skill content left on disk after uninstall; symlink containment is pre-existing but could use a pinning test as follow-up); multi-harness/multi-host (windsurf target now has a clean single-primitive mapping; uninstall parity with other harnesses restored); OSS / community-driven (repeat contributor @yoelabril shipped the windsurf target and returned to fix its edge case within 4 weeks); pragmatic as npm (uninstall now does what users expect: removes the directory, not just the files inside it).

Growth signal. Repeat-contributor signal worth amplifying in release notes: @yoelabril shipped the windsurf target (#1066) then returned unprompted to fix uninstall cleanup (#1486) within 4 weeks. This is a retention proof-point for the contributor funnel narrative and demonstrates that external contributors can own surfaces end-to-end. Name the pattern in the CHANGELOG one-liner.

Panel summary

Persona B R N Takeaway
Python Architect 0 0 1 Clean data-model fix: removes overlapping primitive from TargetProfile, deletes dead code + tests, adds regression pin. Architecturally sound.
CLI Logging Expert 0 0 1 Removed warning was dead code coupled to a dead path; uninstall cleanup output already surfaces skill dir removal. No output regression.
DevX UX Expert 0 0 1 Migration story is solid; only ergonomic gap is the silent no-op when targeting windsurf with agents post-fix.
Supply Chain Security Expert 0 0 1 PR fix is sound from supply-chain perspective; eliminates overlapping-primitive collision that left stale Cascade-auto-invokable skill content on disk after uninstall.
OSS Growth Hacker 0 0 3 Textbook contributor-arc story; @yoelabril shipped windsurf target (#1066) and is back fixing its edge case 4 weeks later -- retention proof-point.
Doc Writer 0 0 2 Docs updates are accurate, consistent across the three matrices, and cover the migration; one nit on cross-link discoverability.
Test Coverage Expert 0 0 1 7 regression tests at integration-with-fixtures tier cover the fix; all pass in 1.12s. Ship.

B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.

Top 5 follow-ups

  1. [Supply Chain Security Expert] Add symlink regression trap test for windsurf uninstall rmtree path -- secure-by-default surface with outcome:missing evidence. General containment check exists but is not pinned for the windsurf-specific directory removal path; a future refactor could silently bypass it.
  2. [Doc Writer] Verify manifest-schema.md windsurf row accuracy re: AGENTS.md emission post agents-primitive removal -- if AGENTS.md was coupled to the removed agents primitive on windsurf, the reference doc row is now stale. One-minute verification.
  3. [DevX UX Expert] Emit diagnostics.info when target lacks agents primitive but agent sources exist -- closes discoverability gap for users migrating from agents-on-windsurf. Low effort, high first-run clarity.
  4. [Doc Writer] Add relative cross-link from migration callout to skills authoring page -- users reading the migration caution block need one click to reach the SKILL.md frontmatter reference.
  5. [OSS Growth Hacker] Polish CHANGELOG entry to a punchy one-liner crediting @yoelabril for social reshareability -- dense commit-log-style entries get zero amplification; a one-liner compounds contributor-funnel messaging.

Architecture

classDiagram
    direction LR
    class TargetProfile {
      <<ValueObject>>
      +name: str
      +root_dir: str
      +primitives: dict
      +supports(prim) bool
      +for_scope(user_scope) TargetProfile
    }
    class PrimitiveMapping {
      <<ValueObject>>
      +subdir: str
      +extension: str
      +format_id: str
      +deploy_root: str
    }
    class BaseIntegrator {
      <<Abstract>>
      +partition_managed_files(managed) dict
      +sync_remove_files()
    }
    class SkillIntegrator {
      +sync_integration(pkg, root, files, targets) dict
    }
    class AgentIntegrator {
      +integrate_agents_for_target(target, sources, root)
      +sync_integration(pkg, root, files)
    }
    TargetProfile *-- PrimitiveMapping
    BaseIntegrator ..> TargetProfile
    SkillIntegrator --|> BaseIntegrator
    AgentIntegrator --|> BaseIntegrator
    SkillIntegrator ..> TargetProfile
    AgentIntegrator ..> TargetProfile
    note for TargetProfile "Each deploy path owned by\nexactly one primitive per target"
Loading
flowchart TD
    A[apm uninstall pkg] --> B[uninstall/engine.py: build managed_files from lockfile]
    B --> C{BaseIntegrator.partition_managed_files}
    C -->|prefix .windsurf/skills/| D[skills bucket]
    C -->|prefix .github/agents/| E[agents_copilot bucket]
    D --> F[SkillIntegrator.sync_integration]
    F --> G{target.is_dir}
    G -->|Yes| H[shutil.rmtree target]
    G -->|No| I[target.unlink]
    H --> J[stats files_removed plus 1]
    I --> J
    E --> K[AgentIntegrator.sync_integration]
    K --> L[file-level unlink]
Loading
sequenceDiagram
    participant User
    participant Engine as uninstall/engine.py
    participant Base as BaseIntegrator
    participant Skill as SkillIntegrator
    participant FS as Filesystem
    User->>Engine: apm uninstall my-pkg
    Engine->>Base: partition_managed_files
    Note over Base: windsurf has no agents primitive now -- path routes to skills bucket only
    Base-->>Engine: skills .windsurf/skills/my-pkg
    Engine->>Skill: sync_integration managed_files
    Skill->>FS: resolve .windsurf/skills/my-pkg
    FS-->>Skill: is_dir True
    Skill->>FS: shutil.rmtree .windsurf/skills/my-pkg/
    Skill-->>Engine: files_removed 1 errors 0
Loading

Recommendation

Merge as-is. All signals are green: lint passes, 7 regression tests pass at integration-with-fixtures tier proving the fix, docs migration is in place, and no panelist raised a blocking or even a recommended-severity finding. The symlink trap and manifest-schema accuracy check are worth filing as fast follow-up issues but do not gate this PR -- the containment check already exists in production code and the behavior fix is proven by passing test evidence. Reward the contributor arc with a clean merge.


Full per-persona findings

Python Architect

  • [nit] Design patterns and architectural assessment for this PR
    Used in this PR: Dataclass-as-value-object (TargetProfile frozen dataclass = single source of truth for primitive->path mapping; fix propagates through partition_managed_files and SkillIntegrator without touching them). Used in this PR: Base class + subclass (SkillIntegrator inherits BaseIntegrator and owns directory-aware cleanup via sync_integration). Pragmatic suggestion: none -- the current shape is the simplest correct design at this scope. Declarative TargetProfile already encodes the invariant; regression test pins it directly.

CLI Logging Expert

  • [nit] No verbose-only migration breadcrumb when agents stop deploying to windsurf on upgrade
    A user who previously had .apm/agents/*.agent.md compiling to .windsurf/skills/ will see no CLI indication that this stopped happening after upgrading. The docs caution block covers it, but the newspaper-test principle says the CLI should surface behavioral changes where the user would notice them -- during apm install --verbose at minimum. This is polish, not blocking: behavioral change is correct, docs explain migration, removed diagnostics.warn was tightly coupled to the now-dead transformer.
    Suggested: Consider a one-time verbose_detail line in integrate_agents_for_target when target is windsurf and agents exist in the manifest. Low priority; skip if scope creep.

DevX UX Expert

  • [nit] Silent no-op when integrate_agents_for_target returns early because target lacks agents primitive at src/apm_cli/integration/agent_integrator.py:105
    When source agent files exist but the target has no agents primitive support, the user gets zero feedback. An author who previously saw agents deploy to windsurf now sees silence with no explanation. A one-time diagnostics.info() would close the discoverability gap. Consistent with existing gaps for other unsupported primitives; suitable for a follow-up issue rather than blocking this PR.
    Suggested: Add diagnostics.info('Target {target_name} does not support the agents primitive; skipping agent integration.') before the early return.

Supply Chain Security Expert

  • [nit] Add a symlink regression trap for the windsurf uninstall path at tests/unit/integration/test_windsurf_uninstall_skills.py
    Containment check at skill_integrator.py:1349 catches symlink-outside-project generally, but no test pins this for the windsurf-specific rmtree path. A symlink pointing outside project_root could bypass containment if future refactors move the check. Pinning it closes the loop.
    Suggested: Add a test case: create a managed skill dir as a symlink to outside dir, assert uninstall refuses or does not follow the symlink outside project root.
    Proof (missing at): tests/unit/integration/test_windsurf_uninstall_skills.py -- proves: Uninstall of a symlinked skill directory does not delete content outside the project root [secure-by-default]

OSS Growth Hacker

  • [nit] CHANGELOG entry is dense; distill to a one-liner hook for release-narrative reposting at CHANGELOG.md
    Release notes that read like commit logs get zero social reshares. A punchy one-liner is repostable by the contributor and by watchers, compounding reach.
    Suggested: Replace dense entry with: 'apm uninstall now fully cleans .windsurf/skills/ directories (by @yoelabril)'
  • [nit] Migration caution box in instructions-and-agents.md would compound with a before/after file-tree snippet at docs/src/content/docs/producer/author-primitives/instructions-and-agents.md
    Readers skim admonitions; a visual diff lands in under 2 seconds and reduces support questions.
    Suggested: Add a before/after file-tree snippet inside the caution box.
  • [nit] Release-notes shout-out should reinforce that the same contributor who built the feature returned to fix its edge case.
    Naming the pattern signals to prospective contributors that they can own surfaces end-to-end, not just drive-by. Compounds contributor funnel messaging.

Auth Expert -- inactive

PR touches only windsurf TargetProfile cleanup and adds uninstall regression tests; no auth-path files are modified.

Doc Writer

  • [nit] Migration callout could link to the skills authoring page so readers land on the re-author path with one click. at docs/src/content/docs/producer/author-primitives/instructions-and-agents.md
    Migration block tells readers to re-author personas as skills but does not link to producer/author-primitives/skills/ where SKILL.md frontmatter is documented. A relative link keeps the block short.
    Suggested: Change 're-author it as a skill under .apm/skills//SKILL.md' to 're-author it as a skill under .apm/skills//SKILL.md'.
  • [nit] reference/manifest-schema.md row for windsurf still lists AGENTS.md in the deploy summary; verify it is still emitted after agents primitive removal. at docs/src/content/docs/reference/manifest-schema.md:154
    Accuracy spot-check: the manifest-schema row is unchanged by the PR. If AGENTS.md is still produced (as the compiled context aggregator, independent of the agents primitive) this is fine; if AGENTS.md was tied to the removed agents primitive on windsurf, the row is now wrong.

Test Coverage Expert

  • [nit] No test exercises the full apm uninstall CLI command path for windsurf targets end-to-end. at tests/unit/integration/test_windsurf_uninstall_skills.py
    The new tests exercise SkillIntegrator.sync_integration and partition_managed_files with real filesystem fixtures (integration-with-fixtures tier; covers cross-module contract). A full e2e test invoking apm uninstall with a real lockfile and windsurf target dir would additionally cover the uninstall/engine.py orchestration layer, but the risk is low: engine.py dispatches to sync_integration unchanged, and the bug was entirely in the TargetProfile shape + integrator routing. Not blocking; the regression-trap already lives at the right boundary.
    Proof (passed): tests/unit/integration/test_windsurf_uninstall_skills.py::TestWindsurfSkillUninstallCleanup::test_sync_removes_windsurf_skill_directories -- proves: apm uninstall removes deployed .windsurf/skills// directories without touching user-authored skill dirs [multi-harness-support,devx]
    assert not managed_a.exists() and stats['files_removed'] == 2

This panel is advisory. It does not block merge. Re-apply the panel-review label after addressing feedback to re-run.

…l-skills-cleanup

# Conflicts:
#	docs/src/content/docs/producer/author-primitives/instructions-and-agents.md
…llow-up microsoft#1520

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Collaborator

@danielmeppiel danielmeppiel left a comment

Choose a reason for hiding this comment

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

Ship. Fix is contract-correct; conflicts resolved against current main; CHANGELOG threaded with follow-up #1520 (windsurf convergence onto .agents/skills/ per Cascade native discovery).

@danielmeppiel danielmeppiel merged commit 38f8640 into microsoft:main May 27, 2026
14 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.

[BUG] uninstall: skill directories survive cleanup on windsurf target

5 participants