@mindfoldhq/trellis 0.5.0-beta.9 → 0.5.0-rc.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.
Files changed (192) hide show
  1. package/README.md +60 -95
  2. package/dist/cli/index.js +7 -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 +117 -117
  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 +289 -33
  10. package/dist/commands/update.js.map +1 -1
  11. package/dist/configurators/antigravity.d.ts.map +1 -1
  12. package/dist/configurators/antigravity.js +2 -8
  13. package/dist/configurators/antigravity.js.map +1 -1
  14. package/dist/configurators/claude.d.ts.map +1 -1
  15. package/dist/configurators/claude.js +4 -10
  16. package/dist/configurators/claude.js.map +1 -1
  17. package/dist/configurators/codebuddy.d.ts.map +1 -1
  18. package/dist/configurators/codebuddy.js +3 -3
  19. package/dist/configurators/codebuddy.js.map +1 -1
  20. package/dist/configurators/codex.d.ts.map +1 -1
  21. package/dist/configurators/codex.js +5 -13
  22. package/dist/configurators/codex.js.map +1 -1
  23. package/dist/configurators/copilot.d.ts.map +1 -1
  24. package/dist/configurators/copilot.js +5 -19
  25. package/dist/configurators/copilot.js.map +1 -1
  26. package/dist/configurators/cursor.d.ts.map +1 -1
  27. package/dist/configurators/cursor.js +3 -3
  28. package/dist/configurators/cursor.js.map +1 -1
  29. package/dist/configurators/droid.d.ts.map +1 -1
  30. package/dist/configurators/droid.js +3 -3
  31. package/dist/configurators/droid.js.map +1 -1
  32. package/dist/configurators/gemini.d.ts.map +1 -1
  33. package/dist/configurators/gemini.js +3 -5
  34. package/dist/configurators/gemini.js.map +1 -1
  35. package/dist/configurators/index.d.ts.map +1 -1
  36. package/dist/configurators/index.js +37 -49
  37. package/dist/configurators/index.js.map +1 -1
  38. package/dist/configurators/kilo.d.ts.map +1 -1
  39. package/dist/configurators/kilo.js +2 -8
  40. package/dist/configurators/kilo.js.map +1 -1
  41. package/dist/configurators/kiro.d.ts.map +1 -1
  42. package/dist/configurators/kiro.js +3 -3
  43. package/dist/configurators/kiro.js.map +1 -1
  44. package/dist/configurators/opencode.d.ts.map +1 -1
  45. package/dist/configurators/opencode.js +7 -4
  46. package/dist/configurators/opencode.js.map +1 -1
  47. package/dist/configurators/pi.d.ts +3 -0
  48. package/dist/configurators/pi.d.ts.map +1 -0
  49. package/dist/configurators/pi.js +44 -0
  50. package/dist/configurators/pi.js.map +1 -0
  51. package/dist/configurators/qoder.d.ts.map +1 -1
  52. package/dist/configurators/qoder.js +3 -5
  53. package/dist/configurators/qoder.js.map +1 -1
  54. package/dist/configurators/shared.d.ts +28 -6
  55. package/dist/configurators/shared.d.ts.map +1 -1
  56. package/dist/configurators/shared.js +47 -15
  57. package/dist/configurators/shared.js.map +1 -1
  58. package/dist/configurators/windsurf.d.ts.map +1 -1
  59. package/dist/configurators/windsurf.js +2 -8
  60. package/dist/configurators/windsurf.js.map +1 -1
  61. package/dist/constants/paths.d.ts +2 -0
  62. package/dist/constants/paths.d.ts.map +1 -1
  63. package/dist/constants/paths.js +2 -0
  64. package/dist/constants/paths.js.map +1 -1
  65. package/dist/migrations/manifests/0.5.0-beta.0.json +2 -0
  66. package/dist/migrations/manifests/0.5.0-beta.10.json +9 -0
  67. package/dist/migrations/manifests/0.5.0-beta.11.json +9 -0
  68. package/dist/migrations/manifests/0.5.0-beta.12.json +9 -0
  69. package/dist/migrations/manifests/0.5.0-beta.13.json +9 -0
  70. package/dist/migrations/manifests/0.5.0-beta.14.json +9 -0
  71. package/dist/migrations/manifests/0.5.0-beta.15.json +116 -0
  72. package/dist/migrations/manifests/0.5.0-beta.16.json +9 -0
  73. package/dist/migrations/manifests/0.5.0-beta.17.json +9 -0
  74. package/dist/migrations/manifests/0.5.0-beta.18.json +9 -0
  75. package/dist/migrations/manifests/0.5.0-beta.19.json +9 -0
  76. package/dist/migrations/manifests/0.5.0-beta.5.json +2 -0
  77. package/dist/migrations/manifests/0.5.0-rc.0.json +9 -0
  78. package/dist/templates/claude/agents/trellis-research.md +1 -1
  79. package/dist/templates/claude/settings.json +0 -4
  80. package/dist/templates/codebuddy/agents/trellis-research.md +1 -1
  81. package/dist/templates/codex/agents/trellis-research.toml +3 -2
  82. package/dist/templates/codex/hooks/session-start.py +126 -26
  83. package/dist/templates/codex/skills/finish-work/SKILL.md +41 -109
  84. package/dist/templates/codex/skills/start/SKILL.md +12 -9
  85. package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +73 -0
  86. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/add-project-local-conventions.md +83 -0
  87. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +54 -0
  88. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-context-loading.md +81 -0
  89. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-hooks.md +57 -0
  90. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +78 -0
  91. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-spec-structure.md +83 -0
  92. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-task-lifecycle.md +90 -0
  93. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-workflow.md +64 -0
  94. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/overview.md +55 -0
  95. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/context-injection.md +68 -0
  96. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +80 -0
  97. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/overview.md +51 -0
  98. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/spec-system.md +102 -0
  99. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +101 -0
  100. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workflow.md +75 -0
  101. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workspace-memory.md +71 -0
  102. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +79 -0
  103. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/hooks-and-settings.md +69 -0
  104. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +59 -0
  105. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +74 -0
  106. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +83 -0
  107. package/dist/templates/common/commands/continue.md +9 -5
  108. package/dist/templates/common/commands/finish-work.md +34 -10
  109. package/dist/templates/common/index.d.ts +22 -2
  110. package/dist/templates/common/index.d.ts.map +1 -1
  111. package/dist/templates/common/index.js +53 -4
  112. package/dist/templates/common/index.js.map +1 -1
  113. package/dist/templates/common/skills/brainstorm.md +3 -0
  114. package/dist/templates/copilot/hooks/session-start.py +127 -30
  115. package/dist/templates/copilot/prompts/finish-work.prompt.md +44 -112
  116. package/dist/templates/copilot/prompts/start.prompt.md +12 -9
  117. package/dist/templates/cursor/agents/trellis-check.md +1 -1
  118. package/dist/templates/cursor/agents/trellis-implement.md +1 -1
  119. package/dist/templates/cursor/agents/trellis-research.md +2 -2
  120. package/dist/templates/cursor/hooks.json +7 -1
  121. package/dist/templates/droid/droids/trellis-research.md +1 -1
  122. package/dist/templates/extract.d.ts +6 -0
  123. package/dist/templates/extract.d.ts.map +1 -1
  124. package/dist/templates/extract.js +14 -0
  125. package/dist/templates/extract.js.map +1 -1
  126. package/dist/templates/gemini/agents/trellis-research.md +1 -1
  127. package/dist/templates/kiro/agents/trellis-research.json +1 -1
  128. package/dist/templates/markdown/agents.md +19 -12
  129. package/dist/templates/markdown/gitignore.txt +3 -0
  130. package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +24 -0
  131. package/dist/templates/opencode/agents/trellis-check.md +1 -1
  132. package/dist/templates/opencode/agents/trellis-implement.md +7 -4
  133. package/dist/templates/opencode/agents/trellis-research.md +2 -2
  134. package/dist/templates/opencode/lib/trellis-context.js +100 -13
  135. package/dist/templates/opencode/plugins/inject-subagent-context.js +70 -5
  136. package/dist/templates/opencode/plugins/inject-workflow-state.js +38 -58
  137. package/dist/templates/opencode/plugins/session-start.js +76 -31
  138. package/dist/templates/pi/agents/trellis-check.md +28 -0
  139. package/dist/templates/pi/agents/trellis-implement.md +33 -0
  140. package/dist/templates/pi/agents/trellis-research.md +25 -0
  141. package/dist/templates/pi/extensions/trellis/index.ts.txt +997 -0
  142. package/dist/templates/pi/index.d.ts +5 -0
  143. package/dist/templates/pi/index.d.ts.map +1 -0
  144. package/dist/templates/pi/index.js +12 -0
  145. package/dist/templates/pi/index.js.map +1 -0
  146. package/dist/templates/pi/settings.json +12 -0
  147. package/dist/templates/qoder/agents/trellis-research.md +1 -1
  148. package/dist/templates/shared-hooks/index.d.ts +31 -0
  149. package/dist/templates/shared-hooks/index.d.ts.map +1 -1
  150. package/dist/templates/shared-hooks/index.js +59 -0
  151. package/dist/templates/shared-hooks/index.js.map +1 -1
  152. package/dist/templates/shared-hooks/inject-shell-session-context.py +180 -0
  153. package/dist/templates/shared-hooks/inject-subagent-context.py +156 -27
  154. package/dist/templates/shared-hooks/inject-workflow-state.py +85 -105
  155. package/dist/templates/shared-hooks/session-start.py +222 -36
  156. package/dist/templates/trellis/gitignore.txt +3 -0
  157. package/dist/templates/trellis/index.d.ts +1 -0
  158. package/dist/templates/trellis/index.d.ts.map +1 -1
  159. package/dist/templates/trellis/index.js +2 -0
  160. package/dist/templates/trellis/index.js.map +1 -1
  161. package/dist/templates/trellis/scripts/common/__init__.py +8 -0
  162. package/dist/templates/trellis/scripts/common/active_task.py +593 -0
  163. package/dist/templates/trellis/scripts/common/cli_adapter.py +72 -14
  164. package/dist/templates/trellis/scripts/common/paths.py +61 -58
  165. package/dist/templates/trellis/scripts/common/session_context.py +12 -0
  166. package/dist/templates/trellis/scripts/common/task_context.py +27 -194
  167. package/dist/templates/trellis/scripts/common/task_store.py +102 -26
  168. package/dist/templates/trellis/scripts/common/tasks.py +4 -1
  169. package/dist/templates/trellis/scripts/common/workflow_phase.py +15 -3
  170. package/dist/templates/trellis/scripts/task.py +99 -34
  171. package/dist/templates/trellis/workflow.md +332 -69
  172. package/dist/types/ai-tools.d.ts +12 -3
  173. package/dist/types/ai-tools.d.ts.map +1 -1
  174. package/dist/types/ai-tools.js +29 -0
  175. package/dist/types/ai-tools.js.map +1 -1
  176. package/dist/utils/file-writer.d.ts.map +1 -1
  177. package/dist/utils/file-writer.js +7 -2
  178. package/dist/utils/file-writer.js.map +1 -1
  179. package/dist/utils/posix.d.ts +13 -0
  180. package/dist/utils/posix.d.ts.map +1 -0
  181. package/dist/utils/posix.js +15 -0
  182. package/dist/utils/posix.js.map +1 -0
  183. package/dist/utils/template-fetcher.d.ts +22 -6
  184. package/dist/utils/template-fetcher.d.ts.map +1 -1
  185. package/dist/utils/template-fetcher.js +405 -27
  186. package/dist/utils/template-fetcher.js.map +1 -1
  187. package/dist/utils/template-hash.d.ts +22 -3
  188. package/dist/utils/template-hash.d.ts.map +1 -1
  189. package/dist/utils/template-hash.js +99 -19
  190. package/dist/utils/template-hash.js.map +1 -1
  191. package/package.json +7 -7
  192. package/dist/templates/shared-hooks/statusline.py +0 -218
