@mindfoldhq/trellis 0.4.0-beta.1 → 0.4.0-beta.10

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.
Files changed (200) hide show
  1. package/README.md +19 -5
  2. package/dist/cli/index.js +3 -0
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/commands/init.d.ts +3 -0
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +48 -23
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/update.d.ts.map +1 -1
  9. package/dist/commands/update.js +52 -41
  10. package/dist/commands/update.js.map +1 -1
  11. package/dist/configurators/codebuddy.d.ts +11 -0
  12. package/dist/configurators/codebuddy.d.ts.map +1 -0
  13. package/dist/configurators/codebuddy.js +58 -0
  14. package/dist/configurators/codebuddy.js.map +1 -0
  15. package/dist/configurators/codex.d.ts +7 -4
  16. package/dist/configurators/codex.d.ts.map +1 -1
  17. package/dist/configurators/codex.js +40 -10
  18. package/dist/configurators/codex.js.map +1 -1
  19. package/dist/configurators/copilot.d.ts +9 -0
  20. package/dist/configurators/copilot.d.ts.map +1 -0
  21. package/dist/configurators/copilot.js +34 -0
  22. package/dist/configurators/copilot.js.map +1 -0
  23. package/dist/configurators/index.d.ts +11 -1
  24. package/dist/configurators/index.d.ts.map +1 -1
  25. package/dist/configurators/index.js +72 -4
  26. package/dist/configurators/index.js.map +1 -1
  27. package/dist/configurators/opencode.d.ts +1 -1
  28. package/dist/configurators/opencode.js +1 -1
  29. package/dist/configurators/windsurf.d.ts +8 -0
  30. package/dist/configurators/windsurf.d.ts.map +1 -0
  31. package/dist/configurators/windsurf.js +18 -0
  32. package/dist/configurators/windsurf.js.map +1 -0
  33. package/dist/configurators/workflow.d.ts +1 -1
  34. package/dist/configurators/workflow.d.ts.map +1 -1
  35. package/dist/configurators/workflow.js +4 -2
  36. package/dist/configurators/workflow.js.map +1 -1
  37. package/dist/migrations/manifests/0.3.10.json +9 -0
  38. package/dist/migrations/manifests/0.4.0-beta.10.json +9 -0
  39. package/dist/migrations/manifests/0.4.0-beta.2.json +9 -0
  40. package/dist/migrations/manifests/0.4.0-beta.3.json +9 -0
  41. package/dist/migrations/manifests/0.4.0-beta.4.json +9 -0
  42. package/dist/migrations/manifests/0.4.0-beta.5.json +9 -0
  43. package/dist/migrations/manifests/0.4.0-beta.6.json +9 -0
  44. package/dist/migrations/manifests/0.4.0-beta.7.json +9 -0
  45. package/dist/migrations/manifests/0.4.0-beta.8.json +34 -0
  46. package/dist/migrations/manifests/0.4.0-beta.9.json +9 -0
  47. package/dist/templates/claude/commands/trellis/record-session.md +3 -2
  48. package/dist/templates/claude/hooks/inject-subagent-context.py +8 -1
  49. package/dist/templates/claude/hooks/ralph-loop.py +18 -10
  50. package/dist/templates/claude/hooks/session-start.py +33 -9
  51. package/dist/templates/claude/hooks/statusline.py +211 -0
  52. package/dist/templates/claude/settings.json +4 -0
  53. package/dist/templates/codebuddy/commands/trellis/before-dev.md +29 -0
  54. package/dist/templates/codebuddy/commands/trellis/brainstorm.md +487 -0
  55. package/dist/templates/codebuddy/commands/trellis/break-loop.md +107 -0
  56. package/dist/templates/codebuddy/commands/trellis/check-cross-layer.md +153 -0
  57. package/dist/templates/codebuddy/commands/trellis/check.md +25 -0
  58. package/dist/templates/codebuddy/commands/trellis/create-command.md +154 -0
  59. package/dist/templates/codebuddy/commands/trellis/finish-work.md +143 -0
  60. package/dist/templates/codebuddy/commands/trellis/integrate-skill.md +219 -0
  61. package/dist/templates/codebuddy/commands/trellis/onboard.md +358 -0
  62. package/dist/templates/codebuddy/commands/trellis/record-session.md +61 -0
  63. package/dist/templates/codebuddy/commands/trellis/start.md +373 -0
  64. package/dist/templates/codebuddy/commands/trellis/update-spec.md +354 -0
  65. package/dist/templates/codebuddy/index.d.ts +25 -0
  66. package/dist/templates/codebuddy/index.d.ts.map +1 -0
  67. package/dist/templates/codebuddy/index.js +45 -0
  68. package/dist/templates/codebuddy/index.js.map +1 -0
  69. package/dist/templates/codex/agents/check.toml +23 -0
  70. package/dist/templates/codex/agents/implement.toml +19 -0
  71. package/dist/templates/codex/agents/research.toml +26 -0
  72. package/dist/templates/codex/codex-skills/parallel/SKILL.md +194 -0
  73. package/dist/templates/codex/config.toml +5 -0
  74. package/dist/templates/codex/hooks/session-start.py +228 -0
  75. package/dist/templates/codex/hooks.json +16 -0
  76. package/dist/templates/codex/index.d.ts +27 -5
  77. package/dist/templates/codex/index.d.ts.map +1 -1
  78. package/dist/templates/codex/index.js +60 -8
  79. package/dist/templates/codex/index.js.map +1 -1
  80. package/dist/templates/codex/skills/before-dev/SKILL.md +1 -1
  81. package/dist/templates/codex/skills/brainstorm/SKILL.md +1 -1
  82. package/dist/templates/codex/skills/break-loop/SKILL.md +1 -1
  83. package/dist/templates/codex/skills/check/SKILL.md +1 -1
  84. package/dist/templates/codex/skills/check-cross-layer/SKILL.md +1 -1
  85. package/dist/templates/codex/skills/create-command/SKILL.md +1 -1
  86. package/dist/templates/codex/skills/finish-work/SKILL.md +1 -1
  87. package/dist/templates/codex/skills/improve-ut/SKILL.md +69 -0
  88. package/dist/templates/codex/skills/integrate-skill/SKILL.md +1 -1
  89. package/dist/templates/codex/skills/onboard/SKILL.md +1 -1
  90. package/dist/templates/codex/skills/record-session/SKILL.md +4 -3
  91. package/dist/templates/codex/skills/start/SKILL.md +1 -1
  92. package/dist/templates/codex/skills/update-spec/SKILL.md +1 -1
  93. package/dist/templates/copilot/hooks/session-start.py +218 -0
  94. package/dist/templates/copilot/hooks.json +11 -0
  95. package/dist/templates/copilot/index.d.ts +23 -0
  96. package/dist/templates/copilot/index.d.ts.map +1 -0
  97. package/dist/templates/copilot/index.js +54 -0
  98. package/dist/templates/copilot/index.js.map +1 -0
  99. package/dist/templates/copilot/prompts/before-dev.prompt.md +33 -0
  100. package/dist/templates/copilot/prompts/brainstorm.prompt.md +491 -0
  101. package/dist/templates/copilot/prompts/break-loop.prompt.md +129 -0
  102. package/dist/templates/copilot/prompts/check-cross-layer.prompt.md +157 -0
  103. package/dist/templates/copilot/prompts/check.prompt.md +29 -0
  104. package/dist/templates/copilot/prompts/create-command.prompt.md +116 -0
  105. package/dist/templates/copilot/prompts/finish-work.prompt.md +157 -0
  106. package/dist/templates/copilot/prompts/integrate-skill.prompt.md +223 -0
  107. package/dist/templates/copilot/prompts/onboard.prompt.md +362 -0
  108. package/dist/templates/copilot/prompts/parallel.prompt.md +196 -0
  109. package/dist/templates/copilot/prompts/record-session.prompt.md +66 -0
  110. package/dist/templates/copilot/prompts/start.prompt.md +397 -0
  111. package/dist/templates/copilot/prompts/update-spec.prompt.md +358 -0
  112. package/dist/templates/cursor/commands/trellis-record-session.md +3 -2
  113. package/dist/templates/extract.d.ts +36 -0
  114. package/dist/templates/extract.d.ts.map +1 -1
  115. package/dist/templates/extract.js +64 -0
  116. package/dist/templates/extract.js.map +1 -1
  117. package/dist/templates/gemini/commands/trellis/record-session.toml +3 -2
  118. package/dist/templates/iflow/commands/trellis/record-session.md +3 -2
  119. package/dist/templates/iflow/hooks/inject-subagent-context.py +8 -1
  120. package/dist/templates/iflow/hooks/ralph-loop.py +8 -1
  121. package/dist/templates/iflow/hooks/session-start.py +33 -9
  122. package/dist/templates/kilo/workflows/record-session.md +3 -2
  123. package/dist/templates/kiro/skills/before-dev/SKILL.md +1 -1
  124. package/dist/templates/kiro/skills/brainstorm/SKILL.md +1 -1
  125. package/dist/templates/kiro/skills/break-loop/SKILL.md +1 -1
  126. package/dist/templates/kiro/skills/check/SKILL.md +1 -1
  127. package/dist/templates/kiro/skills/check-cross-layer/SKILL.md +1 -1
  128. package/dist/templates/kiro/skills/create-command/SKILL.md +1 -1
  129. package/dist/templates/kiro/skills/finish-work/SKILL.md +1 -1
  130. package/dist/templates/kiro/skills/integrate-skill/SKILL.md +1 -1
  131. package/dist/templates/kiro/skills/onboard/SKILL.md +1 -1
  132. package/dist/templates/kiro/skills/record-session/SKILL.md +4 -3
  133. package/dist/templates/kiro/skills/start/SKILL.md +1 -1
  134. package/dist/templates/kiro/skills/update-spec/SKILL.md +1 -1
  135. package/dist/templates/markdown/agents.md +4 -0
  136. package/dist/templates/markdown/spec/backend/directory-structure.md +1 -1
  137. package/dist/templates/markdown/workspace-index.md +2 -0
  138. package/dist/templates/opencode/agents/dispatch.md +20 -19
  139. package/dist/templates/opencode/commands/trellis/record-session.md +3 -2
  140. package/dist/templates/opencode/lib/trellis-context.js +42 -2
  141. package/dist/templates/opencode/{plugin → plugins}/session-start.js +7 -27
  142. package/dist/templates/qoder/skills/before-dev/SKILL.md +1 -1
  143. package/dist/templates/qoder/skills/brainstorm/SKILL.md +1 -1
  144. package/dist/templates/qoder/skills/break-loop/SKILL.md +1 -1
  145. package/dist/templates/qoder/skills/check/SKILL.md +1 -1
  146. package/dist/templates/qoder/skills/check-cross-layer/SKILL.md +1 -1
  147. package/dist/templates/qoder/skills/create-command/SKILL.md +1 -1
  148. package/dist/templates/qoder/skills/finish-work/SKILL.md +1 -1
  149. package/dist/templates/qoder/skills/integrate-skill/SKILL.md +1 -1
  150. package/dist/templates/qoder/skills/onboard/SKILL.md +1 -1
  151. package/dist/templates/qoder/skills/record-session/SKILL.md +4 -3
  152. package/dist/templates/qoder/skills/start/SKILL.md +1 -1
  153. package/dist/templates/qoder/skills/update-spec/SKILL.md +1 -1
  154. package/dist/templates/trellis/scripts/add_session.py +69 -16
  155. package/dist/templates/trellis/scripts/common/__init__.py +2 -0
  156. package/dist/templates/trellis/scripts/common/cli_adapter.py +133 -21
  157. package/dist/templates/trellis/scripts/common/config.py +40 -0
  158. package/dist/templates/trellis/scripts/common/developer.py +2 -2
  159. package/dist/templates/trellis/scripts/common/packages_context.py +9 -4
  160. package/dist/templates/trellis/scripts/common/paths.py +57 -6
  161. package/dist/templates/trellis/scripts/common/session_context.py +98 -2
  162. package/dist/templates/trellis/scripts/common/task_context.py +27 -1
  163. package/dist/templates/trellis/scripts/common/task_store.py +6 -4
  164. package/dist/templates/trellis/scripts/common/task_utils.py +14 -8
  165. package/dist/templates/trellis/scripts/create_bootstrap.py +1 -1
  166. package/dist/templates/trellis/scripts/multi_agent/plan.py +7 -6
  167. package/dist/templates/trellis/scripts/multi_agent/start.py +16 -11
  168. package/dist/templates/trellis/scripts/task.py +1 -1
  169. package/dist/templates/trellis/scripts-shell-archive/create-bootstrap.sh +1 -1
  170. package/dist/templates/trellis/workflow.md +17 -4
  171. package/dist/templates/windsurf/index.d.ts +21 -0
  172. package/dist/templates/windsurf/index.d.ts.map +1 -0
  173. package/dist/templates/windsurf/index.js +44 -0
  174. package/dist/templates/windsurf/index.js.map +1 -0
  175. package/dist/templates/windsurf/workflows/trellis-before-dev.md +31 -0
  176. package/dist/templates/windsurf/workflows/trellis-brainstorm.md +491 -0
  177. package/dist/templates/windsurf/workflows/trellis-break-loop.md +111 -0
  178. package/dist/templates/windsurf/workflows/trellis-check-cross-layer.md +157 -0
  179. package/dist/templates/windsurf/workflows/trellis-check.md +27 -0
  180. package/dist/templates/windsurf/workflows/trellis-create-command.md +154 -0
  181. package/dist/templates/windsurf/workflows/trellis-finish-work.md +147 -0
  182. package/dist/templates/windsurf/workflows/trellis-integrate-skill.md +220 -0
  183. package/dist/templates/windsurf/workflows/trellis-onboard.md +362 -0
  184. package/dist/templates/windsurf/workflows/trellis-record-session.md +66 -0
  185. package/dist/templates/windsurf/workflows/trellis-start.md +373 -0
  186. package/dist/templates/windsurf/workflows/trellis-update-spec.md +358 -0
  187. package/dist/types/ai-tools.d.ts +15 -3
  188. package/dist/types/ai-tools.d.ts.map +1 -1
  189. package/dist/types/ai-tools.js +42 -2
  190. package/dist/types/ai-tools.js.map +1 -1
  191. package/dist/utils/project-detector.d.ts +5 -0
  192. package/dist/utils/project-detector.d.ts.map +1 -1
  193. package/dist/utils/project-detector.js +7 -0
  194. package/dist/utils/project-detector.js.map +1 -1
  195. package/dist/utils/template-fetcher.d.ts +24 -3
  196. package/dist/utils/template-fetcher.d.ts.map +1 -1
  197. package/dist/utils/template-fetcher.js +129 -16
  198. package/dist/utils/template-fetcher.js.map +1 -1
  199. package/package.json +1 -1
  200. /package/dist/templates/opencode/{plugin → plugins}/inject-subagent-context.js +0 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: start
