@every-env/compound-plugin 0.8.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +3 -3
- package/AGENTS.md +5 -1
- package/CHANGELOG.md +50 -0
- package/CLAUDE.md +3 -3
- package/README.md +52 -14
- package/docs/plans/2026-02-14-feat-auto-detect-install-and-gemini-sync-plan.md +360 -0
- package/docs/plans/2026-02-25-feat-windsurf-global-scope-support-plan.md +627 -0
- package/docs/plans/2026-03-01-feat-ce-command-aliases-backwards-compatible-deprecation-plan.md +261 -0
- package/docs/plans/feature_opencode-commands-as-md-and-config-merge.md +574 -0
- package/docs/solutions/adding-converter-target-providers.md +692 -0
- package/docs/solutions/plugin-versioning-requirements.md +3 -3
- package/docs/specs/kiro.md +171 -0
- package/docs/specs/windsurf.md +477 -0
- package/package.json +1 -1
- package/plans/landing-page-launchkit-refresh.md +2 -2
- package/plugins/compound-engineering/.claude-plugin/plugin.json +2 -2
- package/plugins/compound-engineering/CHANGELOG.md +72 -1
- package/plugins/compound-engineering/CLAUDE.md +9 -7
- package/plugins/compound-engineering/README.md +10 -7
- package/plugins/compound-engineering/agents/research/git-history-analyzer.md +1 -1
- package/plugins/compound-engineering/agents/research/learnings-researcher.md +1 -1
- package/plugins/compound-engineering/agents/review/code-simplicity-reviewer.md +1 -1
- package/plugins/compound-engineering/commands/ce/brainstorm.md +145 -0
- package/plugins/compound-engineering/commands/ce/compound.md +240 -0
- package/plugins/compound-engineering/commands/ce/plan.md +636 -0
- package/plugins/compound-engineering/commands/ce/review.md +525 -0
- package/plugins/compound-engineering/commands/ce/work.md +470 -0
- package/plugins/compound-engineering/commands/create-agent-skill.md +1 -1
- package/plugins/compound-engineering/commands/deepen-plan.md +6 -6
- package/plugins/compound-engineering/commands/deploy-docs.md +1 -1
- package/plugins/compound-engineering/commands/feature-video.md +15 -6
- package/plugins/compound-engineering/commands/heal-skill.md +1 -1
- package/plugins/compound-engineering/commands/lfg.md +3 -3
- package/plugins/compound-engineering/commands/slfg.md +3 -3
- package/plugins/compound-engineering/commands/test-xcode.md +2 -2
- package/plugins/compound-engineering/commands/workflows/brainstorm.md +4 -123
- package/plugins/compound-engineering/commands/workflows/compound.md +4 -234
- package/plugins/compound-engineering/commands/workflows/plan.md +4 -562
- package/plugins/compound-engineering/commands/workflows/review.md +4 -522
- package/plugins/compound-engineering/commands/workflows/work.md +4 -448
- package/plugins/compound-engineering/skills/brainstorming/SKILL.md +3 -3
- package/plugins/compound-engineering/skills/document-review/SKILL.md +1 -1
- package/plugins/compound-engineering/skills/file-todos/SKILL.md +1 -1
- package/plugins/compound-engineering/skills/git-worktree/SKILL.md +5 -5
- package/plugins/compound-engineering/skills/proof/SKILL.md +185 -0
- package/plugins/compound-engineering/skills/resolve-pr-parallel/SKILL.md +1 -1
- package/plugins/compound-engineering/skills/setup/SKILL.md +2 -2
- package/src/commands/convert.ts +101 -23
- package/src/commands/install.ts +102 -41
- package/src/commands/sync.ts +58 -38
- package/src/converters/claude-to-kiro.ts +262 -0
- package/src/converters/claude-to-openclaw.ts +240 -0
- package/src/converters/claude-to-opencode.ts +12 -10
- package/src/converters/claude-to-qwen.ts +238 -0
- package/src/converters/claude-to-windsurf.ts +205 -0
- package/src/sync/gemini.ts +76 -0
- package/src/targets/index.ts +69 -1
- package/src/targets/kiro.ts +122 -0
- package/src/targets/openclaw.ts +96 -0
- package/src/targets/opencode.ts +76 -10
- package/src/targets/qwen.ts +64 -0
- package/src/targets/windsurf.ts +104 -0
- package/src/types/kiro.ts +44 -0
- package/src/types/openclaw.ts +52 -0
- package/src/types/opencode.ts +7 -8
- package/src/types/qwen.ts +48 -0
- package/src/types/windsurf.ts +34 -0
- package/src/utils/detect-tools.ts +46 -0
- package/src/utils/files.ts +7 -0
- package/src/utils/resolve-output.ts +50 -0
- package/src/utils/secrets.ts +24 -0
- package/tests/cli.test.ts +78 -0
- package/tests/converter.test.ts +43 -10
- package/tests/detect-tools.test.ts +96 -0
- package/tests/kiro-converter.test.ts +381 -0
- package/tests/kiro-writer.test.ts +273 -0
- package/tests/openclaw-converter.test.ts +200 -0
- package/tests/opencode-writer.test.ts +142 -5
- package/tests/qwen-converter.test.ts +238 -0
- package/tests/resolve-output.test.ts +131 -0
- package/tests/sync-gemini.test.ts +106 -0
- package/tests/windsurf-converter.test.ts +573 -0
- package/tests/windsurf-writer.test.ts +359 -0
- package/docs/css/docs.css +0 -675
- package/docs/css/style.css +0 -2886
- package/docs/index.html +0 -1046
- package/docs/js/main.js +0 -225
- package/docs/pages/agents.html +0 -649
- package/docs/pages/changelog.html +0 -534
- package/docs/pages/commands.html +0 -523
- package/docs/pages/getting-started.html +0 -582
- package/docs/pages/mcp-servers.html +0 -409
- package/docs/pages/skills.html +0 -611
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Windsurf Global Scope Support
|
|
3
|
+
type: feat
|
|
4
|
+
status: completed
|
|
5
|
+
date: 2026-02-25
|
|
6
|
+
deepened: 2026-02-25
|
|
7
|
+
prior: docs/plans/2026-02-23-feat-add-windsurf-target-provider-plan.md (removed — superseded)
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Windsurf Global Scope Support
|
|
11
|
+
|
|
12
|
+
## Post-Implementation Revisions (2026-02-26)
|
|
13
|
+
|
|
14
|
+
After auditing the implementation against `docs/specs/windsurf.md`, two significant changes were made:
|
|
15
|
+
|
|
16
|
+
1. **Agents → Skills (not Workflows)**: Claude agents map to Windsurf Skills (`skills/{name}/SKILL.md`), not Workflows. Skills are "complex multi-step tasks with supporting resources" — a better conceptual match for specialized expertise/personas. Workflows are "reusable step-by-step procedures" — a better match for Claude Commands (slash commands).
|
|
17
|
+
|
|
18
|
+
2. **Workflows are flat files**: Command workflows are written to `global_workflows/{name}.md` (global scope) or `workflows/{name}.md` (workspace scope). No subdirectories — the spec requires flat files.
|
|
19
|
+
|
|
20
|
+
3. **Content transforms updated**: `@agent-name` references are kept as-is (Windsurf skill invocation syntax). `/command` references produce `/{name}` (not `/commands/{name}`). `Task agent(args)` produces `Use the @agent-name skill: args`.
|
|
21
|
+
|
|
22
|
+
### Final Component Mapping (per spec)
|
|
23
|
+
|
|
24
|
+
| Claude Code | Windsurf | Output Path | Invocation |
|
|
25
|
+
|---|---|---|---|
|
|
26
|
+
| Agents (`.md`) | Skills | `skills/{name}/SKILL.md` | `@skill-name` or automatic |
|
|
27
|
+
| Commands (`.md`) | Workflows (flat) | `global_workflows/{name}.md` (global) / `workflows/{name}.md` (workspace) | `/{workflow-name}` |
|
|
28
|
+
| Skills (`SKILL.md`) | Skills (pass-through) | `skills/{name}/SKILL.md` | `@skill-name` |
|
|
29
|
+
| MCP servers | `mcp_config.json` | `mcp_config.json` | N/A |
|
|
30
|
+
| Hooks | Skipped with warning | N/A | N/A |
|
|
31
|
+
| CLAUDE.md | Skipped | N/A | N/A |
|
|
32
|
+
|
|
33
|
+
### Files Changed in Revision
|
|
34
|
+
|
|
35
|
+
- `src/types/windsurf.ts` — `agentWorkflows` → `agentSkills: WindsurfGeneratedSkill[]`
|
|
36
|
+
- `src/converters/claude-to-windsurf.ts` — `convertAgentToSkill()`, updated content transforms
|
|
37
|
+
- `src/targets/windsurf.ts` — Skills written as `skills/{name}/SKILL.md`, flat workflows
|
|
38
|
+
- Tests updated to match
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Enhancement Summary
|
|
43
|
+
|
|
44
|
+
**Deepened on:** 2026-02-25
|
|
45
|
+
**Research agents used:** architecture-strategist, kieran-typescript-reviewer, security-sentinel, code-simplicity-reviewer, pattern-recognition-specialist
|
|
46
|
+
**External research:** Windsurf MCP docs, Windsurf tutorial docs
|
|
47
|
+
|
|
48
|
+
### Key Improvements from Deepening
|
|
49
|
+
1. **HTTP/SSE servers should be INCLUDED** — Windsurf supports all 3 transport types (stdio, Streamable HTTP, SSE). Original plan incorrectly skipped them.
|
|
50
|
+
2. **File permissions: use `0o600`** — `mcp_config.json` contains secrets and must not be world-readable. Add secure write support.
|
|
51
|
+
3. **Extract `resolveTargetOutputRoot` to shared utility** — both commands duplicate this; adding scope makes it worse. Extract first.
|
|
52
|
+
4. **Bug fix: missing `result[name] = entry`** — all 5 review agents caught a copy-paste bug in the `buildMcpConfig` sample code.
|
|
53
|
+
5. **`hasPotentialSecrets` to shared utility** — currently in sync.ts, would be duplicated. Extract to `src/utils/secrets.ts`.
|
|
54
|
+
6. **Windsurf `mcp_config.json` is global-only** — per Windsurf docs, no per-project MCP config support. Workspace scope writes it for forward-compatibility but emit a warning.
|
|
55
|
+
7. **Windsurf supports `${env:VAR}` interpolation** — consider writing env var references instead of literal values for secrets.
|
|
56
|
+
|
|
57
|
+
### New Considerations Discovered
|
|
58
|
+
- Backup files accumulate with secrets and are never cleaned up — cap at 3 backups
|
|
59
|
+
- Workspace `mcp_config.json` could be committed to git — warn about `.gitignore`
|
|
60
|
+
- `WindsurfMcpServerEntry` type needs `serverUrl` field for HTTP/SSE servers
|
|
61
|
+
- Simplicity reviewer recommends handling scope as windsurf-specific in CLI rather than generic `TargetHandler` fields — but brainstorm explicitly chose "generic with windsurf as first adopter". **Decision: keep generic approach** per user's brainstorm decision, with JSDoc documenting the relationship between `defaultScope` and `supportedScopes`.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Overview
|
|
66
|
+
|
|
67
|
+
Add a generic `--scope global|workspace` flag to the converter CLI with Windsurf as the first adopter. Global scope writes to `~/.codeium/windsurf/`, making workflows, skills, and MCP servers available across all projects. This also upgrades MCP handling from a human-readable setup doc (`mcp-setup.md`) to a proper machine-readable config (`mcp_config.json`), and removes AGENTS.md generation (the plugin's CLAUDE.md contains development-internal instructions, not user-facing content).
|
|
68
|
+
|
|
69
|
+
## Problem Statement / Motivation
|
|
70
|
+
|
|
71
|
+
The current Windsurf converter (v0.10.0) writes everything to project-level `.windsurf/`, requiring re-installation per project. Windsurf supports global paths for skills (`~/.codeium/windsurf/skills/`) and MCP config (`~/.codeium/windsurf/mcp_config.json`). Users should install once and get capabilities everywhere.
|
|
72
|
+
|
|
73
|
+
Additionally, the v0.10.0 MCP output was a markdown setup guide — not an actual integration. Windsurf reads `mcp_config.json` directly, so we should write to that file.
|
|
74
|
+
|
|
75
|
+
## Breaking Changes from v0.10.0
|
|
76
|
+
|
|
77
|
+
This is a **minor version bump** (v0.11.0) with intentional breaking changes to the experimental Windsurf target:
|
|
78
|
+
|
|
79
|
+
1. **Default output location changed** — `--to windsurf` now defaults to global scope (`~/.codeium/windsurf/`). Use `--scope workspace` for the old behavior.
|
|
80
|
+
2. **AGENTS.md no longer generated** — old files are left in place (not deleted).
|
|
81
|
+
3. **`mcp-setup.md` replaced by `mcp_config.json`** — proper machine-readable integration. Old files left in place.
|
|
82
|
+
4. **Env var secrets included with warning** — previously redacted, now included (required for the config file to work).
|
|
83
|
+
5. **`--output` semantics changed** — `--output` now specifies the direct target directory (not a parent where `.windsurf/` is created).
|
|
84
|
+
|
|
85
|
+
## Proposed Solution
|
|
86
|
+
|
|
87
|
+
### Phase 0: Extract Shared Utilities (prerequisite)
|
|
88
|
+
|
|
89
|
+
**Files:** `src/utils/resolve-output.ts` (new), `src/utils/secrets.ts` (new)
|
|
90
|
+
|
|
91
|
+
#### 0a. Extract `resolveTargetOutputRoot` to shared utility
|
|
92
|
+
|
|
93
|
+
Both `install.ts` and `convert.ts` have near-identical `resolveTargetOutputRoot` functions that are already diverging (`hasExplicitOutput` exists in install.ts but not convert.ts). Adding scope would make the duplication worse.
|
|
94
|
+
|
|
95
|
+
- [x] Create `src/utils/resolve-output.ts` with a unified function:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import os from "os"
|
|
99
|
+
import path from "path"
|
|
100
|
+
import type { TargetScope } from "../targets"
|
|
101
|
+
|
|
102
|
+
export function resolveTargetOutputRoot(options: {
|
|
103
|
+
targetName: string
|
|
104
|
+
outputRoot: string
|
|
105
|
+
codexHome: string
|
|
106
|
+
piHome: string
|
|
107
|
+
hasExplicitOutput: boolean
|
|
108
|
+
scope?: TargetScope
|
|
109
|
+
}): string {
|
|
110
|
+
const { targetName, outputRoot, codexHome, piHome, hasExplicitOutput, scope } = options
|
|
111
|
+
if (targetName === "codex") return codexHome
|
|
112
|
+
if (targetName === "pi") return piHome
|
|
113
|
+
if (targetName === "droid") return path.join(os.homedir(), ".factory")
|
|
114
|
+
if (targetName === "cursor") {
|
|
115
|
+
const base = hasExplicitOutput ? outputRoot : process.cwd()
|
|
116
|
+
return path.join(base, ".cursor")
|
|
117
|
+
}
|
|
118
|
+
if (targetName === "gemini") {
|
|
119
|
+
const base = hasExplicitOutput ? outputRoot : process.cwd()
|
|
120
|
+
return path.join(base, ".gemini")
|
|
121
|
+
}
|
|
122
|
+
if (targetName === "copilot") {
|
|
123
|
+
const base = hasExplicitOutput ? outputRoot : process.cwd()
|
|
124
|
+
return path.join(base, ".github")
|
|
125
|
+
}
|
|
126
|
+
if (targetName === "kiro") {
|
|
127
|
+
const base = hasExplicitOutput ? outputRoot : process.cwd()
|
|
128
|
+
return path.join(base, ".kiro")
|
|
129
|
+
}
|
|
130
|
+
if (targetName === "windsurf") {
|
|
131
|
+
if (hasExplicitOutput) return outputRoot
|
|
132
|
+
if (scope === "global") return path.join(os.homedir(), ".codeium", "windsurf")
|
|
133
|
+
return path.join(process.cwd(), ".windsurf")
|
|
134
|
+
}
|
|
135
|
+
return outputRoot
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- [x] Update `install.ts` to import and call `resolveTargetOutputRoot` from shared utility
|
|
140
|
+
- [x] Update `convert.ts` to import and call `resolveTargetOutputRoot` from shared utility
|
|
141
|
+
- [x] Add `hasExplicitOutput` tracking to `convert.ts` (currently missing)
|
|
142
|
+
|
|
143
|
+
### Research Insights (Phase 0)
|
|
144
|
+
|
|
145
|
+
**Architecture review:** Both commands will call the same function with the same signature. This eliminates the divergence and ensures scope resolution has a single source of truth. The `--also` loop in both commands also uses this function with `handler.defaultScope`.
|
|
146
|
+
|
|
147
|
+
**Pattern review:** This follows the same extraction pattern as `resolveTargetHome` in `src/utils/resolve-home.ts`.
|
|
148
|
+
|
|
149
|
+
#### 0b. Extract `hasPotentialSecrets` to shared utility
|
|
150
|
+
|
|
151
|
+
Currently in `sync.ts:20-31`. The same regex pattern also appears in `claude-to-windsurf.ts:223` as `redactEnvValue`. Extract to avoid a third copy.
|
|
152
|
+
|
|
153
|
+
- [x] Create `src/utils/secrets.ts`:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const SENSITIVE_PATTERN = /key|token|secret|password|credential|api_key/i
|
|
157
|
+
|
|
158
|
+
export function hasPotentialSecrets(
|
|
159
|
+
servers: Record<string, { env?: Record<string, string> }>,
|
|
160
|
+
): boolean {
|
|
161
|
+
for (const server of Object.values(servers)) {
|
|
162
|
+
if (server.env) {
|
|
163
|
+
for (const key of Object.keys(server.env)) {
|
|
164
|
+
if (SENSITIVE_PATTERN.test(key)) return true
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return false
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
- [x] Update `sync.ts` to import from shared utility
|
|
173
|
+
- [x] Use in new windsurf converter
|
|
174
|
+
|
|
175
|
+
### Phase 1: Types and TargetHandler
|
|
176
|
+
|
|
177
|
+
**Files:** `src/types/windsurf.ts`, `src/targets/index.ts`
|
|
178
|
+
|
|
179
|
+
#### 1a. Update WindsurfBundle type
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// src/types/windsurf.ts
|
|
183
|
+
export type WindsurfMcpServerEntry = {
|
|
184
|
+
command?: string
|
|
185
|
+
args?: string[]
|
|
186
|
+
env?: Record<string, string>
|
|
187
|
+
serverUrl?: string
|
|
188
|
+
headers?: Record<string, string>
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export type WindsurfMcpConfig = {
|
|
192
|
+
mcpServers: Record<string, WindsurfMcpServerEntry>
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export type WindsurfBundle = {
|
|
196
|
+
agentWorkflows: WindsurfWorkflow[]
|
|
197
|
+
commandWorkflows: WindsurfWorkflow[]
|
|
198
|
+
skillDirs: WindsurfSkillDir[]
|
|
199
|
+
mcpConfig: WindsurfMcpConfig | null
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
- [x] Remove `agentsMd: string | null`
|
|
204
|
+
- [x] Replace `mcpSetupDoc: string | null` with `mcpConfig: WindsurfMcpConfig | null`
|
|
205
|
+
- [x] Add `WindsurfMcpServerEntry` (supports both stdio and HTTP/SSE) and `WindsurfMcpConfig` types
|
|
206
|
+
|
|
207
|
+
### Research Insights (Phase 1a)
|
|
208
|
+
|
|
209
|
+
**Windsurf docs confirm** three transport types: stdio (`command` + `args`), Streamable HTTP (`serverUrl`), and SSE (`serverUrl` or `url`). The `WindsurfMcpServerEntry` type must support all three — making `command` optional and adding `serverUrl` and `headers` fields.
|
|
210
|
+
|
|
211
|
+
**TypeScript reviewer:** Consider making `WindsurfMcpServerEntry` a discriminated union if strict typing is desired. However, since this mirrors JSON config structure, a flat type with optional fields is pragmatically simpler.
|
|
212
|
+
|
|
213
|
+
#### 1b. Add TargetScope to TargetHandler
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// src/targets/index.ts
|
|
217
|
+
export type TargetScope = "global" | "workspace"
|
|
218
|
+
|
|
219
|
+
export type TargetHandler<TBundle = unknown> = {
|
|
220
|
+
name: string
|
|
221
|
+
implemented: boolean
|
|
222
|
+
/**
|
|
223
|
+
* Default scope when --scope is not provided.
|
|
224
|
+
* Only meaningful when supportedScopes is defined.
|
|
225
|
+
* Falls back to "workspace" if absent.
|
|
226
|
+
*/
|
|
227
|
+
defaultScope?: TargetScope
|
|
228
|
+
/** Valid scope values. If absent, the --scope flag is rejected for this target. */
|
|
229
|
+
supportedScopes?: TargetScope[]
|
|
230
|
+
convert: (plugin: ClaudePlugin, options: ClaudeToOpenCodeOptions) => TBundle | null
|
|
231
|
+
write: (outputRoot: string, bundle: TBundle) => Promise<void>
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
- [x] Add `TargetScope` type export
|
|
236
|
+
- [x] Add `defaultScope?` and `supportedScopes?` to `TargetHandler` with JSDoc
|
|
237
|
+
- [x] Set windsurf target: `defaultScope: "global"`, `supportedScopes: ["global", "workspace"]`
|
|
238
|
+
- [x] No changes to other targets (they have no scope fields, flag is ignored)
|
|
239
|
+
|
|
240
|
+
### Research Insights (Phase 1b)
|
|
241
|
+
|
|
242
|
+
**Simplicity review:** Argued this is premature generalization (only 1 of 8 targets uses scopes). Recommended handling scope as windsurf-specific with `if (targetName !== "windsurf")` guard instead. **Decision: keep generic approach** per brainstorm decision "Generic with windsurf as first adopter", but add JSDoc documenting the invariant.
|
|
243
|
+
|
|
244
|
+
**TypeScript review:** Suggested a `ScopeConfig` grouped object to prevent `defaultScope` without `supportedScopes`. The JSDoc approach is simpler and sufficient for now.
|
|
245
|
+
|
|
246
|
+
**Architecture review:** Adding optional fields to `TargetHandler` follows Open/Closed Principle — existing targets are unaffected. Clean extension.
|
|
247
|
+
|
|
248
|
+
### Phase 2: Converter Changes
|
|
249
|
+
|
|
250
|
+
**Files:** `src/converters/claude-to-windsurf.ts`
|
|
251
|
+
|
|
252
|
+
#### 2a. Remove AGENTS.md generation
|
|
253
|
+
|
|
254
|
+
- [x] Remove `buildAgentsMd()` function
|
|
255
|
+
- [x] Remove `agentsMd` from return value
|
|
256
|
+
|
|
257
|
+
#### 2b. Replace MCP setup doc with MCP config
|
|
258
|
+
|
|
259
|
+
- [x] Remove `buildMcpSetupDoc()` function
|
|
260
|
+
- [x] Remove `redactEnvValue()` helper
|
|
261
|
+
- [x] Add `buildMcpConfig()` that returns `WindsurfMcpConfig | null`
|
|
262
|
+
- [x] Include **all** env vars (including secrets) — no redaction
|
|
263
|
+
- [x] Use shared `hasPotentialSecrets()` from `src/utils/secrets.ts`
|
|
264
|
+
- [x] Include **both** stdio and HTTP/SSE servers (Windsurf supports all transport types)
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
function buildMcpConfig(
|
|
268
|
+
servers?: Record<string, ClaudeMcpServer>,
|
|
269
|
+
): WindsurfMcpConfig | null {
|
|
270
|
+
if (!servers || Object.keys(servers).length === 0) return null
|
|
271
|
+
|
|
272
|
+
const result: Record<string, WindsurfMcpServerEntry> = {}
|
|
273
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
274
|
+
if (server.command) {
|
|
275
|
+
// stdio transport
|
|
276
|
+
const entry: WindsurfMcpServerEntry = { command: server.command }
|
|
277
|
+
if (server.args?.length) entry.args = server.args
|
|
278
|
+
if (server.env && Object.keys(server.env).length > 0) entry.env = server.env
|
|
279
|
+
result[name] = entry
|
|
280
|
+
} else if (server.url) {
|
|
281
|
+
// HTTP/SSE transport
|
|
282
|
+
const entry: WindsurfMcpServerEntry = { serverUrl: server.url }
|
|
283
|
+
if (server.headers && Object.keys(server.headers).length > 0) entry.headers = server.headers
|
|
284
|
+
if (server.env && Object.keys(server.env).length > 0) entry.env = server.env
|
|
285
|
+
result[name] = entry
|
|
286
|
+
} else {
|
|
287
|
+
console.warn(`Warning: MCP server "${name}" has no command or URL. Skipping.`)
|
|
288
|
+
continue
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (Object.keys(result).length === 0) return null
|
|
293
|
+
|
|
294
|
+
// Warn about secrets (don't redact — they're needed for the config to work)
|
|
295
|
+
if (hasPotentialSecrets(result)) {
|
|
296
|
+
console.warn(
|
|
297
|
+
"Warning: MCP servers contain env vars that may include secrets (API keys, tokens).\n" +
|
|
298
|
+
" These will be written to mcp_config.json. Review before sharing the config file.",
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return { mcpServers: result }
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Research Insights (Phase 2)
|
|
307
|
+
|
|
308
|
+
**Windsurf docs (critical correction):** Windsurf supports **stdio, Streamable HTTP, and SSE** transports in `mcp_config.json`. HTTP/SSE servers use `serverUrl` (not `url`). The original plan incorrectly planned to skip HTTP/SSE servers. This is now corrected — all transport types are included.
|
|
309
|
+
|
|
310
|
+
**All 5 review agents flagged:** The original code sample was missing `result[name] = entry` — the entry was built but never stored. Fixed above.
|
|
311
|
+
|
|
312
|
+
**Security review:** The warning message should enumerate which specific env var names triggered detection. Enhanced version:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
if (hasPotentialSecrets(result)) {
|
|
316
|
+
const flagged = Object.entries(result)
|
|
317
|
+
.filter(([, s]) => s.env && Object.keys(s.env).some(k => SENSITIVE_PATTERN.test(k)))
|
|
318
|
+
.map(([name]) => name)
|
|
319
|
+
console.warn(
|
|
320
|
+
`Warning: MCP servers contain env vars that may include secrets: ${flagged.join(", ")}.\n` +
|
|
321
|
+
" These will be written to mcp_config.json. Review before sharing the config file.",
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Windsurf env var interpolation:** Windsurf supports `${env:VARIABLE_NAME}` syntax in `mcp_config.json`. Future enhancement: write env var references instead of literal values for secrets. Out of scope for v0.11.0 (requires more research on which fields support interpolation).
|
|
327
|
+
|
|
328
|
+
### Phase 3: Writer Changes
|
|
329
|
+
|
|
330
|
+
**Files:** `src/targets/windsurf.ts`, `src/utils/files.ts`
|
|
331
|
+
|
|
332
|
+
#### 3a. Simplify writer — remove AGENTS.md and double-nesting guard
|
|
333
|
+
|
|
334
|
+
The writer always writes directly into `outputRoot`. The CLI resolves the correct output root based on scope.
|
|
335
|
+
|
|
336
|
+
- [x] Remove AGENTS.md writing block (lines 10-17)
|
|
337
|
+
- [x] Remove `resolveWindsurfPaths()` — no longer needed
|
|
338
|
+
- [x] Write workflows, skills, and MCP config directly into `outputRoot`
|
|
339
|
+
|
|
340
|
+
### Research Insights (Phase 3a)
|
|
341
|
+
|
|
342
|
+
**Pattern review (dissent):** Every other writer (kiro, copilot, gemini, droid) has a `resolve*Paths()` function with a double-nesting guard. Removing it makes Windsurf the only target where the CLI fully owns nesting. This creates an inconsistency in the `write()` contract.
|
|
343
|
+
|
|
344
|
+
**Resolution:** Accept the divergence — Windsurf has genuinely different semantics (global vs workspace). Add a JSDoc comment on `TargetHandler.write()` documenting that some writers may apply additional nesting while the Windsurf writer expects the final resolved path. Long-term, other targets could migrate to this pattern in a separate refactor.
|
|
345
|
+
|
|
346
|
+
#### 3b. Replace MCP setup doc with JSON config merge
|
|
347
|
+
|
|
348
|
+
Follow Kiro pattern (`src/targets/kiro.ts:68-92`) with security hardening:
|
|
349
|
+
|
|
350
|
+
- [x] Read existing `mcp_config.json` if present
|
|
351
|
+
- [x] Backup before overwrite (`backupFile()`)
|
|
352
|
+
- [x] Parse existing JSON (warn and replace if corrupted; add `!Array.isArray()` guard)
|
|
353
|
+
- [x] Merge at `mcpServers` key: plugin entries overwrite same-name entries, user entries preserved
|
|
354
|
+
- [x] Preserve all other top-level keys in existing file
|
|
355
|
+
- [x] Write merged result with **restrictive permissions** (`0o600`)
|
|
356
|
+
- [x] Emit warning when writing to workspace scope (Windsurf `mcp_config.json` is global-only per docs)
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
// MCP config merge with security hardening
|
|
360
|
+
if (bundle.mcpConfig) {
|
|
361
|
+
const mcpPath = path.join(outputRoot, "mcp_config.json")
|
|
362
|
+
const backupPath = await backupFile(mcpPath)
|
|
363
|
+
if (backupPath) {
|
|
364
|
+
console.log(`Backed up existing mcp_config.json to ${backupPath}`)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
let existingConfig: Record<string, unknown> = {}
|
|
368
|
+
if (await pathExists(mcpPath)) {
|
|
369
|
+
try {
|
|
370
|
+
const parsed = await readJson<unknown>(mcpPath)
|
|
371
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
372
|
+
existingConfig = parsed as Record<string, unknown>
|
|
373
|
+
}
|
|
374
|
+
} catch {
|
|
375
|
+
console.warn("Warning: existing mcp_config.json could not be parsed and will be replaced.")
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const existingServers =
|
|
380
|
+
existingConfig.mcpServers &&
|
|
381
|
+
typeof existingConfig.mcpServers === "object" &&
|
|
382
|
+
!Array.isArray(existingConfig.mcpServers)
|
|
383
|
+
? (existingConfig.mcpServers as Record<string, unknown>)
|
|
384
|
+
: {}
|
|
385
|
+
const merged = { ...existingConfig, mcpServers: { ...existingServers, ...bundle.mcpConfig.mcpServers } }
|
|
386
|
+
await writeJsonSecure(mcpPath, merged) // 0o600 permissions
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Research Insights (Phase 3b)
|
|
391
|
+
|
|
392
|
+
**Security review (HIGH):** The current `writeJson()` in `src/utils/files.ts` uses default umask (`0o644`) — world-readable. The sync targets all use `{ mode: 0o600 }` for secret-containing files. The Windsurf writer (and Kiro writer) must do the same.
|
|
393
|
+
|
|
394
|
+
**Implementation:** Add a `writeJsonSecure()` helper or add a `mode` parameter to `writeJson()`:
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
// src/utils/files.ts
|
|
398
|
+
export async function writeJsonSecure(filePath: string, data: unknown): Promise<void> {
|
|
399
|
+
const content = JSON.stringify(data, null, 2)
|
|
400
|
+
await ensureDir(path.dirname(filePath))
|
|
401
|
+
await fs.writeFile(filePath, content + "\n", { encoding: "utf8", mode: 0o600 })
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**Security review (MEDIUM):** Backup files inherit default permissions. Ensure `backupFile()` also sets `0o600` on the backup copy when the source may contain secrets.
|
|
406
|
+
|
|
407
|
+
**Security review (MEDIUM):** Workspace `mcp_config.json` could be committed to git. After writing to workspace scope, emit a warning:
|
|
408
|
+
|
|
409
|
+
```
|
|
410
|
+
Warning: .windsurf/mcp_config.json may contain secrets. Ensure it is in .gitignore.
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**TypeScript review:** The `readJson<Record<string, unknown>>` assertion is unsafe — a valid JSON array or string passes parsing but fails the type. Added `!Array.isArray()` guard.
|
|
414
|
+
|
|
415
|
+
**TypeScript review:** The `bundle.mcpConfig` null check is sufficient — when non-null, `mcpServers` is guaranteed to have entries (the converter returns null for empty servers). Simplified from `bundle.mcpConfig && Object.keys(...)`.
|
|
416
|
+
|
|
417
|
+
**Windsurf docs (important):** `mcp_config.json` is a **global configuration only** — Windsurf has no per-project MCP config support. Writing it to `.windsurf/` in workspace scope may not be discovered by Windsurf. Emit a warning for workspace scope but still write the file for forward-compatibility.
|
|
418
|
+
|
|
419
|
+
#### 3c. Updated writer structure
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
export async function writeWindsurfBundle(outputRoot: string, bundle: WindsurfBundle): Promise<void> {
|
|
423
|
+
await ensureDir(outputRoot)
|
|
424
|
+
|
|
425
|
+
// Write agent workflows
|
|
426
|
+
if (bundle.agentWorkflows.length > 0) {
|
|
427
|
+
const agentDir = path.join(outputRoot, "workflows", "agents")
|
|
428
|
+
await ensureDir(agentDir)
|
|
429
|
+
for (const workflow of bundle.agentWorkflows) {
|
|
430
|
+
validatePathSafe(workflow.name, "agent workflow")
|
|
431
|
+
const content = formatFrontmatter({ description: workflow.description }, `# ${workflow.name}\n\n${workflow.body}`)
|
|
432
|
+
await writeText(path.join(agentDir, `${workflow.name}.md`), content + "\n")
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Write command workflows
|
|
437
|
+
if (bundle.commandWorkflows.length > 0) {
|
|
438
|
+
const cmdDir = path.join(outputRoot, "workflows", "commands")
|
|
439
|
+
await ensureDir(cmdDir)
|
|
440
|
+
for (const workflow of bundle.commandWorkflows) {
|
|
441
|
+
validatePathSafe(workflow.name, "command workflow")
|
|
442
|
+
const content = formatFrontmatter({ description: workflow.description }, `# ${workflow.name}\n\n${workflow.body}`)
|
|
443
|
+
await writeText(path.join(cmdDir, `${workflow.name}.md`), content + "\n")
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Copy skill directories
|
|
448
|
+
if (bundle.skillDirs.length > 0) {
|
|
449
|
+
const skillsDir = path.join(outputRoot, "skills")
|
|
450
|
+
await ensureDir(skillsDir)
|
|
451
|
+
for (const skill of bundle.skillDirs) {
|
|
452
|
+
validatePathSafe(skill.name, "skill directory")
|
|
453
|
+
const destDir = path.join(skillsDir, skill.name)
|
|
454
|
+
const resolvedDest = path.resolve(destDir)
|
|
455
|
+
if (!resolvedDest.startsWith(path.resolve(skillsDir))) {
|
|
456
|
+
console.warn(`Warning: Skill name "${skill.name}" escapes skills/. Skipping.`)
|
|
457
|
+
continue
|
|
458
|
+
}
|
|
459
|
+
await copyDir(skill.sourceDir, destDir)
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Merge MCP config (see 3b above)
|
|
464
|
+
if (bundle.mcpConfig) {
|
|
465
|
+
// ... merge logic from 3b
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Phase 4: CLI Wiring
|
|
471
|
+
|
|
472
|
+
**Files:** `src/commands/install.ts`, `src/commands/convert.ts`
|
|
473
|
+
|
|
474
|
+
#### 4a. Add `--scope` flag to both commands
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
scope: {
|
|
478
|
+
type: "string",
|
|
479
|
+
description: "Scope level: global | workspace (default varies by target)",
|
|
480
|
+
},
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
- [x] Add `scope` arg to `install.ts`
|
|
484
|
+
- [x] Add `scope` arg to `convert.ts`
|
|
485
|
+
|
|
486
|
+
#### 4b. Validate scope with type guard
|
|
487
|
+
|
|
488
|
+
Use a proper type guard instead of unsafe `as TargetScope` cast:
|
|
489
|
+
|
|
490
|
+
```typescript
|
|
491
|
+
function isTargetScope(value: string): value is TargetScope {
|
|
492
|
+
return value === "global" || value === "workspace"
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const scopeValue = args.scope ? String(args.scope) : undefined
|
|
496
|
+
if (scopeValue !== undefined) {
|
|
497
|
+
if (!target.supportedScopes) {
|
|
498
|
+
throw new Error(`Target "${targetName}" does not support the --scope flag.`)
|
|
499
|
+
}
|
|
500
|
+
if (!isTargetScope(scopeValue) || !target.supportedScopes.includes(scopeValue)) {
|
|
501
|
+
throw new Error(`Target "${targetName}" does not support --scope ${scopeValue}. Supported: ${target.supportedScopes.join(", ")}`)
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const resolvedScope = scopeValue ?? target.defaultScope ?? "workspace"
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
- [x] Add `isTargetScope` type guard
|
|
508
|
+
- [x] Add scope validation in both commands (single block, not two separate checks)
|
|
509
|
+
|
|
510
|
+
### Research Insights (Phase 4b)
|
|
511
|
+
|
|
512
|
+
**TypeScript review:** The original plan cast `scopeValue as TargetScope` before validation — a type lie. Use a proper type guard function to keep the type system honest.
|
|
513
|
+
|
|
514
|
+
**Simplicity review:** The two-step validation (check supported, then check exists) can be a single block with the type guard approach above.
|
|
515
|
+
|
|
516
|
+
#### 4c. Update output root resolution
|
|
517
|
+
|
|
518
|
+
Both commands now use the shared `resolveTargetOutputRoot` from Phase 0a.
|
|
519
|
+
|
|
520
|
+
- [x] Call shared function with `scope: resolvedScope` for primary target
|
|
521
|
+
- [x] Default scope: `target.defaultScope ?? "workspace"` (only used when target supports scopes)
|
|
522
|
+
|
|
523
|
+
#### 4d. Handle `--also` targets
|
|
524
|
+
|
|
525
|
+
`--scope` applies only to the primary `--to` target. Extra `--also` targets use their own `defaultScope`.
|
|
526
|
+
|
|
527
|
+
- [x] Pass `handler.defaultScope` for `--also` targets (each uses its own default)
|
|
528
|
+
- [x] Update the `--also` loop in both commands to use target-specific scope resolution
|
|
529
|
+
|
|
530
|
+
### Research Insights (Phase 4d)
|
|
531
|
+
|
|
532
|
+
**Architecture review:** There is no way for users to specify scope for an `--also` target (e.g., `--also windsurf:workspace`). Accept as a known v0.11.0 limitation. If users need workspace scope for windsurf, they can run two separate commands. Add a code comment indicating where per-target scope overrides would be added in the future.
|
|
533
|
+
|
|
534
|
+
### Phase 5: Tests
|
|
535
|
+
|
|
536
|
+
**Files:** `tests/windsurf-converter.test.ts`, `tests/windsurf-writer.test.ts`
|
|
537
|
+
|
|
538
|
+
#### 5a. Update converter tests
|
|
539
|
+
|
|
540
|
+
- [x] Remove all AGENTS.md tests (lines 275-303: empty plugin, CLAUDE.md missing)
|
|
541
|
+
- [x] Remove all `mcpSetupDoc` tests (lines 305-366: stdio, HTTP/SSE, redaction, null)
|
|
542
|
+
- [x] Update `fixturePlugin` default — remove `agentsMd` and `mcpSetupDoc` references
|
|
543
|
+
- [x] Add `mcpConfig` tests:
|
|
544
|
+
- stdio server produces correct JSON structure with `command`, `args`, `env`
|
|
545
|
+
- HTTP/SSE server produces correct JSON structure with `serverUrl`, `headers`
|
|
546
|
+
- mixed servers (stdio + HTTP) both included
|
|
547
|
+
- env vars included (not redacted) — verify actual values present
|
|
548
|
+
- `hasPotentialSecrets()` emits console.warn for sensitive keys
|
|
549
|
+
- `hasPotentialSecrets()` does NOT warn when no sensitive keys
|
|
550
|
+
- no servers produces null mcpConfig
|
|
551
|
+
- empty bundle has null mcpConfig
|
|
552
|
+
- server with no command and no URL is skipped with warning
|
|
553
|
+
|
|
554
|
+
#### 5b. Update writer tests
|
|
555
|
+
|
|
556
|
+
- [x] Remove AGENTS.md tests (backup test, creation test, double-nesting AGENTS.md parent test)
|
|
557
|
+
- [x] Remove double-nesting guard test (guard removed)
|
|
558
|
+
- [x] Remove `mcp-setup.md` write test
|
|
559
|
+
- [x] Update `emptyBundle` fixture — remove `agentsMd`, `mcpSetupDoc`, add `mcpConfig: null`
|
|
560
|
+
- [x] Add `mcp_config.json` tests:
|
|
561
|
+
- writes mcp_config.json to outputRoot
|
|
562
|
+
- merges with existing mcp_config.json (preserves user servers)
|
|
563
|
+
- backs up existing mcp_config.json before overwrite
|
|
564
|
+
- handles corrupted existing mcp_config.json (warn and replace)
|
|
565
|
+
- handles existing mcp_config.json with array (not object) at root
|
|
566
|
+
- handles existing mcp_config.json with `mcpServers: null`
|
|
567
|
+
- preserves non-mcpServers keys in existing file
|
|
568
|
+
- server name collision: plugin entry wins
|
|
569
|
+
- file permissions are 0o600 (not world-readable)
|
|
570
|
+
- [x] Update full bundle test — writer writes directly into outputRoot (no `.windsurf/` nesting)
|
|
571
|
+
|
|
572
|
+
#### 5c. Add scope resolution tests
|
|
573
|
+
|
|
574
|
+
Test the shared `resolveTargetOutputRoot` function:
|
|
575
|
+
|
|
576
|
+
- [x] Default scope for windsurf is "global" → resolves to `~/.codeium/windsurf/`
|
|
577
|
+
- [x] Explicit `--scope workspace` → resolves to `cwd/.windsurf/`
|
|
578
|
+
- [x] `--output` overrides scope resolution (both global and workspace)
|
|
579
|
+
- [x] Invalid scope value for windsurf → error
|
|
580
|
+
- [x] `--scope` on non-scope target (e.g., opencode) → error
|
|
581
|
+
- [x] `--also windsurf` uses windsurf's default scope ("global")
|
|
582
|
+
- [x] `isTargetScope` type guard correctly identifies valid/invalid values
|
|
583
|
+
|
|
584
|
+
### Phase 6: Documentation
|
|
585
|
+
|
|
586
|
+
**Files:** `README.md`, `CHANGELOG.md`
|
|
587
|
+
|
|
588
|
+
- [x] Update README.md Windsurf section to mention `--scope` flag and global default
|
|
589
|
+
- [x] Add CHANGELOG entry for v0.11.0 with breaking changes documented
|
|
590
|
+
- [x] Document migration path: `--scope workspace` for old behavior
|
|
591
|
+
- [x] Note that Windsurf `mcp_config.json` is global-only (workspace MCP config may not be discovered)
|
|
592
|
+
|
|
593
|
+
## Acceptance Criteria
|
|
594
|
+
|
|
595
|
+
- [x] `install compound-engineering --to windsurf` writes to `~/.codeium/windsurf/` by default
|
|
596
|
+
- [x] `install compound-engineering --to windsurf --scope workspace` writes to `cwd/.windsurf/`
|
|
597
|
+
- [x] `--output /custom/path` overrides scope for both commands
|
|
598
|
+
- [x] `--scope` on non-supporting target produces clear error
|
|
599
|
+
- [x] `mcp_config.json` merges with existing file (backup created, user entries preserved)
|
|
600
|
+
- [x] `mcp_config.json` written with `0o600` permissions (not world-readable)
|
|
601
|
+
- [x] No AGENTS.md generated for either scope
|
|
602
|
+
- [x] Env var secrets included in `mcp_config.json` with `console.warn` listing affected servers
|
|
603
|
+
- [x] Both stdio and HTTP/SSE MCP servers included in `mcp_config.json`
|
|
604
|
+
- [x] All existing tests updated, all new tests pass
|
|
605
|
+
- [x] No regressions in other targets
|
|
606
|
+
- [x] `resolveTargetOutputRoot` extracted to shared utility (no duplication)
|
|
607
|
+
|
|
608
|
+
## Dependencies & Risks
|
|
609
|
+
|
|
610
|
+
**Risk: Global workflow path is undocumented.** Windsurf may not discover workflows from `~/.codeium/windsurf/workflows/`. Mitigation: documented as a known assumption in the brainstorm. Users can `--scope workspace` if global workflows aren't discovered.
|
|
611
|
+
|
|
612
|
+
**Risk: Breaking changes for existing v0.10.0 users.** Mitigation: document migration path clearly. `--scope workspace` restores previous behavior. Target is experimental with a small user base.
|
|
613
|
+
|
|
614
|
+
**Risk: Workspace `mcp_config.json` not read by Windsurf.** Per Windsurf docs, `mcp_config.json` is global-only configuration. Workspace scope writes the file for forward-compatibility but emits a warning. The primary use case is global scope anyway.
|
|
615
|
+
|
|
616
|
+
**Risk: Secrets in `mcp_config.json` committed to git.** Mitigation: `0o600` file permissions, console.warn about sensitive env vars, warning about `.gitignore` for workspace scope.
|
|
617
|
+
|
|
618
|
+
## References & Research
|
|
619
|
+
|
|
620
|
+
- Spec: `docs/specs/windsurf.md` (authoritative reference for component mapping)
|
|
621
|
+
- Kiro MCP merge pattern: [src/targets/kiro.ts:68-92](../../src/targets/kiro.ts)
|
|
622
|
+
- Sync secrets warning: [src/commands/sync.ts:20-28](../../src/commands/sync.ts)
|
|
623
|
+
- Windsurf MCP docs: https://docs.windsurf.com/windsurf/cascade/mcp
|
|
624
|
+
- Windsurf Skills global path: https://docs.windsurf.com/windsurf/cascade/skills
|
|
625
|
+
- Windsurf MCP tutorial: https://windsurf.com/university/tutorials/configuring-first-mcp-server
|
|
626
|
+
- Adding converter targets (learning): [docs/solutions/adding-converter-target-providers.md](../solutions/adding-converter-target-providers.md)
|
|
627
|
+
- Plugin versioning (learning): [docs/solutions/plugin-versioning-requirements.md](../solutions/plugin-versioning-requirements.md)
|