@@ -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, Windsurf, Qoder, CodeBuddy, GitHub Copilot, and Factory Droid interfaces.
4
+ Abstracts differences between Claude Code, OpenCode, Cursor, iFlow, Codex, Kilo, Kiro Code, Gemini CLI, Antigravity, Windsurf, Qoder, CodeBuddy, GitHub Copilot, Factory Droid, and Pi Agent interfaces.
5
5
 
6
6
  Supported platforms:
7
7
  - claude: Claude Code (default)
@@ -18,6 +18,7 @@ Supported platforms:
18
18
  - codebuddy: CodeBuddy
19
19
  - copilot: GitHub Copilot (VS Code)
20
20
  - droid: Factory Droid (commands-based)
21
+ - pi: Pi Agent (extension-backed)
21
22
 
22
23
  Usage:
23
24
  from common.cli_adapter import CLIAdapter
@@ -51,6 +52,7 @@ Platform = Literal[
51
52
  "codebuddy",
52
53
  "copilot",
53
54
  "droid",
55
+ "pi",
54
56
  ]
55
57
 
56
58
 
@@ -95,7 +97,7 @@ class CLIAdapter:
95
97
  """Get platform-specific config directory name.
96
98
 
97
99
  Returns:
98
- Directory name ('.claude', '.opencode', '.cursor', '.iflow', '.codex', '.kilocode', '.kiro', '.gemini', '.agent', '.windsurf', '.qoder', or '.codebuddy')
100
+ Directory name ('.claude', '.opencode', '.cursor', '.iflow', '.codex', '.kilocode', '.kiro', '.gemini', '.agent', '.windsurf', '.qoder', '.codebuddy', '.github/copilot', '.factory', or '.pi')
99
101
  """