3
- description: "Start Session"
3
+ description: "Initializes an AI development session by reading workflow guides, developer identity, git status, active tasks, and project guidelines from .trellis/. Classifies incoming tasks and routes to brainstorm, direct edit, or task workflow. Use when beginning a new coding session, resuming work, starting a new task, or re-establishing project context."
4
4
  ---
5
5
 
6
6
  # Start Session
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: update-spec
3
- description: "Update Code-Spec - Capture Executable Contracts"
3
+ description: "Captures executable contracts and coding knowledge into .trellis/spec/ documents after implementation, debugging, or design decisions. Enforces code-spec depth for infra and cross-layer changes with mandatory sections for signatures, contracts, validation matrices, and test points. Use when a feature is implemented, a bug is fixed, a design decision is made, a new pattern is discovered, or cross-layer contracts change."
4
4
  ---
5
5
 
6
6
  # Update Spec - Capture Knowledge into Specifications
@@ -5,17 +5,23 @@ Add a new session to journal file and update index.md.
5
5
 
6
6
  Usage:
7
7
  python3 add_session.py --title "Title" --commit "hash" --summary "Summary" [--package cli]
8
+ python3 add_session.py --title "Title" --branch "feat/my-branch"
8
9
 
9
10
  # Pipe detailed content via stdin (use --stdin to opt in):
