@mindfoldhq/trellis 0.4.0-beta.7 → 0.4.0-beta.9
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/README.md +10 -5
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +34 -10
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +33 -2
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/codex.d.ts +7 -4
- package/dist/configurators/codex.d.ts.map +1 -1
- package/dist/configurators/codex.js +40 -10
- package/dist/configurators/codex.js.map +1 -1
- package/dist/configurators/copilot.d.ts +9 -0
- package/dist/configurators/copilot.d.ts.map +1 -0
- package/dist/configurators/copilot.js +34 -0
- package/dist/configurators/copilot.js.map +1 -0
- package/dist/configurators/index.d.ts +11 -1
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +59 -4
- package/dist/configurators/index.js.map +1 -1
- package/dist/configurators/windsurf.d.ts +8 -0
- package/dist/configurators/windsurf.d.ts.map +1 -0
- package/dist/configurators/windsurf.js +18 -0
- package/dist/configurators/windsurf.js.map +1 -0
- package/dist/migrations/manifests/0.4.0-beta.8.json +34 -0
- package/dist/migrations/manifests/0.4.0-beta.9.json +9 -0
- package/dist/templates/claude/commands/trellis/record-session.md +2 -1
- package/dist/templates/claude/hooks/inject-subagent-context.py +8 -1
- package/dist/templates/claude/hooks/ralph-loop.py +8 -1
- package/dist/templates/claude/hooks/session-start.py +31 -7
- package/dist/templates/claude/hooks/statusline.py +211 -0
- package/dist/templates/claude/settings.json +4 -0
- package/dist/templates/codex/agents/check.toml +23 -0
- package/dist/templates/codex/agents/implement.toml +19 -0
- package/dist/templates/codex/agents/research.toml +26 -0
- package/dist/templates/codex/codex-skills/parallel/SKILL.md +194 -0
- package/dist/templates/codex/config.toml +5 -0
- package/dist/templates/codex/hooks/session-start.py +228 -0
- package/dist/templates/codex/hooks.json +16 -0
- package/dist/templates/codex/index.d.ts +27 -5
- package/dist/templates/codex/index.d.ts.map +1 -1
- package/dist/templates/codex/index.js +60 -8
- package/dist/templates/codex/index.js.map +1 -1
- package/dist/templates/codex/skills/improve-ut/SKILL.md +69 -0
- package/dist/templates/codex/skills/record-session/SKILL.md +2 -1
- package/dist/templates/copilot/hooks/session-start.py +218 -0
- package/dist/templates/copilot/hooks.json +11 -0
- package/dist/templates/copilot/index.d.ts +23 -0
- package/dist/templates/copilot/index.d.ts.map +1 -0
- package/dist/templates/copilot/index.js +54 -0
- package/dist/templates/copilot/index.js.map +1 -0
- package/dist/templates/copilot/prompts/before-dev.prompt.md +33 -0
- package/dist/templates/copilot/prompts/brainstorm.prompt.md +491 -0
- package/dist/templates/copilot/prompts/break-loop.prompt.md +129 -0
- package/dist/templates/copilot/prompts/check-cross-layer.prompt.md +157 -0
- package/dist/templates/copilot/prompts/check.prompt.md +29 -0
- package/dist/templates/copilot/prompts/create-command.prompt.md +116 -0
- package/dist/templates/copilot/prompts/finish-work.prompt.md +157 -0
- package/dist/templates/copilot/prompts/integrate-skill.prompt.md +223 -0
- package/dist/templates/copilot/prompts/onboard.prompt.md +362 -0
- package/dist/templates/copilot/prompts/parallel.prompt.md +196 -0
- package/dist/templates/copilot/prompts/record-session.prompt.md +66 -0
- package/dist/templates/copilot/prompts/start.prompt.md +397 -0
- package/dist/templates/copilot/prompts/update-spec.prompt.md +358 -0
- package/dist/templates/cursor/commands/trellis-record-session.md +2 -1
- package/dist/templates/extract.d.ts +29 -0
- package/dist/templates/extract.d.ts.map +1 -1
- package/dist/templates/extract.js +51 -0
- package/dist/templates/extract.js.map +1 -1
- package/dist/templates/gemini/commands/trellis/record-session.toml +2 -1
- package/dist/templates/iflow/commands/trellis/record-session.md +2 -1
- package/dist/templates/iflow/hooks/inject-subagent-context.py +8 -1
- package/dist/templates/iflow/hooks/ralph-loop.py +8 -1
- package/dist/templates/iflow/hooks/session-start.py +31 -7
- package/dist/templates/kilo/workflows/record-session.md +2 -1
- package/dist/templates/kiro/skills/record-session/SKILL.md +2 -1
- package/dist/templates/markdown/agents.md +4 -0
- package/dist/templates/markdown/spec/backend/directory-structure.md +1 -1
- package/dist/templates/markdown/workspace-index.md +2 -0
- package/dist/templates/opencode/agents/dispatch.md +20 -19
- package/dist/templates/opencode/commands/trellis/record-session.md +2 -1
- package/dist/templates/opencode/lib/trellis-context.js +42 -2
- package/dist/templates/opencode/plugins/session-start.js +7 -27
- package/dist/templates/qoder/skills/record-session/SKILL.md +2 -1
- package/dist/templates/trellis/scripts/add_session.py +69 -16
- package/dist/templates/trellis/scripts/common/__init__.py +2 -0
- package/dist/templates/trellis/scripts/common/cli_adapter.py +108 -16
- package/dist/templates/trellis/scripts/common/developer.py +2 -2
- package/dist/templates/trellis/scripts/common/paths.py +57 -6
- package/dist/templates/trellis/scripts/common/task_store.py +6 -4
- package/dist/templates/trellis/scripts/common/task_utils.py +14 -8
- package/dist/templates/trellis/scripts/multi_agent/plan.py +7 -6
- package/dist/templates/trellis/scripts/multi_agent/start.py +16 -11
- package/dist/templates/trellis/scripts/task.py +1 -1
- package/dist/templates/windsurf/index.d.ts +21 -0
- package/dist/templates/windsurf/index.d.ts.map +1 -0
- package/dist/templates/windsurf/index.js +44 -0
- package/dist/templates/windsurf/index.js.map +1 -0
- package/dist/templates/windsurf/workflows/trellis-before-dev.md +31 -0
- package/dist/templates/windsurf/workflows/trellis-brainstorm.md +491 -0
- package/dist/templates/windsurf/workflows/trellis-break-loop.md +111 -0
- package/dist/templates/windsurf/workflows/trellis-check-cross-layer.md +157 -0
- package/dist/templates/windsurf/workflows/trellis-check.md +27 -0
- package/dist/templates/windsurf/workflows/trellis-create-command.md +154 -0
- package/dist/templates/windsurf/workflows/trellis-finish-work.md +147 -0
- package/dist/templates/windsurf/workflows/trellis-integrate-skill.md +220 -0
- package/dist/templates/windsurf/workflows/trellis-onboard.md +362 -0
- package/dist/templates/windsurf/workflows/trellis-record-session.md +66 -0
- package/dist/templates/windsurf/workflows/trellis-start.md +373 -0
- package/dist/templates/windsurf/workflows/trellis-update-spec.md +358 -0
- package/dist/types/ai-tools.d.ts +15 -3
- package/dist/types/ai-tools.d.ts.map +1 -1
- package/dist/types/ai-tools.js +34 -2
- package/dist/types/ai-tools.js.map +1 -1
- package/dist/utils/template-fetcher.d.ts +17 -4
- package/dist/utils/template-fetcher.d.ts.map +1 -1
- package/dist/utils/template-fetcher.js +94 -12
- package/dist/utils/template-fetcher.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
CLI Adapter for Multi-Platform Support.
|
|
3
3
|
|
|
4
|
-
Abstracts differences between Claude Code, OpenCode, Cursor, iFlow, Codex, Kilo, Kiro Code, Gemini CLI, Antigravity, Qoder, and
|
|
4
|
+
Abstracts differences between Claude Code, OpenCode, Cursor, iFlow, Codex, Kilo, Kiro Code, Gemini CLI, Antigravity, Windsurf, Qoder, CodeBuddy, and GitHub Copilot interfaces.
|
|
5
5
|
|
|
6
6
|
Supported platforms:
|
|
7
7
|
- claude: Claude Code (default)
|
|
@@ -13,8 +13,10 @@ Supported platforms:
|
|
|
13
13
|
- kiro: Kiro Code (skills-based)
|
|
14
14
|
- gemini: Gemini CLI
|
|
15
15
|
- antigravity: Antigravity (workflow-based)
|
|
16
|
+
- windsurf: Windsurf (workflow-based)
|
|
16
17
|
- qoder: Qoder
|
|
17
18
|
- codebuddy: CodeBuddy
|
|
19
|
+
- copilot: GitHub Copilot (VS Code)
|
|
18
20
|
|
|
19
21
|
Usage:
|
|
20
22
|
from common.cli_adapter import CLIAdapter
|
|
@@ -43,8 +45,10 @@ Platform = Literal[
|
|
|
43
45
|
"kiro",
|
|
44
46
|
"gemini",
|
|
45
47
|
"antigravity",
|
|
48
|
+
"windsurf",
|
|
46
49
|
"qoder",
|
|
47
50
|
"codebuddy",
|
|
51
|
+
"copilot",
|
|
48
52
|
]
|
|
49
53
|
|
|
50
54
|
|
|
@@ -89,7 +93,7 @@ class CLIAdapter:
|
|
|
89
93
|
"""Get platform-specific config directory name.
|
|
90
94
|
|
|
91
95
|
Returns:
|
|
92
|
-
Directory name ('.claude', '.opencode', '.cursor', '.iflow', '.
|
|
96
|
+
Directory name ('.claude', '.opencode', '.cursor', '.iflow', '.codex', '.kilocode', '.kiro', '.gemini', '.agent', '.windsurf', '.qoder', or '.codebuddy')
|
|
93
97
|
"""
|
|
94
98
|
if self.platform == "opencode":
|
|
95
99
|
return ".opencode"
|
|
@@ -98,7 +102,7 @@ class CLIAdapter:
|
|
|
98
102
|
elif self.platform == "iflow":
|
|
99
103
|
return ".iflow"
|
|
100
104
|
elif self.platform == "codex":
|
|
101
|
-
return ".
|
|
105
|
+
return ".codex"
|
|
102
106
|
elif self.platform == "kilo":
|
|
103
107
|
return ".kilocode"
|
|
104
108
|
elif self.platform == "kiro":
|
|
@@ -107,10 +111,14 @@ class CLIAdapter:
|
|
|
107
111
|
return ".gemini"
|
|
108
112
|
elif self.platform == "antigravity":
|
|
109
113
|
return ".agent"
|
|
114
|
+
elif self.platform == "windsurf":
|
|
115
|
+
return ".windsurf"
|
|
110
116
|
elif self.platform == "qoder":
|
|
111
117
|
return ".qoder"
|
|
112
118
|
elif self.platform == "codebuddy":
|
|
113
119
|
return ".codebuddy"
|
|
120
|
+
elif self.platform == "copilot":
|
|
121
|
+
return ".github/copilot"
|
|
114
122
|
else:
|
|
115
123
|
return ".claude"
|
|
116
124
|
|
|
@@ -121,7 +129,7 @@ class CLIAdapter:
|
|
|
121
129
|
project_root: Project root directory
|
|
122
130
|
|
|
123
131
|
Returns:
|
|
124
|
-
Path to config directory (.claude, .opencode, .cursor, .iflow, .
|
|
132
|
+
Path to config directory (.claude, .opencode, .cursor, .iflow, .codex, .kilocode, .kiro, .gemini, .agent, .windsurf, .qoder, or .codebuddy)
|
|
125
133
|
"""
|
|
126
134
|
return project_root / self.config_dir_name
|
|
127
135
|
|
|
@@ -133,9 +141,11 @@ class CLIAdapter:
|
|
|
133
141
|
project_root: Project root directory
|
|
134
142
|
|
|
135
143
|
Returns:
|
|
136
|
-
Path to agent .md
|
|
144
|
+
Path to agent definition file (.md for most platforms, .toml for Codex)
|
|
137
145
|
"""
|
|
138
146
|
mapped_name = self.get_agent_name(agent)
|
|
147
|
+
if self.platform == "codex":
|
|
148
|
+
return self.get_config_dir(project_root) / "agents" / f"{mapped_name}.toml"
|
|
139
149
|
return self.get_config_dir(project_root) / "agents" / f"{mapped_name}.md"
|
|
140
150
|
|
|
141
151
|
def get_commands_path(self, project_root: Path, *parts: str) -> Path:
|
|
@@ -151,8 +161,19 @@ class CLIAdapter:
|
|
|
151
161
|
Note:
|
|
152
162
|
Cursor uses prefix naming: .cursor/commands/trellis-<name>.md
|
|
153
163
|
Antigravity uses workflow directory: .agent/workflows/<name>.md
|
|
164
|
+
Windsurf uses workflow directory: .windsurf/workflows/trellis-<name>.md
|
|
165
|
+
Copilot uses prompt files: .github/prompts/<name>.prompt.md
|
|
154
166
|
Claude/OpenCode use subdirectory: .claude/commands/trellis/<name>.md
|
|
155
167
|
"""
|
|
168
|
+
if self.platform == "windsurf":
|
|
169
|
+
workflow_dir = self.get_config_dir(project_root) / "workflows"
|
|
170
|
+
if not parts:
|
|
171
|
+
return workflow_dir
|
|
172
|
+
if len(parts) >= 2 and parts[0] == "trellis":
|
|
173
|
+
filename = parts[-1]
|
|
174
|
+
return workflow_dir / f"trellis-{filename}"
|
|
175
|
+
return workflow_dir / Path(*parts)
|
|
176
|
+
|
|
156
177
|
if self.platform in ("antigravity", "kilo"):
|
|
157
178
|
workflow_dir = self.get_config_dir(project_root) / "workflows"
|
|
158
179
|
if not parts:
|
|
@@ -162,6 +183,17 @@ class CLIAdapter:
|
|
|
162
183
|
return workflow_dir / filename
|
|
163
184
|
return workflow_dir / Path(*parts)
|
|
164
185
|
|
|
186
|
+
if self.platform == "copilot":
|
|
187
|
+
prompts_dir = project_root / ".github" / "prompts"
|
|
188
|
+
if not parts:
|
|
189
|
+
return prompts_dir
|
|
190
|
+
if len(parts) >= 2 and parts[0] == "trellis":
|
|
191
|
+
filename = parts[-1]
|
|
192
|
+
if filename.endswith(".md"):
|
|
193
|
+
filename = filename[:-3]
|
|
194
|
+
return prompts_dir / f"{filename}.prompt.md"
|
|
195
|
+
return prompts_dir / Path(*parts)
|
|
196
|
+
|
|
165
197
|
if not parts:
|
|
166
198
|
return self.get_config_dir(project_root) / "commands"
|
|
167
199
|
|
|
@@ -190,6 +222,7 @@ class CLIAdapter:
|
|
|
190
222
|
Kiro: .kiro/skills/<name>/SKILL.md
|
|
191
223
|
Gemini: .gemini/commands/trellis/<name>.toml
|
|
192
224
|
Antigravity: .agent/workflows/<name>.md
|
|
225
|
+
Windsurf: .windsurf/workflows/trellis-<name>.md
|
|
193
226
|
Others: .{platform}/commands/trellis/<name>.md
|
|
194
227
|
"""
|
|
195
228
|
if self.platform == "cursor":
|
|
@@ -202,8 +235,12 @@ class CLIAdapter:
|
|
|
202
235
|
return f".gemini/commands/trellis/{name}.toml"
|
|
203
236
|
elif self.platform == "antigravity":
|
|
204
237
|
return f".agent/workflows/{name}.md"
|
|
238
|
+
elif self.platform == "windsurf":
|
|
239
|
+
return f".windsurf/workflows/trellis-{name}.md"
|
|
205
240
|
elif self.platform == "kilo":
|
|
206
241
|
return f".kilocode/workflows/{name}.md"
|
|
242
|
+
elif self.platform == "copilot":
|
|
243
|
+
return f".github/prompts/{name}.prompt.md"
|
|
207
244
|
else:
|
|
208
245
|
return f"{self.config_dir_name}/commands/trellis/{name}.md"
|
|
209
246
|
|
|
@@ -229,10 +266,14 @@ class CLIAdapter:
|
|
|
229
266
|
return {} # Gemini CLI doesn't have a non-interactive env var
|
|
230
267
|
elif self.platform == "antigravity":
|
|
231
268
|
return {}
|
|
269
|
+
elif self.platform == "windsurf":
|
|
270
|
+
return {}
|
|
232
271
|
elif self.platform == "qoder":
|
|
233
272
|
return {}
|
|
234
273
|
elif self.platform == "codebuddy":
|
|
235
274
|
return {}
|
|
275
|
+
elif self.platform == "copilot":
|
|
276
|
+
return {}
|
|
236
277
|
else:
|
|
237
278
|
return {"CLAUDE_NON_INTERACTIVE": "1"}
|
|
238
279
|
|
|
@@ -298,12 +339,20 @@ class CLIAdapter:
|
|
|
298
339
|
raise ValueError(
|
|
299
340
|
"Antigravity workflows are UI slash commands; CLI agent run is not supported."
|
|
300
341
|
)
|
|
342
|
+
elif self.platform == "windsurf":
|
|
343
|
+
raise ValueError(
|
|
344
|
+
"Windsurf workflows are UI slash commands; CLI agent run is not supported."
|
|
345
|
+
)
|
|
301
346
|
elif self.platform == "qoder":
|
|
302
347
|
cmd = ["qodercli", "-p", prompt]
|
|
303
348
|
elif self.platform == "codebuddy":
|
|
304
349
|
raise ValueError(
|
|
305
350
|
"CodeBuddy does not support non-interactive mode (no CLI agent)"
|
|
306
351
|
)
|
|
352
|
+
elif self.platform == "copilot":
|
|
353
|
+
raise ValueError(
|
|
354
|
+
"GitHub Copilot is IDE-only; CLI agent run is not supported."
|
|
355
|
+
)
|
|
307
356
|
|
|
308
357
|
else: # claude
|
|
309
358
|
cmd = ["claude", "-p"]
|
|
@@ -350,12 +399,20 @@ class CLIAdapter:
|
|
|
350
399
|
raise ValueError(
|
|
351
400
|
"Antigravity workflows are UI slash commands; CLI resume is not supported."
|
|
352
401
|
)
|
|
402
|
+
elif self.platform == "windsurf":
|
|
403
|
+
raise ValueError(
|
|
404
|
+
"Windsurf workflows are UI slash commands; CLI resume is not supported."
|
|
405
|
+
)
|
|
353
406
|
elif self.platform == "qoder":
|
|
354
407
|
return ["qodercli", "--resume", session_id]
|
|
355
408
|
elif self.platform == "codebuddy":
|
|
356
409
|
raise ValueError(
|
|
357
410
|
"CodeBuddy does not support non-interactive mode (no CLI agent)"
|
|
358
411
|
)
|
|
412
|
+
elif self.platform == "copilot":
|
|
413
|
+
raise ValueError(
|
|
414
|
+
"GitHub Copilot is IDE-only; CLI resume is not supported."
|
|
415
|
+
)
|
|
359
416
|
else:
|
|
360
417
|
return ["claude", "--resume", session_id]
|
|
361
418
|
|
|
@@ -418,10 +475,14 @@ class CLIAdapter:
|
|
|
418
475
|
return "gemini"
|
|
419
476
|
elif self.platform == "antigravity":
|
|
420
477
|
return "agy"
|
|
478
|
+
elif self.platform == "windsurf":
|
|
479
|
+
return "windsurf"
|
|
421
480
|
elif self.platform == "qoder":
|
|
422
481
|
return "qodercli"
|
|
423
482
|
elif self.platform == "codebuddy":
|
|
424
483
|
return "codebuddy"
|
|
484
|
+
elif self.platform == "copilot":
|
|
485
|
+
return "copilot"
|
|
425
486
|
else:
|
|
426
487
|
return "claude"
|
|
427
488
|
|
|
@@ -429,9 +490,18 @@ class CLIAdapter:
|
|
|
429
490
|
def supports_cli_agents(self) -> bool:
|
|
430
491
|
"""Check if platform supports running agents via CLI.
|
|
431
492
|
|
|
432
|
-
Claude Code, OpenCode, and
|
|
493
|
+
Claude Code, OpenCode, iFlow, and Codex support CLI agent execution.
|
|
433
494
|
Cursor is IDE-only and doesn't support CLI agents.
|
|
434
495
|
"""
|
|
496
|
+
return self.platform in ("claude", "opencode", "iflow", "codex")
|
|
497
|
+
|
|
498
|
+
@property
|
|
499
|
+
def requires_agent_definition_file(self) -> bool:
|
|
500
|
+
"""Check if platform requires an agent definition file (.md/.toml) to run.
|
|
501
|
+
|
|
502
|
+
Claude Code, OpenCode, iFlow: require agent .md files (--agent flag).
|
|
503
|
+
Codex: auto-discovers agents from .codex/agents/*.toml, no --agent flag.
|
|
504
|
+
"""
|
|
435
505
|
return self.platform in ("claude", "opencode", "iflow")
|
|
436
506
|
|
|
437
507
|
# =========================================================================
|
|
@@ -477,7 +547,7 @@ def get_cli_adapter(platform: str = "claude") -> CLIAdapter:
|
|
|
477
547
|
"""Get CLI adapter for the specified platform.
|
|
478
548
|
|
|
479
549
|
Args:
|
|
480
|
-
platform: Platform name ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'qoder', or 'codebuddy')
|
|
550
|
+
platform: Platform name ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', or 'codebuddy')
|
|
481
551
|
|
|
482
552
|
Returns:
|
|
483
553
|
CLIAdapter instance
|
|
@@ -495,11 +565,13 @@ def get_cli_adapter(platform: str = "claude") -> CLIAdapter:
|
|
|
495
565
|
"kiro",
|
|
496
566
|
"gemini",
|
|
497
567
|
"antigravity",
|
|
568
|
+
"windsurf",
|
|
498
569
|
"qoder",
|
|
499
570
|
"codebuddy",
|
|
571
|
+
"copilot",
|
|
500
572
|
):
|
|
501
573
|
raise ValueError(
|
|
502
|
-
f"Unsupported platform: {platform} (must be 'claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'qoder', or '
|
|
574
|
+
f"Unsupported platform: {platform} (must be 'claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', 'codebuddy', or 'copilot')"
|
|
503
575
|
)
|
|
504
576
|
|
|
505
577
|
return CLIAdapter(platform=platform) # type: ignore
|
|
@@ -511,11 +583,15 @@ _ALL_PLATFORM_CONFIG_DIRS = (
|
|
|
511
583
|
".iflow",
|
|
512
584
|
".opencode",
|
|
513
585
|
".agents",
|
|
586
|
+
".codex",
|
|
514
587
|
".kilocode",
|
|
515
588
|
".kiro",
|
|
516
589
|
".gemini",
|
|
517
590
|
".agent",
|
|
591
|
+
".windsurf",
|
|
592
|
+
".qoder",
|
|
518
593
|
".codebuddy",
|
|
594
|
+
".github/copilot",
|
|
519
595
|
)
|
|
520
596
|
"""All platform config directory names (used by detect_platform exclusion checks)."""
|
|
521
597
|
|
|
@@ -537,20 +613,21 @@ def detect_platform(project_root: Path) -> Platform:
|
|
|
537
613
|
2. .opencode directory exists → opencode
|
|
538
614
|
3. .iflow directory exists → iflow
|
|
539
615
|
4. .cursor directory exists (without .claude) → cursor
|
|
540
|
-
5. .
|
|
616
|
+
5. .codex exists and no other platform dirs → codex
|
|
541
617
|
6. .kilocode directory exists → kilo
|
|
542
618
|
7. .kiro/skills exists and no other platform dirs → kiro
|
|
543
619
|
8. .gemini directory exists → gemini
|
|
544
620
|
9. .agent/workflows exists and no other platform dirs → antigravity
|
|
545
|
-
10. .
|
|
546
|
-
11. .
|
|
547
|
-
12.
|
|
621
|
+
10. .windsurf/workflows exists and no other platform dirs → windsurf
|
|
622
|
+
11. .codebuddy directory exists → codebuddy
|
|
623
|
+
12. .qoder directory exists → qoder
|
|
624
|
+
13. Default → claude
|
|
548
625
|
|
|
549
626
|
Args:
|
|
550
627
|
project_root: Project root directory
|
|
551
628
|
|
|
552
629
|
Returns:
|
|
553
|
-
Detected platform ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'qoder', 'codebuddy', or default 'claude')
|
|
630
|
+
Detected platform ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', 'codebuddy', or default 'claude')
|
|
554
631
|
"""
|
|
555
632
|
import os
|
|
556
633
|
|
|
@@ -566,8 +643,10 @@ def detect_platform(project_root: Path) -> Platform:
|
|
|
566
643
|
"kiro",
|
|
567
644
|
"gemini",
|
|
568
645
|
"antigravity",
|
|
646
|
+
"windsurf",
|
|
569
647
|
"qoder",
|
|
570
648
|
"codebuddy",
|
|
649
|
+
"copilot",
|
|
571
650
|
):
|
|
572
651
|
return env_platform # type: ignore
|
|
573
652
|
|
|
@@ -588,9 +667,10 @@ def detect_platform(project_root: Path) -> Platform:
|
|
|
588
667
|
if (project_root / ".gemini").is_dir():
|
|
589
668
|
return "gemini"
|
|
590
669
|
|
|
591
|
-
# Check for
|
|
592
|
-
|
|
593
|
-
|
|
670
|
+
# Check for .codex directory (Codex-specific)
|
|
671
|
+
# .agents/skills/ alone does NOT trigger codex detection (it's a shared standard)
|
|
672
|
+
if (project_root / ".codex").is_dir() and not _has_other_platform_dir(
|
|
673
|
+
project_root, {".codex", ".agents"}
|
|
594
674
|
):
|
|
595
675
|
return "codex"
|
|
596
676
|
|
|
@@ -612,6 +692,14 @@ def detect_platform(project_root: Path) -> Platform:
|
|
|
612
692
|
):
|
|
613
693
|
return "antigravity"
|
|
614
694
|
|
|
695
|
+
# Check for Windsurf workflow directory only when no other platform config exists
|
|
696
|
+
if (
|
|
697
|
+
project_root / ".windsurf" / "workflows"
|
|
698
|
+
).is_dir() and not _has_other_platform_dir(
|
|
699
|
+
project_root, {".windsurf"}
|
|
700
|
+
):
|
|
701
|
+
return "windsurf"
|
|
702
|
+
|
|
615
703
|
# Check for .codebuddy directory (CodeBuddy-specific)
|
|
616
704
|
if (project_root / ".codebuddy").is_dir():
|
|
617
705
|
return "codebuddy"
|
|
@@ -620,6 +708,10 @@ def detect_platform(project_root: Path) -> Platform:
|
|
|
620
708
|
if (project_root / ".qoder").is_dir():
|
|
621
709
|
return "qoder"
|
|
622
710
|
|
|
711
|
+
# Check for .github/copilot directory (GitHub Copilot-specific)
|
|
712
|
+
if (project_root / ".github" / "copilot").is_dir():
|
|
713
|
+
return "copilot"
|
|
714
|
+
|
|
623
715
|
return "claude"
|
|
624
716
|
|
|
625
717
|
|
|
@@ -123,8 +123,8 @@ def init_developer(name: str, repo_root: Path | None = None) -> bool:
|
|
|
123
123
|
## Session History
|
|
124
124
|
|
|
125
125
|
<!-- @@@auto:session-history -->
|
|
126
|
-
| # | Date | Title | Commits |
|
|
127
|
-
|
|
126
|
+
| # | Date | Title | Commits | Branch |
|
|
127
|
+
|---|------|-------|---------|--------|
|
|
128
128
|
<!-- @@@/auto:session-history -->
|
|
129
129
|
|
|
130
130
|
---
|
|
@@ -221,6 +221,50 @@ def _get_current_task_file(repo_root: Path | None = None) -> Path:
|
|
|
221
221
|
return repo_root / DIR_WORKFLOW / FILE_CURRENT_TASK
|
|
222
222
|
|
|
223
223
|
|
|
224
|
+
def normalize_task_ref(task_ref: str) -> str:
|
|
225
|
+
"""Normalize a task ref for stable storage in .current-task.
|
|
226
|
+
|
|
227
|
+
Stored refs should prefer repo-relative POSIX paths like
|
|
228
|
+
`.trellis/tasks/03-27-my-task`, even on Windows. Absolute paths are preserved
|
|
229
|
+
unless they can later be converted back to repo-relative form by callers.
|
|
230
|
+
"""
|
|
231
|
+
normalized = task_ref.strip()
|
|
232
|
+
if not normalized:
|
|
233
|
+
return ""
|
|
234
|
+
|
|
235
|
+
path_obj = Path(normalized)
|
|
236
|
+
if path_obj.is_absolute():
|
|
237
|
+
return str(path_obj)
|
|
238
|
+
|
|
239
|
+
normalized = normalized.replace("\\", "/")
|
|
240
|
+
while normalized.startswith("./"):
|
|
241
|
+
normalized = normalized[2:]
|
|
242
|
+
|
|
243
|
+
if normalized.startswith(f"{DIR_TASKS}/"):
|
|
244
|
+
return f"{DIR_WORKFLOW}/{normalized}"
|
|
245
|
+
|
|
246
|
+
return normalized
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def resolve_task_ref(task_ref: str, repo_root: Path | None = None) -> Path | None:
|
|
250
|
+
"""Resolve a task ref from .current-task to an absolute task directory path."""
|
|
251
|
+
if repo_root is None:
|
|
252
|
+
repo_root = get_repo_root()
|
|
253
|
+
|
|
254
|
+
normalized = normalize_task_ref(task_ref)
|
|
255
|
+
if not normalized:
|
|
256
|
+
return None
|
|
257
|
+
|
|
258
|
+
path_obj = Path(normalized)
|
|
259
|
+
if path_obj.is_absolute():
|
|
260
|
+
return path_obj
|
|
261
|
+
|
|
262
|
+
if normalized.startswith(f"{DIR_WORKFLOW}/"):
|
|
263
|
+
return repo_root / path_obj
|
|
264
|
+
|
|
265
|
+
return repo_root / DIR_WORKFLOW / DIR_TASKS / path_obj
|
|
266
|
+
|
|
267
|
+
|
|
224
268
|
def get_current_task(repo_root: Path | None = None) -> str | None:
|
|
225
269
|
"""Get current task directory path (relative to repo_root).
|
|
226
270
|
|
|
@@ -236,7 +280,8 @@ def get_current_task(repo_root: Path | None = None) -> str | None:
|
|
|
236
280
|
return None
|
|
237
281
|
|
|
238
282
|
try:
|
|
239
|
-
|
|
283
|
+
content = current_file.read_text(encoding="utf-8").strip()
|
|
284
|
+
return normalize_task_ref(content) if content else None
|
|
240
285
|
except (OSError, IOError):
|
|
241
286
|
return None
|
|
242
287
|
|
|
@@ -255,7 +300,7 @@ def get_current_task_abs(repo_root: Path | None = None) -> Path | None:
|
|
|
255
300
|
|
|
256
301
|
relative = get_current_task(repo_root)
|
|
257
302
|
if relative:
|
|
258
|
-
return repo_root
|
|
303
|
+
return resolve_task_ref(relative, repo_root)
|
|
259
304
|
return None
|
|
260
305
|
|
|
261
306
|
|
|
@@ -272,18 +317,24 @@ def set_current_task(task_path: str, repo_root: Path | None = None) -> bool:
|
|
|
272
317
|
if repo_root is None:
|
|
273
318
|
repo_root = get_repo_root()
|
|
274
319
|
|
|
275
|
-
|
|
320
|
+
normalized = normalize_task_ref(task_path)
|
|
321
|
+
if not normalized:
|
|
276
322
|
return False
|
|
277
323
|
|
|
278
324
|
# Verify task directory exists
|
|
279
|
-
full_path = repo_root
|
|
280
|
-
if not full_path.is_dir():
|
|
325
|
+
full_path = resolve_task_ref(normalized, repo_root)
|
|
326
|
+
if full_path is None or not full_path.is_dir():
|
|
281
327
|
return False
|
|
282
328
|
|
|
329
|
+
try:
|
|
330
|
+
normalized = full_path.relative_to(repo_root).as_posix()
|
|
331
|
+
except ValueError:
|
|
332
|
+
normalized = str(full_path)
|
|
333
|
+
|
|
283
334
|
current_file = _get_current_task_file(repo_root)
|
|
284
335
|
|
|
285
336
|
try:
|
|
286
|
-
current_file.write_text(
|
|
337
|
+
current_file.write_text(normalized, encoding="utf-8")
|
|
287
338
|
return True
|
|
288
339
|
except (OSError, IOError):
|
|
289
340
|
return False
|
|
@@ -163,10 +163,12 @@ def cmd_create(args: argparse.Namespace) -> int:
|
|
|
163
163
|
"worktree_path": None,
|
|
164
164
|
"current_phase": 0,
|
|
165
165
|
"next_action": [
|
|
166
|
-
{"phase": 1, "action": "
|
|
167
|
-
{"phase": 2, "action": "
|
|
168
|
-
{"phase": 3, "action": "
|
|
169
|
-
{"phase": 4, "action": "
|
|
166
|
+
{"phase": 1, "action": "brainstorm"},
|
|
167
|
+
{"phase": 2, "action": "research"},
|
|
168
|
+
{"phase": 3, "action": "implement"},
|
|
169
|
+
{"phase": 4, "action": "check"},
|
|
170
|
+
{"phase": 5, "action": "update-spec"},
|
|
171
|
+
{"phase": 6, "action": "record-session"},
|
|
170
172
|
],
|
|
171
173
|
"commit": None,
|
|
172
174
|
"pr_url": None,
|
|
@@ -37,23 +37,25 @@ def is_safe_task_path(task_path: str, repo_root: Path | None = None) -> bool:
|
|
|
37
37
|
if repo_root is None:
|
|
38
38
|
repo_root = get_repo_root()
|
|
39
39
|
|
|
40
|
+
normalized = task_path.replace("\\", "/")
|
|
41
|
+
|
|
40
42
|
# Check empty or null
|
|
41
|
-
if not
|
|
43
|
+
if not normalized or normalized == "null":
|
|
42
44
|
print("Error: empty or null task path", file=sys.stderr)
|
|
43
45
|
return False
|
|
44
46
|
|
|
45
47
|
# Reject absolute paths
|
|
46
|
-
if task_path.
|
|
48
|
+
if Path(task_path).is_absolute():
|
|
47
49
|
print(f"Error: absolute path not allowed: {task_path}", file=sys.stderr)
|
|
48
50
|
return False
|
|
49
51
|
|
|
50
52
|
# Reject ".", "..", paths starting with "./" or "../", or containing ".."
|
|
51
|
-
if
|
|
53
|
+
if normalized in (".", "..") or normalized.startswith("./") or normalized.startswith("../") or ".." in normalized:
|
|
52
54
|
print(f"Error: path traversal not allowed: {task_path}", file=sys.stderr)
|
|
53
55
|
return False
|
|
54
56
|
|
|
55
57
|
# Final check: ensure resolved path is not the repo root
|
|
56
|
-
abs_path = repo_root /
|
|
58
|
+
abs_path = repo_root / Path(normalized)
|
|
57
59
|
if abs_path.exists():
|
|
58
60
|
try:
|
|
59
61
|
resolved = abs_path.resolve()
|
|
@@ -187,13 +189,17 @@ def resolve_task_dir(target_dir: str, repo_root: Path) -> Path:
|
|
|
187
189
|
if not target_dir:
|
|
188
190
|
return Path()
|
|
189
191
|
|
|
192
|
+
normalized = target_dir.replace("\\", "/")
|
|
193
|
+
while normalized.startswith("./"):
|
|
194
|
+
normalized = normalized[2:]
|
|
195
|
+
|
|
190
196
|
# Absolute path
|
|
191
|
-
if target_dir.
|
|
197
|
+
if Path(target_dir).is_absolute():
|
|
192
198
|
return Path(target_dir)
|
|
193
199
|
|
|
194
200
|
# Relative path (contains path separator or starts with .trellis)
|
|
195
|
-
if "/" in
|
|
196
|
-
return repo_root /
|
|
201
|
+
if "/" in normalized or normalized.startswith(".trellis"):
|
|
202
|
+
return repo_root / Path(normalized)
|
|
197
203
|
|
|
198
204
|
# Task name - try to find in tasks directory
|
|
199
205
|
tasks_dir = get_tasks_dir(repo_root)
|
|
@@ -202,7 +208,7 @@ def resolve_task_dir(target_dir: str, repo_root: Path) -> Path:
|
|
|
202
208
|
return found
|
|
203
209
|
|
|
204
210
|
# Fallback to treating as relative path
|
|
205
|
-
return repo_root /
|
|
211
|
+
return repo_root / Path(normalized)
|
|
206
212
|
|
|
207
213
|
|
|
208
214
|
# =============================================================================
|
|
@@ -53,7 +53,7 @@ def main() -> int:
|
|
|
53
53
|
parser.add_argument("--requirement", "-r", required=True, help="Requirement description")
|
|
54
54
|
parser.add_argument(
|
|
55
55
|
"--platform", "-p",
|
|
56
|
-
choices=["claude", "cursor", "iflow", "opencode", "qoder"],
|
|
56
|
+
choices=["claude", "cursor", "iflow", "opencode", "codex", "qoder"],
|
|
57
57
|
default=DEFAULT_PLATFORM,
|
|
58
58
|
help="Platform to use (default: claude)"
|
|
59
59
|
)
|
|
@@ -76,11 +76,12 @@ def main() -> int:
|
|
|
76
76
|
project_root = get_repo_root()
|
|
77
77
|
|
|
78
78
|
# Check plan agent exists (path varies by platform)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
if adapter.requires_agent_definition_file:
|
|
80
|
+
plan_md = adapter.get_agent_path("plan", project_root)
|
|
81
|
+
if not plan_md.is_file():
|
|
82
|
+
log_error(f"Agent definition not found at {plan_md}")
|
|
83
|
+
log_info(f"Platform: {platform}")
|
|
84
|
+
return 1
|
|
84
85
|
|
|
85
86
|
ensure_developer(project_root)
|
|
86
87
|
|
|
@@ -187,7 +187,7 @@ def main() -> int:
|
|
|
187
187
|
parser.add_argument("task_dir", help="Task directory path")
|
|
188
188
|
parser.add_argument(
|
|
189
189
|
"--platform", "-p",
|
|
190
|
-
choices=["claude", "cursor", "iflow", "opencode", "qoder"],
|
|
190
|
+
choices=["claude", "cursor", "iflow", "opencode", "codex", "qoder"],
|
|
191
191
|
default=DEFAULT_PLATFORM,
|
|
192
192
|
help="Platform to use (default: claude)"
|
|
193
193
|
)
|
|
@@ -202,12 +202,16 @@ def main() -> int:
|
|
|
202
202
|
project_root = get_repo_root()
|
|
203
203
|
|
|
204
204
|
# Normalize paths
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
task_dir_abs =
|
|
205
|
+
task_dir_path = Path(task_dir_arg)
|
|
206
|
+
if task_dir_path.is_absolute():
|
|
207
|
+
task_dir_abs = task_dir_path
|
|
208
208
|
else:
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
task_dir_abs = project_root / task_dir_path
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
task_dir_relative = task_dir_abs.relative_to(project_root).as_posix()
|
|
213
|
+
except ValueError:
|
|
214
|
+
task_dir_relative = str(task_dir_abs)
|
|
211
215
|
|
|
212
216
|
task_json_path = task_dir_abs / FILE_TASK_JSON
|
|
213
217
|
|
|
@@ -218,11 +222,12 @@ def main() -> int:
|
|
|
218
222
|
log_error(f"task.json not found at {task_json_path}")
|
|
219
223
|
return 1
|
|
220
224
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
225
|
+
if adapter.requires_agent_definition_file:
|
|
226
|
+
dispatch_md = adapter.get_agent_path("dispatch", project_root)
|
|
227
|
+
if not dispatch_md.is_file():
|
|
228
|
+
log_error(f"Agent definition not found at {dispatch_md}")
|
|
229
|
+
log_info(f"Platform: {platform}")
|
|
230
|
+
return 1
|
|
226
231
|
|
|
227
232
|
config_file = get_worktree_config(project_root)
|
|
228
233
|
if not config_file.is_file():
|
|
@@ -84,7 +84,7 @@ def cmd_start(args: argparse.Namespace) -> int:
|
|
|
84
84
|
|
|
85
85
|
# Convert to relative path for storage
|
|
86
86
|
try:
|
|
87
|
-
task_dir =
|
|
87
|
+
task_dir = full_path.relative_to(repo_root).as_posix()
|
|
88
88
|
except ValueError:
|
|
89
89
|
task_dir = str(full_path)
|
|
90
90
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windsurf workflow templates
|
|
3
|
+
*
|
|
4
|
+
* These are GENERIC templates for user projects.
|
|
5
|
+
* Do NOT use Trellis project's own .windsurf/ directory (which may be customized).
|
|
6
|
+
*
|
|
7
|
+
* Directory structure:
|
|
8
|
+
* windsurf/
|
|
9
|
+
* └── workflows/ # Workflow files
|
|
10
|
+
*/
|
|
11
|
+
export interface WorkflowTemplate {
|
|
12
|
+
name: string;
|
|
13
|
+
content: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Get all workflow templates.
|
|
17
|
+
* Workflow names match their filename stem
|
|
18
|
+
* (e.g. trellis-start.md -> /trellis-start).
|
|
19
|
+
*/
|
|
20
|
+
export declare function getAllWorkflows(): WorkflowTemplate[];
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/windsurf/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAqBH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,gBAAgB,EAAE,CAcpD"}
|