100
102
  if self.platform == "opencode":
101
103
  return ".opencode"
@@ -123,6 +125,8 @@ class CLIAdapter:
123
125
  return ".github/copilot"
124
126
  elif self.platform == "droid":
125
127
  return ".factory"
128
+ elif self.platform == "pi":
129
+ return ".pi"
126
130
  else:
127
131
  return ".claude"
128
132
 
@@ -133,7 +137,7 @@ class CLIAdapter:
133
137
  project_root: Project root directory
134
138
 
135
139
  Returns:
136
- Path to config directory (.claude, .opencode, .cursor, .iflow, .codex, .kilocode, .kiro, .gemini, .agent, .windsurf, .qoder, or .codebuddy)
140
+ Path to config directory (.claude, .opencode, .cursor, .iflow, .codex, .kilocode, .kiro, .gemini, .agent, .windsurf, .qoder, .codebuddy, .github/copilot, .factory, or .pi)
137
141
  """
138
142
  return project_root / self.config_dir_name
139
143
 
@@ -167,8 +171,20 @@ class CLIAdapter:
167
171
  Antigravity uses workflow directory: .agent/workflows/<name>.md
168
172
  Windsurf uses workflow directory: .windsurf/workflows/trellis-<name>.md
169
173
  Copilot uses prompt files: .github/prompts/<name>.prompt.md
174
+ Pi uses prompt templates: .pi/prompts/trellis-<name>.md
170
175
  Claude/OpenCode use subdirectory: .claude/commands/trellis/<name>.md
171
176
  """