10
11
  cat << 'EOF' | python3 add_session.py --stdin --title "Title" --summary "Summary"
11
12
  <session content here>
12
13
  EOF
14
+
15
+ Branch resolution order:
16
+ 1. --branch CLI arg (explicit)
17
+ 2. task.json branch field (from active task)
18
+ 3. git branch --show-current (auto-detect)
19
+ 4. None (omitted gracefully)
13
20
  """
14
21
 
15
22
  from __future__ import annotations
16
23
 
17
24
  import argparse
18
- import json
19
25
  import re
20
26
  import subprocess
21
27
  import sys
@@ -30,6 +36,7 @@ from common.paths import (
30
36
  get_workspace_dir,
31
37
  )
32
38
  from common.developer import ensure_developer
39
+ from common.git import run_git
33
40
  from common.tasks import load_task
34
41
  from common.config import (
35
42
  get_packages,
@@ -139,6 +146,7 @@ def generate_session_content(
139
146
  extra_content: str,
140
147
  today: str,
141
148
  package: str | None = None,
149
+ branch: str | None = None,
142
150
  ) -> str:
143
151
  """Generate session content."""
144
152
  if commit and commit != "-":
@@ -151,13 +159,14 @@ def generate_session_content(
151
159
  commit_table = "(No commits - planning session)"
152
160
 
153
161
  package_line = f"\n**Package**: {package}" if package else ""
162
+ branch_line = f"\n**Branch**: `{branch}`" if branch else ""
154
163
 
155
164
  return f"""