177
+ if self.platform == "pi":
178
+ prompts_dir = self.get_config_dir(project_root) / "prompts"
179
+ if not parts:
180
+ return prompts_dir
181
+ if len(parts) >= 2 and parts[0] == "trellis":
182
+ filename = parts[-1]
183
+ if filename.endswith(".md"):
184
+ filename = filename[:-3]
185
+ return prompts_dir / f"trellis-{filename}.md"
186
+ return prompts_dir / Path(*parts)
187
+
172
188
  if self.platform == "windsurf":
173
189
  workflow_dir = self.get_config_dir(project_root) / "workflows"
174
190
  if not parts:
@@ -222,19 +238,22 @@ class CLIAdapter:
222
238
 
223
239
  Note:
224
240
  Cursor: .cursor/commands/trellis-<name>.md
225
- Codex: .agents/skills/<name>/SKILL.md
226
- Kiro: .kiro/skills/<name>/SKILL.md
241
+ Codex: .agents/skills/trellis-<name>/SKILL.md
242
+ Kiro: .kiro/skills/trellis-<name>/SKILL.md
227
243
  Gemini: .gemini/commands/trellis/<name>.toml
228
244
  Antigravity: .agent/workflows/<name>.md
229
245
  Windsurf: .windsurf/workflows/trellis-<name>.md
246
+ Pi: .pi/prompts/trellis-<name>.md
230
247
  Others: .{platform}/commands/trellis/<name>.md
231
248
  """
232
249
  if self.platform == "cursor":
233
250
  return f".cursor/commands/trellis-{name}.md"
234
251
  elif self.platform == "codex":
235
- return f".agents/skills/{name}/SKILL.md"
252
+ # 0.5.0-beta.0 renamed all skill dirs to add the `trellis-` prefix
253
+ # (see that release's manifest for the 60+ rename entries).
254
+ return f".agents/skills/trellis-{name}/SKILL.md"
236
255
  elif self.platform == "kiro":
237
- return f".kiro/skills/{name}/SKILL.md"
256
+ return f".kiro/skills/trellis-{name}/SKILL.md"
238
257
  elif self.platform == "gemini":
239
258
  return f".gemini/commands/trellis/{name}.toml"
240
259
  elif self.platform == "antigravity":
@@ -247,6 +266,8 @@ class CLIAdapter:
247
266
  return f".github/prompts/{name}.prompt.md"
248
267
  elif self.platform == "droid":
249
268
  return f".factory/commands/trellis/{name}.md"
269
+ elif self.platform == "pi":
270
+ return f".pi/prompts/trellis-{name}.md"
250
271
  else:
251
272
  return f"{self.config_dir_name}/commands/trellis/{name}.md"
252
273
 
@@ -282,6 +303,8 @@ class CLIAdapter:
282
303
  return {}
283
304
  elif self.platform == "droid":
284
305
  return {}
306
+ elif self.platform == "pi":
307
+ return {}
285
308
  else:
286
309
  return {"CLAUDE_NON_INTERACTIVE": "1"}
287
310
 
@@ -365,6 +388,8 @@ class CLIAdapter:
365
388
  raise ValueError(
366
389
  "Factory Droid CLI agent run is not yet supported."
367
390
  )
391
+ elif self.platform == "pi":
392
+ cmd = ["pi", "-p", prompt]
368
393
 
369
394
  else: # claude
370
395
  cmd = ["claude", "-p"]
@@ -429,6 +454,8 @@ class CLIAdapter:
429
454
  raise ValueError(
430
455
  "Factory Droid CLI resume is not yet supported."
431
456
  )
457
+ elif self.platform == "pi":
458
+ return ["pi", "-c", session_id]
432
459
  else:
433
460
  return ["claude", "--resume", session_id]
434
461
 
@@ -501,6 +528,8 @@ class CLIAdapter:
501
528
  return "copilot"
502
529
  elif self.platform == "droid":
503
530
  return "droid"
531
+ elif self.platform == "pi":
532
+ return "pi"
504
533
  else:
505
534
  return "claude"
506
535
 
@@ -511,7 +540,7 @@ class CLIAdapter:
511
540
  Claude Code, OpenCode, iFlow, and Codex support CLI agent execution.
512
541
  Cursor is IDE-only and doesn't support CLI agents.
513
542
  """
514
- return self.platform in ("claude", "opencode", "iflow", "codex")
543
+ return self.platform in ("claude", "opencode", "iflow", "codex", "pi")
515
544
 
516
545
  @property
517
546
  def requires_agent_definition_file(self) -> bool:
@@ -565,7 +594,7 @@ def get_cli_adapter(platform: str = "claude") -> CLIAdapter:
565
594
  """Get CLI adapter for the specified platform.
566
595
 
567
596
  Args:
568
- platform: Platform name ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', or 'codebuddy')
597
+ platform: Platform name ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', 'codebuddy', 'copilot', 'droid', or 'pi')
569
598
 
570
599
  Returns:
571
600
  CLIAdapter instance
@@ -588,9 +617,10 @@ def get_cli_adapter(platform: str = "claude") -> CLIAdapter:
588
617
  "codebuddy",
589
618
  "copilot",
590
619
  "droid",
620
+ "pi",
591
621
  ):
592
622
  raise ValueError(
593
- f"Unsupported platform: {platform} (must be 'claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', 'codebuddy', 'copilot', or 'droid')"
623
+ f"Unsupported platform: {platform} (must be 'claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', 'codebuddy', 'copilot', 'droid', or 'pi')"
594
624
  )
595
625
 
596
626
  return CLIAdapter(platform=platform) # type: ignore
@@ -601,7 +631,6 @@ _ALL_PLATFORM_CONFIG_DIRS = (
601
631
  ".cursor",
602
632
  ".iflow",
603
633
  ".opencode",
604
- ".agents",
605
634
  ".codex",
606
635
  ".kilocode",
607
636
  ".kiro",
@@ -612,8 +641,13 @@ _ALL_PLATFORM_CONFIG_DIRS = (
612
641
  ".codebuddy",
613
642
  ".github/copilot",
614
643
  ".factory",
644
+ ".pi",
615
645
  )
616
- """All platform config directory names (used by detect_platform exclusion checks)."""
646
+ """Platform-specific config directory names used by detect_platform exclusion
647
+ checks. `.agents/skills/` is NOT listed here: it is a shared cross-platform
648
+ layer (written by Codex, also consumed by Amp/Cline/Warp/etc. via the
649
+ agentskills.io standard), not a single-platform signal. Its presence must not
650
+ block detection of Kiro, Antigravity, Windsurf, or other platforms."""
617
651
 
618
652
 
619
653
  def _has_other_platform_dir(project_root: Path, exclude: set[str]) -> bool:
@@ -641,13 +675,14 @@ def detect_platform(project_root: Path) -> Platform:
641
675
  10. .windsurf/workflows exists and no other platform dirs → windsurf
642
676
  11. .codebuddy directory exists → codebuddy
643
677
  12. .qoder directory exists → qoder
644
- 13. Defaultclaude
678
+ 13. .pi directory exists pi
679
+ 14. Default → claude
645
680
 
646
681
  Args:
647
682
  project_root: Project root directory
648
683
 
649
684
  Returns:
650
- Detected platform ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', 'codebuddy', or default 'claude')
685
+ Detected platform ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', 'codebuddy', 'copilot', 'droid', 'pi', or default 'claude')
651
686
  """
652
687
  import os
653
688
 
@@ -668,6 +703,7 @@ def detect_platform(project_root: Path) -> Platform:
668
703
  "codebuddy",
669
704
  "copilot",
670
705
  "droid",
706
+ "pi",
671
707
  ):
672
708
  return env_platform # type: ignore
673
709
 
@@ -737,6 +773,28 @@ def detect_platform(project_root: Path) -> Platform:
737
773
  if (project_root / ".factory").is_dir():
738
774
  return "droid"
739
775
 
776
+ # Check for .pi directory (Pi Agent-specific)
777
+ if (project_root / ".pi").is_dir():
778
+ return "pi"
779
+
780
+ # Fallback: checkout only has the Codex shared-skills layer
781
+ # (.agents/skills/trellis-* dirs) and no explicit platform config dir.
782
+ # Happens on fresh clones where .codex/ is gitignored/absent but the
783
+ # shared skills were committed to git. Must guard against the case
784
+ # where .claude/ or any other platform dir also exists — .agents/skills/
785
+ # can legitimately coexist with any platform as a shared consumption
786
+ # layer for Amp/Cline/Warp/etc.
787
+ agents_skills = project_root / ".agents" / "skills"
788
+ if agents_skills.is_dir() and not _has_other_platform_dir(
789
+ project_root, set()
790
+ ):
791
+ try:
792
+ for entry in agents_skills.iterdir():
793
+ if entry.is_dir() and entry.name.startswith("trellis-"):
794
+ return "codex"
795
+ except OSError:
796
+ pass
797
+
740
798
  return "claude"
741
799
 
742
800
 
@@ -207,22 +207,8 @@ def count_lines(file_path: Path) -> int:
207
207
  # Current Task Management
208
208
  # =============================================================================
209
209
 
210
- def _get_current_task_file(repo_root: Path | None = None) -> Path:
211
- """Get .current-task file path.
212
-
213
- Args:
214
- repo_root: Repository root path. Defaults to auto-detected.
215
-
216
- Returns:
217
- Path to .current-task file.
218
- """
219
- if repo_root is None:
220
- repo_root = get_repo_root()
221
- return repo_root / DIR_WORKFLOW / FILE_CURRENT_TASK
222
-
223
-
224
210
  def normalize_task_ref(task_ref: str) -> str:
225
- """Normalize a task ref for stable storage in .current-task.
211
+ """Normalize a task ref for stable runtime storage.
226
212
 
227
213
  Stored refs should prefer repo-relative POSIX paths like
228
214
  `.trellis/tasks/03-27-my-task`, even on Windows. Absolute paths are preserved
@@ -247,7 +233,7 @@ def normalize_task_ref(task_ref: str) -> str:
247
233
 
248
234
 
249
235
  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."""
236
+ """Resolve a task ref to an absolute task directory path."""
251
237
  if repo_root is None:
252
238
  repo_root = get_repo_root()
253
239
 
@@ -265,7 +251,11 @@ def resolve_task_ref(task_ref: str, repo_root: Path | None = None) -> Path | Non
265
251
  return repo_root / DIR_WORKFLOW / DIR_TASKS / path_obj
266
252
 
267
253
 
268
- def get_current_task(repo_root: Path | None = None) -> str | None:
254
+ def get_current_task(
255
+ repo_root: Path | None = None,
256
+ platform_input: dict | None = None,
257
+ platform: str | None = None,
258
+ ) -> str | None:
269
259
  """Get current task directory path (relative to repo_root).
270
260
 
271
261
  Args:
@@ -274,19 +264,19 @@ def get_current_task(repo_root: Path | None = None) -> str | None:
274
264
  Returns:
275
265
  Relative path to current task directory or None.
276
266
  """
277
- current_file = _get_current_task_file(repo_root)
267
+ if repo_root is None:
268
+ repo_root = get_repo_root()
278
269
 
279
- if not current_file.is_file():
280
- return None
270
+ from .active_task import resolve_active_task
281
271
 
282
- try:
283
- content = current_file.read_text(encoding="utf-8").strip()
284
- return normalize_task_ref(content) if content else None
285
- except (OSError, IOError):
286
- return None
272
+ return resolve_active_task(repo_root, platform_input, platform).task_path
287
273
 
288
274
 
289
- def get_current_task_abs(repo_root: Path | None = None) -> Path | None:
275
+ def get_current_task_abs(
276
+ repo_root: Path | None = None,
277
+ platform_input: dict | None = None,
278
+ platform: str | None = None,
279
+ ) -> Path | None:
290
280
  """Get current task directory absolute path.
291
281
 
292
282
  Args:
@@ -298,14 +288,33 @@ def get_current_task_abs(repo_root: Path | None = None) -> Path | None:
298
288
  if repo_root is None:
299
289
  repo_root = get_repo_root()
300
290
 
301
- relative = get_current_task(repo_root)
291
+ relative = get_current_task(repo_root, platform_input, platform)
302
292
  if relative:
303
293
  return resolve_task_ref(relative, repo_root)
304
294
  return None
305
295
 
306
296
 
307
- def set_current_task(task_path: str, repo_root: Path | None = None) -> bool:
308
- """Set current task.
297
+ def get_current_task_source(
298
+ repo_root: Path | None = None,
299
+ platform_input: dict | None = None,
300
+ platform: str | None = None,
301
+ ) -> tuple[str, str | None, str | None]:
302
+ """Get active task source as (`source`, `context_key`, `task_path`)."""
303
+ if repo_root is None:
304
+ repo_root = get_repo_root()
305
+
306
+ from .active_task import get_current_task_source as _get_source
307
+
308
+ return _get_source(repo_root, platform_input, platform)
309
+
310
+
311
+ def set_current_task(
312
+ task_path: str,
313
+ repo_root: Path | None = None,
314
+ platform_input: dict | None = None,
315
+ platform: str | None = None,
316
+ ) -> bool:
317
+ """Set current task in session scope.
309
318
 
310
319
  Args:
311
320
  task_path: Task directory path (relative to repo_root).
@@ -317,31 +326,22 @@ def set_current_task(task_path: str, repo_root: Path | None = None) -> bool:
317
326
  if repo_root is None:
318
327
  repo_root = get_repo_root()
319
328
 