156
165
 
157
166
  ## Session {session_num}: {title}
158
167
 
159
168
  **Date**: {today}
160
- **Task**: {title}{package_line}
169
+ **Task**: {title}{package_line}{branch_line}
161
170
 
162
171
  ### Summary
163
172
 
@@ -192,7 +201,8 @@ def update_index(
192
201
  commit: str,
193
202
  new_session: int,
194
203
  active_file: str,
195
- today: str
204
+ today: str,
205
+ branch: str | None = None,
196
206
  ) -> bool:
197
207
  """Update index.md with new session info."""
198
208
  # Format commit for display
@@ -271,10 +281,25 @@ def update_index(
271
281
  continue
272
282
 
273
283
  if in_session_history:
274
- new_lines.append(line)
275
- if re.match(r"^\|\s*-", line) and not header_written:
276
- new_lines.append(f"| {new_session} | {today} | {title} | {commit_display} |")
284
+ # Migrate old 4/6-column headers to 5-column Branch-only history.
285
+ if re.match(
286
+ r"^\|\s*#\s*\|\s*Date\s*\|\s*Title\s*\|\s*Commits\s*\|\s*Branch\s*\|\s*Base Branch\s*\|\s*$",
287
+ line,
288
+ ):
289
+ new_lines.append("| # | Date | Title | Commits | Branch |")
290
+ continue
291
+ if re.match(r"^\|\s*#\s*\|\s*Date\s*\|\s*Title\s*\|\s*Commits\s*\|\s*Branch\s*\|\s*$", line):
292
+ new_lines.append("| # | Date | Title | Commits | Branch |")
293
+ continue
294
+ if re.match(r"^\|\s*#\s*\|\s*Date\s*\|\s*Title\s*\|\s*Commits\s*\|\s*$", line):
295
+ new_lines.append("| # | Date | Title | Commits | Branch |")
296
+ continue
297
+ if re.match(r"^\|[-| ]+\|\s*$", line) and not header_written:
298
+ new_lines.append("|---|------|-------|---------|--------|")
299
+ new_lines.append(f"| {new_session} | {today} | {title} | {commit_display} | `{branch or '-'}` |")
277
300
  header_written = True
301
+ continue
302
+ new_lines.append(line)
278
303
  continue
279
304
 
280
305
  new_lines.append(line)
@@ -291,11 +316,16 @@ def update_index(
291
316
  def _auto_commit_workspace(repo_root: Path) -> None:
292
317
  """Stage .trellis/workspace and .trellis/tasks, then commit with a configured message."""
293
318
  commit_msg = get_session_commit_message(repo_root)
294
- subprocess.run(
319
+ add_result = subprocess.run(
295
320
  ["git", "add", "-A", ".trellis/workspace", ".trellis/tasks"],
296
321
  cwd=repo_root,
297
322
  capture_output=True,
323
+ text=True,
298
324
  )
325
+ if add_result.returncode != 0:
326
+ print(f"[WARN] git add failed (exit {add_result.returncode}): {add_result.stderr.strip()}", file=sys.stderr)
327
+ print("[WARN] Please commit .trellis/ changes manually: git add .trellis && git commit", file=sys.stderr)
328
+ return
299
329
  # Check if there are staged changes
300
330
  result = subprocess.run(
301
331
  ["git", "diff", "--cached", "--quiet", "--", ".trellis/workspace", ".trellis/tasks"],
@@ -323,6 +353,7 @@ def add_session(
323
353
  extra_content: str = "(Add details)",
324
354
  auto_commit: bool = True,
325
355
  package: str | None = None,
356
+ branch: str | None = None,
326
357
  ) -> int:
327
358
  """Add a new session."""
328
359
  repo_root = get_repo_root()
@@ -348,7 +379,8 @@ def add_session(
348
379
  new_session = current_session + 1
349
380
 
350
381
  session_content = generate_session_content(
351
- new_session, title, commit, summary, extra_content, today, package
382
+ new_session, title, commit, summary, extra_content, today, package,
383
+ branch,
352
384
  )
353
385
  content_lines = len(session_content.splitlines())
354
386
 
@@ -385,7 +417,16 @@ def add_session(
385
417
 
386
418
  # Update index.md
387
419
  active_file = f"{FILE_JOURNAL_PREFIX}{target_num}.md"
388
- if not update_index(index_file, dev_dir, title, commit, new_session, active_file, today):
420
+ if not update_index(
421
+ index_file,
422
+ dev_dir,
423
+ title,
424
+ commit,
425
+ new_session,
426
+ active_file,
427
+ today,
428
+ branch,
429
+ ):
389
430
  return 1
390
431
 
391
432
  print("", file=sys.stderr)
@@ -419,6 +460,7 @@ def main() -> int:
419
460
  parser.add_argument("--summary", default="(Add summary)", help="Brief summary")
420
461
  parser.add_argument("--content-file", help="Path to file with detailed content")
421
462
  parser.add_argument("--package", help="Package name tag (e.g., cli, docs-site)")
463
+ parser.add_argument("--branch", help="Branch name (auto-detected if omitted)")
422
464
  parser.add_argument("--no-commit", action="store_true",
423
465
  help="Skip auto-commit of workspace changes")
424
466
  parser.add_argument("--stdin", action="store_true",
@@ -434,8 +476,11 @@ def main() -> int:
434
476
  elif args.stdin:
435
477
  extra_content = sys.stdin.read()
436
478
 
437
- # Resolve package: CLI active task default_package None
479
+ # Load active task once shared by package and branch resolution
438
480
  repo_root = get_repo_root()
481
+ current = get_current_task(repo_root)
482
+ task_data = load_task(repo_root / current) if current else None
483
+
439
484
  package = args.package
440
485
  if package:
441
486
  # CLI source: fail-fast in monorepo, ignore in single-repo
@@ -449,18 +494,26 @@ def main() -> int:
449
494
  return 1
450
495
  else:
451
496
  # Inferred: active task's task.json.package → default_package → None
452
- task_package = None
453
- current = get_current_task(repo_root)
454
- if current:
455
- ct = load_task(repo_root / current)
456
- if ct and ct.package:
457
- task_package = ct.package
497
+ task_package = task_data.package if task_data else None
458
498
  package = resolve_package(task_package, repo_root)
459
499
 
500
+ # Resolve branch: CLI → task.json → git auto-detect → None
501
+ branch = args.branch
502
+
503
+ if not branch:
504
+ if task_data and task_data.raw.get("branch"):
505
+ branch = task_data.raw["branch"]
506
+ else:
507
+ _, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_root)
508
+ detected = branch_out.strip()
509
+ if detected:
510
+ branch = detected
511
+
460
512
  return add_session(
461
513
  args.title, args.commit, args.summary, extra_content,
462
514
  auto_commit=not args.no_commit,
463
515
  package=package,
516
+ branch=branch,
464
517
  )
465
518
 
466
519
 
@@ -75,6 +75,8 @@ from .paths import (
75
75
  count_lines,
76
76
  get_current_task,
77
77
  get_current_task_abs,
78
+ normalize_task_ref,
79
+ resolve_task_ref,
78
80
  set_current_task,
79
81
  clear_current_task,
80
82
  has_current_task,
@@ -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, and Qoder interfaces.
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,7 +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
18
+ - codebuddy: CodeBuddy
19
+ - copilot: GitHub Copilot (VS Code)
17
20
 
18
21
  Usage:
19
22
  from common.cli_adapter import CLIAdapter
@@ -42,7 +45,10 @@ Platform = Literal[
42
45
  "kiro",
43
46
  "gemini",
44
47
  "antigravity",
48
+ "windsurf",
45
49
  "qoder",
50
+ "codebuddy",
51
+ "copilot",
46
52
  ]
47
53
 
48
54
 
@@ -87,7 +93,7 @@ class CLIAdapter:
87
93
  """Get platform-specific config directory name.
88
94
 
89
95
  Returns:
90
- Directory name ('.claude', '.opencode', '.cursor', '.iflow', '.agents', '.kilocode', '.kiro', '.gemini', '.agent', or '.qoder')
96
+ Directory name ('.claude', '.opencode', '.cursor', '.iflow', '.codex', '.kilocode', '.kiro', '.gemini', '.agent', '.windsurf', '.qoder', or '.codebuddy')
91
97
  """
92
98
  if self.platform == "opencode":
93
99
  return ".opencode"
@@ -96,7 +102,7 @@ class CLIAdapter:
96
102
  elif self.platform == "iflow":
97
103
  return ".iflow"
98
104
  elif self.platform == "codex":
99
- return ".agents"
105
+ return ".codex"
100
106
  elif self.platform == "kilo":
101
107
  return ".kilocode"
102
108
  elif self.platform == "kiro":
@@ -105,8 +111,14 @@ class CLIAdapter:
105
111
  return ".gemini"
106
112
  elif self.platform == "antigravity":
107
113
  return ".agent"
114
+ elif self.platform == "windsurf":
115
+ return ".windsurf"
108
116
  elif self.platform == "qoder":
109
117
  return ".qoder"
118
+ elif self.platform == "codebuddy":
119
+ return ".codebuddy"
120
+ elif self.platform == "copilot":
121
+ return ".github/copilot"
110
122
  else:
111
123
  return ".claude"
112
124
 
@@ -117,7 +129,7 @@ class CLIAdapter:
117
129
  project_root: Project root directory
118
130
 
119
131
  Returns:
120
- Path to config directory (.claude, .opencode, .cursor, .iflow, .agents, .kilocode, .kiro, .gemini, .agent, or .qoder)
132
+ Path to config directory (.claude, .opencode, .cursor, .iflow, .codex, .kilocode, .kiro, .gemini, .agent, .windsurf, .qoder, or .codebuddy)
121
133
  """
122
134
  return project_root / self.config_dir_name
123
135
 
@@ -129,9 +141,11 @@ class CLIAdapter:
129
141
  project_root: Project root directory
130
142
 
131
143
  Returns:
132
- Path to agent .md file
144
+ Path to agent definition file (.md for most platforms, .toml for Codex)
133
145
  """
134
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"
135
149
  return self.get_config_dir(project_root) / "agents" / f"{mapped_name}.md"
136
150
 
137
151
  def get_commands_path(self, project_root: Path, *parts: str) -> Path:
@@ -147,8 +161,19 @@ class CLIAdapter:
147
161
  Note:
148
162
  Cursor uses prefix naming: .cursor/commands/trellis-<name>.md
149
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
150
166
  Claude/OpenCode use subdirectory: .claude/commands/trellis/<name>.md
151
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
+
152
177
  if self.platform in ("antigravity", "kilo"):
153
178
  workflow_dir = self.get_config_dir(project_root) / "workflows"
154
179
  if not parts:
@@ -158,6 +183,17 @@ class CLIAdapter:
158
183
  return workflow_dir / filename
159
184
  return workflow_dir / Path(*parts)
160
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
+
161
197
  if not parts:
162
198
  return self.get_config_dir(project_root) / "commands"
163
199
 
@@ -186,6 +222,7 @@ class CLIAdapter:
186
222
  Kiro: .kiro/skills/<name>/SKILL.md
187
223
  Gemini: .gemini/commands/trellis/<name>.toml
188
224
  Antigravity: .agent/workflows/<name>.md
225
+ Windsurf: .windsurf/workflows/trellis-<name>.md
189
226
  Others: .{platform}/commands/trellis/<name>.md
190
227
  """
191
228
  if self.platform == "cursor":
@@ -198,8 +235,12 @@ class CLIAdapter:
198
235
  return f".gemini/commands/trellis/{name}.toml"
199
236
  elif self.platform == "antigravity":
200
237
  return f".agent/workflows/{name}.md"
238
+ elif self.platform == "windsurf":
239
+ return f".windsurf/workflows/trellis-{name}.md"
201
240
  elif self.platform == "kilo":
202
241
  return f".kilocode/workflows/{name}.md"
242
+ elif self.platform == "copilot":
243
+ return f".github/prompts/{name}.prompt.md"
203
244
  else:
204
245
  return f"{self.config_dir_name}/commands/trellis/{name}.md"
205
246
 
@@ -225,8 +266,14 @@ class CLIAdapter:
225
266
  return {} # Gemini CLI doesn't have a non-interactive env var
226
267
  elif self.platform == "antigravity":
227
268
  return {}
269
+ elif self.platform == "windsurf":
270
+ return {}
228
271
  elif self.platform == "qoder":
229
272
  return {}
273
+ elif self.platform == "codebuddy":
274
+ return {}
275
+ elif self.platform == "copilot":
276
+ return {}
230
277
  else:
231
278
  return {"CLAUDE_NON_INTERACTIVE": "1"}
232
279
 
@@ -278,12 +325,8 @@ class CLIAdapter:
278
325
  cmd.append(prompt)
279
326
 
280
327
  elif self.platform == "iflow":
281
- cmd = ["iflow", "-p"]
282
- cmd.extend(["-y", "--agent", mapped_agent])
283
- # iFlow doesn't support --session-id on creation
284
- if verbose:
285
- cmd.append("--verbose")
286
- cmd.append(prompt)
328
+ cmd = ["iflow", "-y", "-p"]
329
+ cmd.append(f"${mapped_agent} {prompt}")
287
330
  elif self.platform == "codex":
288
331
  cmd = ["codex", "exec"]
289
332
  cmd.append(prompt)
@@ -296,8 +339,20 @@ class CLIAdapter:
296
339
  raise ValueError(
297
340
  "Antigravity workflows are UI slash commands; CLI agent run is not supported."
298
341
  )
342
+ elif self.platform == "windsurf":
343
+ raise ValueError(
344
+ "Windsurf workflows are UI slash commands; CLI agent run is not supported."
345
+ )
299
346
  elif self.platform == "qoder":
300
347
  cmd = ["qodercli", "-p", prompt]
348
+ elif self.platform == "codebuddy":
349
+ raise ValueError(
350
+ "CodeBuddy does not support non-interactive mode (no CLI agent)"
351
+ )
352
+ elif self.platform == "copilot":
353
+ raise ValueError(
354
+ "GitHub Copilot is IDE-only; CLI agent run is not supported."
355
+ )
301
356
 
302
357
  else: # claude
303
358
  cmd = ["claude", "-p"]
@@ -344,8 +399,20 @@ class CLIAdapter:
344
399
  raise ValueError(
345
400
  "Antigravity workflows are UI slash commands; CLI resume is not supported."
346
401
  )
402
+ elif self.platform == "windsurf":
403
+ raise ValueError(
404
+ "Windsurf workflows are UI slash commands; CLI resume is not supported."
405
+ )
347
406
  elif self.platform == "qoder":
348
407
  return ["qodercli", "--resume", session_id]
408
+ elif self.platform == "codebuddy":
409
+ raise ValueError(
410
+ "CodeBuddy does not support non-interactive mode (no CLI agent)"
411
+ )
412
+ elif self.platform == "copilot":
413
+ raise ValueError(
414
+ "GitHub Copilot is IDE-only; CLI resume is not supported."
415
+ )
349
416
  else:
350
417
  return ["claude", "--resume", session_id]
351
418
 
@@ -408,8 +475,14 @@ class CLIAdapter:
408
475
  return "gemini"
409
476
  elif self.platform == "antigravity":
410
477
  return "agy"
478
+ elif self.platform == "windsurf":
479
+ return "windsurf"
411
480
  elif self.platform == "qoder":
412
481
  return "qodercli"
482
+ elif self.platform == "codebuddy":
483
+ return "codebuddy"
484
+ elif self.platform == "copilot":
485
+ return "copilot"
413
486
  else:
414
487
  return "claude"
415
488
 
@@ -417,9 +490,18 @@ class CLIAdapter:
417
490
  def supports_cli_agents(self) -> bool:
418
491
  """Check if platform supports running agents via CLI.
419
492
 
420
- Claude Code, OpenCode, and iFlow support CLI agent execution.
493
+ Claude Code, OpenCode, iFlow, and Codex support CLI agent execution.
421
494
  Cursor is IDE-only and doesn't support CLI agents.
422
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
+ """
423
505
  return self.platform in ("claude", "opencode", "iflow")
424
506
 
425
507
  # =========================================================================
@@ -465,7 +547,7 @@ def get_cli_adapter(platform: str = "claude") -> CLIAdapter:
465
547
  """Get CLI adapter for the specified platform.
466
548
 
467
549
  Args:
468
- platform: Platform name ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', or 'qoder')
550
+ platform: Platform name ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', or 'codebuddy')
469
551
 
470
552
  Returns:
471
553
  CLIAdapter instance
@@ -483,10 +565,13 @@ def get_cli_adapter(platform: str = "claude") -> CLIAdapter:
483
565
  "kiro",
484
566
  "gemini",
485
567
  "antigravity",
568
+ "windsurf",
486
569
  "qoder",
570
+ "codebuddy",
571
+ "copilot",
487
572
  ):
488
573
  raise ValueError(
489
- f"Unsupported platform: {platform} (must be 'claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', or 'qoder')"
574
+ f"Unsupported platform: {platform} (must be 'claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', 'codebuddy', or 'copilot')"
490
575
  )
491
576
 
492
577
  return CLIAdapter(platform=platform) # type: ignore
@@ -498,10 +583,15 @@ _ALL_PLATFORM_CONFIG_DIRS = (
498
583
  ".iflow",
499
584
  ".opencode",
500
585
  ".agents",
586
+ ".codex",
501
587
  ".kilocode",
502
588
  ".kiro",
503
589
  ".gemini",
504
590
  ".agent",
591
+ ".windsurf",
592
+ ".qoder",
593
+ ".codebuddy",
594
+ ".github/copilot",
505
595
  )
506
596
  """All platform config directory names (used by detect_platform exclusion checks)."""
507
597
 
@@ -523,19 +613,21 @@ def detect_platform(project_root: Path) -> Platform:
523
613
  2. .opencode directory exists → opencode
524
614
  3. .iflow directory exists → iflow
525
615
  4. .cursor directory exists (without .claude) → cursor
526
- 5. .agents/skills exists and no other platform dirs → codex
616
+ 5. .codex exists and no other platform dirs → codex
527
617
  6. .kilocode directory exists → kilo
528
618
  7. .kiro/skills exists and no other platform dirs → kiro
529
619
  8. .gemini directory exists → gemini
530
620
  9. .agent/workflows exists and no other platform dirs → antigravity
531
- 10. .qoder directory exists → qoder
532
- 11. Defaultclaude
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
533
625
 
534
626
  Args:
535
627
  project_root: Project root directory
536
628
 
537
629
  Returns:
538
- Detected platform ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', or 'qoder')
630
+ Detected platform ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', 'codebuddy', or default 'claude')
539
631
  """
540
632
  import os
541
633
 
@@ -551,7 +643,10 @@ def detect_platform(project_root: Path) -> Platform:
551
643
  "kiro",
552
644
  "gemini",
553
645
  "antigravity",
646
+ "windsurf",
554
647
  "qoder",
648
+ "codebuddy",
649
+ "copilot",
555
650
  ):
556
651
  return env_platform # type: ignore
557
652
 
@@ -572,9 +667,10 @@ def detect_platform(project_root: Path) -> Platform:
572
667
  if (project_root / ".gemini").is_dir():
573
668
  return "gemini"
574
669
 
575
- # Check for Codex skills directory only when no other platform config exists
576
- if (project_root / ".agents" / "skills").is_dir() and not _has_other_platform_dir(
577
- project_root, {".agents"}
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"}
578
674
  ):
579
675
  return "codex"
580
676
 
@@ -596,10 +692,26 @@ def detect_platform(project_root: Path) -> Platform:
596
692
  ):
597
693
  return "antigravity"
598
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
+
703
+ # Check for .codebuddy directory (CodeBuddy-specific)
704
+ if (project_root / ".codebuddy").is_dir():
705
+ return "codebuddy"
706
+
599
707
  # Check for .qoder directory (Qoder-specific)
600
708
  if (project_root / ".qoder").is_dir():
601
709
  return "qoder"
602
710
 
711
+ # Check for .github/copilot directory (GitHub Copilot-specific)
712
+ if (project_root / ".github" / "copilot").is_dir():
713
+ return "copilot"
714
+
603
715
  return "claude"
604
716
 
605
717
 
@@ -21,6 +21,15 @@ DEFAULT_MAX_JOURNAL_LINES = 2000
21
21
  CONFIG_FILE = "config.yaml"
22
22
 
23
23
 
24
+ def _is_true_config_value(value: object) -> bool:
25
+ """Return True when a config value represents an enabled flag."""
26
+ if isinstance(value, bool):
27
+ return value
28
+ if isinstance(value, str):
29
+ return value.strip().lower() == "true"
30
+ return False
31
+
32
+
24
33
  def _get_config_path(repo_root: Path | None = None) -> Path:
25
34
  """Get path to config.yaml."""
26
35
  root = repo_root or get_repo_root()
@@ -130,6 +139,37 @@ def get_submodule_packages(repo_root: Path | None = None) -> dict[str, str]:
130
139
  }
131
140
 
132
141
 
142
+ def get_git_packages(repo_root: Path | None = None) -> dict[str, str]:
143
+ """Get packages that have their own independent git repository.
144
+
145
+ These are sub-directories with their own .git (not submodules),
146
+ marked with ``git: true`` in config.yaml.
147
+
148
+ Returns:
149
+ Dict mapping package name to its path for git-repo packages.
150
+ Empty dict if none configured.
151
+
152
+ Example config::
153
+
154
+ packages:
155
+ backend:
156
+ path: iqs
157
+ git: true
158
+
159
+ Example return::
160
+
161
+ {"backend": "iqs"}
162
+ """
163
+ packages = get_packages(repo_root)
164
+ if packages is None:
165
+ return {}
166
+ return {
167
+ name: cfg.get("path", name)
168
+ for name, cfg in packages.items()
169
+ if _is_true_config_value(cfg.get("git"))
170
+ }
171
+
172
+
133
173
  def is_monorepo(repo_root: Path | None = None) -> bool:
134
174
  """Check if the project is configured as a monorepo (has packages in config)."""
135
175
  return get_packages(repo_root) is not None
@@ -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
  ---