320
- normalized = normalize_task_ref(task_path)
321
- if not normalized:
322
- return False
329
+ from .active_task import set_active_task
323
330
 
324
- # Verify task directory exists
325
- full_path = resolve_task_ref(normalized, repo_root)
326
- if full_path is None or not full_path.is_dir():
327
- return False
331
+ return set_active_task(
332
+ task_path,
333
+ repo_root,
334
+ platform_input=platform_input,
335
+ platform=platform,
336
+ ) is not None
328
337
 
329
- try:
330
- normalized = full_path.relative_to(repo_root).as_posix()
331
- except ValueError:
332
- normalized = str(full_path)
333
338
 
334
- current_file = _get_current_task_file(repo_root)
335
-
336
- try:
337
- current_file.write_text(normalized, encoding="utf-8")
338
- return True
339
- except (OSError, IOError):
340
- return False
341
-
342
-
343
- def clear_current_task(repo_root: Path | None = None) -> bool:
344
- """Clear current task.
339
+ def clear_current_task(
340
+ repo_root: Path | None = None,
341
+ platform_input: dict | None = None,
342
+ platform: str | None = None,
343
+ ) -> bool:
344
+ """Clear current task in session scope.
345
345
 
346
346
  Args:
347
347
  repo_root: Repository root path. Defaults to auto-detected.
@@ -349,14 +349,17 @@ def clear_current_task(repo_root: Path | None = None) -> bool:
349
349
  Returns:
350
350
  True on success.
351
351
  """
352
- current_file = _get_current_task_file(repo_root)
352
+ if repo_root is None:
353
+ repo_root = get_repo_root()
353
354
 
354
- try:
355
- if current_file.is_file():
356
- current_file.unlink()
357
- return True
358
- except (OSError, IOError):
359
- return False
355
+ from .active_task import clear_active_task
356
+
357
+ clear_active_task(
358
+ repo_root,
359
+ platform_input=platform_input,
360
+ platform=platform,
361
+ )
362
+ return True
360
363
 
361
364
 
362
365
  def has_current_task(repo_root: Path | None = None) -> bool:
@@ -29,6 +29,7 @@ from .paths import (
29
29
  count_lines,
30
30
  get_active_journal_file,
31
31
  get_current_task,
32
+ get_current_task_source,
32
33
  get_developer,
33
34
  get_repo_root,
34
35
  get_tasks_dir,
@@ -279,7 +280,11 @@ def get_context_text(repo_root: Path | None = None) -> str:
279
280
  current_task = get_current_task(repo_root)
280
281
  if current_task:
281
282
  current_task_dir = repo_root / current_task
283
+ source_type, context_key, _ = get_current_task_source(repo_root)
282
284
  lines.append(f"Path: {current_task}")
285
+ lines.append(
286
+ f"Source: {source_type}" + (f":{context_key}" if context_key else "")
287
+ )
283
288
 
284
289
  ct = load_task(current_task_dir)
285
290
  if ct:
@@ -429,12 +434,15 @@ def get_context_record_json(repo_root: Path | None = None) -> dict:
429
434
  current_task_info = None
430
435
  current_task = get_current_task(repo_root)
431
436
  if current_task:
437
+ source_type, context_key, _ = get_current_task_source(repo_root)
432
438
  ct = load_task(repo_root / current_task)
433
439
  if ct:
434
440
  current_task_info = {
435
441
  "path": current_task,
436
442
  "name": ct.name,
437
443
  "status": ct.status,
444
+ "source": source_type,
445
+ "contextKey": context_key,
438
446
  }
439
447
 
440
448
  # Package git repos
@@ -539,7 +547,11 @@ def get_context_text_record(repo_root: Path | None = None) -> str:
539
547
  lines.append("## CURRENT TASK")
540
548
  current_task = get_current_task(repo_root)
541
549
  if current_task:
550
+ source_type, context_key, _ = get_current_task_source(repo_root)
542
551
  lines.append(f"Path: {current_task}")
552
+ lines.append(
553
+ f"Source: {source_type}" + (f":{context_key}" if context_key else "")
554
+ )
543
555
  ct = load_task(repo_root / current_task)
544
556
  if ct:
545
557
  lines.append(f"Name: {ct.name}")