@fifine/aim-studio 0.0.1

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 (289) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/bin/aim.js +3 -0
  4. package/dist/cli/index.d.ts +3 -0
  5. package/dist/cli/index.d.ts.map +1 -0
  6. package/dist/cli/index.js +89 -0
  7. package/dist/cli/index.js.map +1 -0
  8. package/dist/commands/init.d.ts +13 -0
  9. package/dist/commands/init.d.ts.map +1 -0
  10. package/dist/commands/init.js +513 -0
  11. package/dist/commands/init.js.map +1 -0
  12. package/dist/commands/update.d.ts +27 -0
  13. package/dist/commands/update.d.ts.map +1 -0
  14. package/dist/commands/update.js +1275 -0
  15. package/dist/commands/update.js.map +1 -0
  16. package/dist/configurators/claude.d.ts +32 -0
  17. package/dist/configurators/claude.d.ts.map +1 -0
  18. package/dist/configurators/claude.js +98 -0
  19. package/dist/configurators/claude.js.map +1 -0
  20. package/dist/configurators/index.d.ts +51 -0
  21. package/dist/configurators/index.d.ts.map +1 -0
  22. package/dist/configurators/index.js +113 -0
  23. package/dist/configurators/index.js.map +1 -0
  24. package/dist/configurators/shared.d.ts +12 -0
  25. package/dist/configurators/shared.d.ts.map +1 -0
  26. package/dist/configurators/shared.js +21 -0
  27. package/dist/configurators/shared.js.map +1 -0
  28. package/dist/configurators/workflow.d.ts +28 -0
  29. package/dist/configurators/workflow.d.ts.map +1 -0
  30. package/dist/configurators/workflow.js +147 -0
  31. package/dist/configurators/workflow.js.map +1 -0
  32. package/dist/constants/paths.d.ts +68 -0
  33. package/dist/constants/paths.d.ts.map +1 -0
  34. package/dist/constants/paths.js +77 -0
  35. package/dist/constants/paths.js.map +1 -0
  36. package/dist/constants/version.d.ts +9 -0
  37. package/dist/constants/version.d.ts.map +1 -0
  38. package/dist/constants/version.js +15 -0
  39. package/dist/constants/version.js.map +1 -0
  40. package/dist/index.d.ts +9 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +9 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/migrations/index.d.ts +54 -0
  45. package/dist/migrations/index.d.ts.map +1 -0
  46. package/dist/migrations/index.js +160 -0
  47. package/dist/migrations/index.js.map +1 -0
  48. package/dist/migrations/manifests/0.0.1.json +9 -0
  49. package/dist/migrations/manifests/0.1.9.json +30 -0
  50. package/dist/migrations/manifests/0.2.0.json +49 -0
  51. package/dist/migrations/manifests/0.2.12.json +9 -0
  52. package/dist/migrations/manifests/0.2.13.json +9 -0
  53. package/dist/migrations/manifests/0.2.14.json +175 -0
  54. package/dist/migrations/manifests/0.2.15.json +33 -0
  55. package/dist/migrations/manifests/0.3.0-beta.0.json +278 -0
  56. package/dist/migrations/manifests/0.3.0-beta.1.json +9 -0
  57. package/dist/migrations/manifests/0.3.0-beta.10.json +9 -0
  58. package/dist/migrations/manifests/0.3.0-beta.11.json +9 -0
  59. package/dist/migrations/manifests/0.3.0-beta.12.json +9 -0
  60. package/dist/migrations/manifests/0.3.0-beta.13.json +9 -0
  61. package/dist/migrations/manifests/0.3.0-beta.14.json +9 -0
  62. package/dist/migrations/manifests/0.3.0-beta.15.json +9 -0
  63. package/dist/migrations/manifests/0.3.0-beta.16.json +9 -0
  64. package/dist/migrations/manifests/0.3.0-beta.2.json +9 -0
  65. package/dist/migrations/manifests/0.3.0-beta.3.json +9 -0
  66. package/dist/migrations/manifests/0.3.0-beta.4.json +9 -0
  67. package/dist/migrations/manifests/0.3.0-beta.5.json +9 -0
  68. package/dist/migrations/manifests/0.3.0-beta.6.json +9 -0
  69. package/dist/migrations/manifests/0.3.0-beta.7.json +11 -0
  70. package/dist/migrations/manifests/0.3.0-beta.8.json +9 -0
  71. package/dist/migrations/manifests/0.3.0-beta.9.json +9 -0
  72. package/dist/migrations/manifests/0.3.0-rc.0.json +9 -0
  73. package/dist/migrations/manifests/0.3.0-rc.1.json +9 -0
  74. package/dist/migrations/manifests/0.3.0-rc.2.json +9 -0
  75. package/dist/templates/CLAUDE.md +71 -0
  76. package/dist/templates/aim/gitignore.txt +29 -0
  77. package/dist/templates/aim/index.d.ts +49 -0
  78. package/dist/templates/aim/index.d.ts.map +1 -0
  79. package/dist/templates/aim/index.js +92 -0
  80. package/dist/templates/aim/index.js.map +1 -0
  81. package/dist/templates/aim/scripts/__init__.py +5 -0
  82. package/dist/templates/aim/scripts/add_session.py +392 -0
  83. package/dist/templates/aim/scripts/common/__init__.py +80 -0
  84. package/dist/templates/aim/scripts/common/cli_adapter.py +435 -0
  85. package/dist/templates/aim/scripts/common/developer.py +190 -0
  86. package/dist/templates/aim/scripts/common/git_context.py +383 -0
  87. package/dist/templates/aim/scripts/common/paths.py +347 -0
  88. package/dist/templates/aim/scripts/common/phase.py +253 -0
  89. package/dist/templates/aim/scripts/common/registry.py +366 -0
  90. package/dist/templates/aim/scripts/common/task_queue.py +255 -0
  91. package/dist/templates/aim/scripts/common/task_utils.py +178 -0
  92. package/dist/templates/aim/scripts/common/worktree.py +219 -0
  93. package/dist/templates/aim/scripts/create_bootstrap.py +290 -0
  94. package/dist/templates/aim/scripts/get_context.py +16 -0
  95. package/dist/templates/aim/scripts/get_developer.py +26 -0
  96. package/dist/templates/aim/scripts/init_developer.py +51 -0
  97. package/dist/templates/aim/scripts/multi_agent/__init__.py +5 -0
  98. package/dist/templates/aim/scripts/multi_agent/cleanup.py +403 -0
  99. package/dist/templates/aim/scripts/multi_agent/create_pr.py +329 -0
  100. package/dist/templates/aim/scripts/multi_agent/plan.py +233 -0
  101. package/dist/templates/aim/scripts/multi_agent/start.py +461 -0
  102. package/dist/templates/aim/scripts/multi_agent/status.py +817 -0
  103. package/dist/templates/aim/scripts/task.py +1068 -0
  104. package/dist/templates/aim/scripts-shell-archive/add-session.sh +384 -0
  105. package/dist/templates/aim/scripts-shell-archive/common/developer.sh +129 -0
  106. package/dist/templates/aim/scripts-shell-archive/common/git-context.sh +263 -0
  107. package/dist/templates/aim/scripts-shell-archive/common/paths.sh +208 -0
  108. package/dist/templates/aim/scripts-shell-archive/common/phase.sh +150 -0
  109. package/dist/templates/aim/scripts-shell-archive/common/registry.sh +247 -0
  110. package/dist/templates/aim/scripts-shell-archive/common/task-queue.sh +142 -0
  111. package/dist/templates/aim/scripts-shell-archive/common/task-utils.sh +151 -0
  112. package/dist/templates/aim/scripts-shell-archive/common/worktree.sh +128 -0
  113. package/dist/templates/aim/scripts-shell-archive/create-bootstrap.sh +299 -0
  114. package/dist/templates/aim/scripts-shell-archive/get-context.sh +7 -0
  115. package/dist/templates/aim/scripts-shell-archive/get-developer.sh +15 -0
  116. package/dist/templates/aim/scripts-shell-archive/init-developer.sh +34 -0
  117. package/dist/templates/aim/scripts-shell-archive/multi-agent/cleanup.sh +396 -0
  118. package/dist/templates/aim/scripts-shell-archive/multi-agent/create-pr.sh +241 -0
  119. package/dist/templates/aim/scripts-shell-archive/multi-agent/plan.sh +207 -0
  120. package/dist/templates/aim/scripts-shell-archive/multi-agent/start.sh +317 -0
  121. package/dist/templates/aim/scripts-shell-archive/multi-agent/status.sh +828 -0
  122. package/dist/templates/aim/scripts-shell-archive/task.sh +1204 -0
  123. package/dist/templates/aim/tasks/.gitkeep +0 -0
  124. package/dist/templates/aim/workflow.md +258 -0
  125. package/dist/templates/aim/worktree.yaml +47 -0
  126. package/dist/templates/claude/agents/check.md +122 -0
  127. package/dist/templates/claude/agents/debug.md +106 -0
  128. package/dist/templates/claude/agents/dispatch.md +230 -0
  129. package/dist/templates/claude/agents/implement.md +96 -0
  130. package/dist/templates/claude/agents/plan.md +396 -0
  131. package/dist/templates/claude/agents/research.md +120 -0
  132. package/dist/templates/claude/agents/story.md +53 -0
  133. package/dist/templates/claude/commands/aim/before-backend-dev.md +13 -0
  134. package/dist/templates/claude/commands/aim/before-frontend-dev.md +13 -0
  135. package/dist/templates/claude/commands/aim/break-loop.md +153 -0
  136. package/dist/templates/claude/commands/aim/check-backend.md +13 -0
  137. package/dist/templates/claude/commands/aim/check-cross-layer.md +153 -0
  138. package/dist/templates/claude/commands/aim/check-frontend.md +13 -0
  139. package/dist/templates/claude/commands/aim/check-story.md +59 -0
  140. package/dist/templates/claude/commands/aim/create-command.md +154 -0
  141. package/dist/templates/claude/commands/aim/export.md +187 -0
  142. package/dist/templates/claude/commands/aim/finish-work.md +104 -0
  143. package/dist/templates/claude/commands/aim/integrate-skill.md +219 -0
  144. package/dist/templates/claude/commands/aim/onboard.md +358 -0
  145. package/dist/templates/claude/commands/aim/parallel.md +217 -0
  146. package/dist/templates/claude/commands/aim/portrait.md +170 -0
  147. package/dist/templates/claude/commands/aim/record-session.md +92 -0
  148. package/dist/templates/claude/commands/aim/start.md +112 -0
  149. package/dist/templates/claude/commands/aim/story.md +140 -0
  150. package/dist/templates/claude/commands/aim/update-spec.md +285 -0
  151. package/dist/templates/claude/commands/aim/visualize.md +182 -0
  152. package/dist/templates/claude/commands/trellis/before-backend-dev.md +13 -0
  153. package/dist/templates/claude/commands/trellis/before-frontend-dev.md +13 -0
  154. package/dist/templates/claude/commands/trellis/break-loop.md +125 -0
  155. package/dist/templates/claude/commands/trellis/check-backend.md +13 -0
  156. package/dist/templates/claude/commands/trellis/check-cross-layer.md +153 -0
  157. package/dist/templates/claude/commands/trellis/check-frontend.md +13 -0
  158. package/dist/templates/claude/commands/trellis/create-command.md +154 -0
  159. package/dist/templates/claude/commands/trellis/finish-work.md +129 -0
  160. package/dist/templates/claude/commands/trellis/integrate-skill.md +219 -0
  161. package/dist/templates/claude/commands/trellis/onboard.md +358 -0
  162. package/dist/templates/claude/commands/trellis/parallel.md +193 -0
  163. package/dist/templates/claude/commands/trellis/record-session.md +62 -0
  164. package/dist/templates/claude/commands/trellis/start.md +280 -0
  165. package/dist/templates/claude/commands/trellis/update-spec.md +285 -0
  166. package/dist/templates/claude/hooks/inject-subagent-context.py +772 -0
  167. package/dist/templates/claude/hooks/ralph-loop.py +388 -0
  168. package/dist/templates/claude/hooks/session-start.py +142 -0
  169. package/dist/templates/claude/index.d.ts +54 -0
  170. package/dist/templates/claude/index.d.ts.map +1 -0
  171. package/dist/templates/claude/index.js +85 -0
  172. package/dist/templates/claude/index.js.map +1 -0
  173. package/dist/templates/claude/settings.json +41 -0
  174. package/dist/templates/extract.d.ts +68 -0
  175. package/dist/templates/extract.d.ts.map +1 -0
  176. package/dist/templates/extract.js +128 -0
  177. package/dist/templates/extract.js.map +1 -0
  178. package/dist/templates/markdown/agents.md +25 -0
  179. package/dist/templates/markdown/gitignore.txt +12 -0
  180. package/dist/templates/markdown/index.d.ts +32 -0
  181. package/dist/templates/markdown/index.d.ts.map +1 -0
  182. package/dist/templates/markdown/index.js +58 -0
  183. package/dist/templates/markdown/index.js.map +1 -0
  184. package/dist/templates/markdown/spec/backend/database-guidelines.md.txt +51 -0
  185. package/dist/templates/markdown/spec/backend/directory-structure.md.txt +54 -0
  186. package/dist/templates/markdown/spec/backend/error-handling.md.txt +51 -0
  187. package/dist/templates/markdown/spec/backend/index.md +40 -0
  188. package/dist/templates/markdown/spec/backend/index.md.txt +38 -0
  189. package/dist/templates/markdown/spec/backend/logging-guidelines.md.txt +51 -0
  190. package/dist/templates/markdown/spec/backend/quality-guidelines.md.txt +51 -0
  191. package/dist/templates/markdown/spec/backend/script-conventions.md +467 -0
  192. package/dist/templates/markdown/spec/frontend/component-guidelines.md.txt +59 -0
  193. package/dist/templates/markdown/spec/frontend/directory-structure.md.txt +54 -0
  194. package/dist/templates/markdown/spec/frontend/hook-guidelines.md.txt +51 -0
  195. package/dist/templates/markdown/spec/frontend/index.md.txt +39 -0
  196. package/dist/templates/markdown/spec/frontend/quality-guidelines.md.txt +51 -0
  197. package/dist/templates/markdown/spec/frontend/state-management.md.txt +51 -0
  198. package/dist/templates/markdown/spec/frontend/type-safety.md.txt +51 -0
  199. package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md +118 -0
  200. package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md.txt +92 -0
  201. package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +94 -0
  202. package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md +394 -0
  203. package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +319 -0
  204. package/dist/templates/markdown/spec/guides/index.md.txt +89 -0
  205. package/dist/templates/markdown/spec/story/character.md.txt +95 -0
  206. package/dist/templates/markdown/spec/story/index.md.txt +31 -0
  207. package/dist/templates/markdown/spec/story/script.md.txt +313 -0
  208. package/dist/templates/markdown/spec/story/world.md.txt +92 -0
  209. package/dist/templates/markdown/workspace-index.md +123 -0
  210. package/dist/templates/markdown/worktree.yaml.txt +58 -0
  211. package/dist/templates/trellis/gitignore.txt +29 -0
  212. package/dist/templates/trellis/index.d.ts +49 -0
  213. package/dist/templates/trellis/index.d.ts.map +1 -0
  214. package/dist/templates/trellis/index.js +92 -0
  215. package/dist/templates/trellis/index.js.map +1 -0
  216. package/dist/templates/trellis/scripts/__init__.py +5 -0
  217. package/dist/templates/trellis/scripts/add_session.py +392 -0
  218. package/dist/templates/trellis/scripts/common/__init__.py +80 -0
  219. package/dist/templates/trellis/scripts/common/cli_adapter.py +435 -0
  220. package/dist/templates/trellis/scripts/common/developer.py +190 -0
  221. package/dist/templates/trellis/scripts/common/git_context.py +383 -0
  222. package/dist/templates/trellis/scripts/common/paths.py +347 -0
  223. package/dist/templates/trellis/scripts/common/phase.py +253 -0
  224. package/dist/templates/trellis/scripts/common/registry.py +366 -0
  225. package/dist/templates/trellis/scripts/common/task_queue.py +255 -0
  226. package/dist/templates/trellis/scripts/common/task_utils.py +178 -0
  227. package/dist/templates/trellis/scripts/common/worktree.py +219 -0
  228. package/dist/templates/trellis/scripts/create_bootstrap.py +290 -0
  229. package/dist/templates/trellis/scripts/get_context.py +16 -0
  230. package/dist/templates/trellis/scripts/get_developer.py +26 -0
  231. package/dist/templates/trellis/scripts/init_developer.py +51 -0
  232. package/dist/templates/trellis/scripts/multi_agent/__init__.py +5 -0
  233. package/dist/templates/trellis/scripts/multi_agent/cleanup.py +403 -0
  234. package/dist/templates/trellis/scripts/multi_agent/create_pr.py +329 -0
  235. package/dist/templates/trellis/scripts/multi_agent/plan.py +233 -0
  236. package/dist/templates/trellis/scripts/multi_agent/start.py +461 -0
  237. package/dist/templates/trellis/scripts/multi_agent/status.py +817 -0
  238. package/dist/templates/trellis/scripts/task.py +1056 -0
  239. package/dist/templates/trellis/scripts-shell-archive/add-session.sh +384 -0
  240. package/dist/templates/trellis/scripts-shell-archive/common/developer.sh +129 -0
  241. package/dist/templates/trellis/scripts-shell-archive/common/git-context.sh +263 -0
  242. package/dist/templates/trellis/scripts-shell-archive/common/paths.sh +208 -0
  243. package/dist/templates/trellis/scripts-shell-archive/common/phase.sh +150 -0
  244. package/dist/templates/trellis/scripts-shell-archive/common/registry.sh +247 -0
  245. package/dist/templates/trellis/scripts-shell-archive/common/task-queue.sh +142 -0
  246. package/dist/templates/trellis/scripts-shell-archive/common/task-utils.sh +151 -0
  247. package/dist/templates/trellis/scripts-shell-archive/common/worktree.sh +128 -0
  248. package/dist/templates/trellis/scripts-shell-archive/create-bootstrap.sh +299 -0
  249. package/dist/templates/trellis/scripts-shell-archive/get-context.sh +7 -0
  250. package/dist/templates/trellis/scripts-shell-archive/get-developer.sh +15 -0
  251. package/dist/templates/trellis/scripts-shell-archive/init-developer.sh +34 -0
  252. package/dist/templates/trellis/scripts-shell-archive/multi-agent/cleanup.sh +396 -0
  253. package/dist/templates/trellis/scripts-shell-archive/multi-agent/create-pr.sh +241 -0
  254. package/dist/templates/trellis/scripts-shell-archive/multi-agent/plan.sh +207 -0
  255. package/dist/templates/trellis/scripts-shell-archive/multi-agent/start.sh +317 -0
  256. package/dist/templates/trellis/scripts-shell-archive/multi-agent/status.sh +828 -0
  257. package/dist/templates/trellis/scripts-shell-archive/task.sh +1204 -0
  258. package/dist/templates/trellis/tasks/.gitkeep +0 -0
  259. package/dist/templates/trellis/workflow.md +416 -0
  260. package/dist/templates/trellis/worktree.yaml +47 -0
  261. package/dist/types/ai-tools.d.ts +48 -0
  262. package/dist/types/ai-tools.d.ts.map +1 -0
  263. package/dist/types/ai-tools.js +32 -0
  264. package/dist/types/ai-tools.js.map +1 -0
  265. package/dist/types/migration.d.ts +86 -0
  266. package/dist/types/migration.d.ts.map +1 -0
  267. package/dist/types/migration.js +8 -0
  268. package/dist/types/migration.js.map +1 -0
  269. package/dist/utils/compare-versions.d.ts +12 -0
  270. package/dist/utils/compare-versions.d.ts.map +1 -0
  271. package/dist/utils/compare-versions.js +76 -0
  272. package/dist/utils/compare-versions.js.map +1 -0
  273. package/dist/utils/file-writer.d.ts +23 -0
  274. package/dist/utils/file-writer.d.ts.map +1 -0
  275. package/dist/utils/file-writer.js +140 -0
  276. package/dist/utils/file-writer.js.map +1 -0
  277. package/dist/utils/project-detector.d.ts +16 -0
  278. package/dist/utils/project-detector.d.ts.map +1 -0
  279. package/dist/utils/project-detector.js +188 -0
  280. package/dist/utils/project-detector.js.map +1 -0
  281. package/dist/utils/template-fetcher.d.ts +51 -0
  282. package/dist/utils/template-fetcher.d.ts.map +1 -0
  283. package/dist/utils/template-fetcher.js +174 -0
  284. package/dist/utils/template-fetcher.js.map +1 -0
  285. package/dist/utils/template-hash.d.ts +78 -0
  286. package/dist/utils/template-hash.d.ts.map +1 -0
  287. package/dist/utils/template-hash.js +239 -0
  288. package/dist/utils/template-hash.js.map +1 -0
  289. package/package.json +87 -0
@@ -0,0 +1,435 @@
1
+ """
2
+ CLI Adapter for Multi-Platform Support.
3
+
4
+ Abstracts differences between Claude Code, OpenCode, Cursor, iFlow, and Codex interfaces.
5
+
6
+ Supported platforms:
7
+ - claude: Claude Code (default)
8
+ - opencode: OpenCode
9
+ - cursor: Cursor IDE
10
+ - iflow: iFlow CLI
11
+ - codex: Codex CLI (skills-based)
12
+
13
+ Usage:
14
+ from common.cli_adapter import CLIAdapter
15
+
16
+ adapter = CLIAdapter("opencode")
17
+ cmd = adapter.build_run_command(
18
+ agent="dispatch",
19
+ session_id="abc123",
20
+ prompt="Start the pipeline"
21
+ )
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from dataclasses import dataclass
27
+ from pathlib import Path
28
+ from typing import ClassVar, Literal
29
+
30
+ Platform = Literal["claude", "opencode", "cursor", "iflow", "codex"]
31
+
32
+
33
+ @dataclass
34
+ class CLIAdapter:
35
+ """Adapter for different AI coding CLI tools."""
36
+
37
+ platform: Platform
38
+
39
+ # =========================================================================
40
+ # Agent Name Mapping
41
+ # =========================================================================
42
+
43
+ # OpenCode has built-in agents that cannot be overridden
44
+ # See: https://github.com/sst/opencode/issues/4271
45
+ # Note: Class-level constant, not a dataclass field
46
+ _AGENT_NAME_MAP: ClassVar[dict[Platform, dict[str, str]]] = {
47
+ "claude": {}, # No mapping needed
48
+ "opencode": {
49
+ "plan": "trellis-plan", # 'plan' is built-in in OpenCode
50
+ },
51
+ }
52
+
53
+ def get_agent_name(self, agent: str) -> str:
54
+ """Get platform-specific agent name.
55
+
56
+ Args:
57
+ agent: Original agent name (e.g., 'plan', 'dispatch')
58
+
59
+ Returns:
60
+ Platform-specific agent name (e.g., 'trellis-plan' for OpenCode)
61
+ """
62
+ mapping = self._AGENT_NAME_MAP.get(self.platform, {})
63
+ return mapping.get(agent, agent)
64
+
65
+ # =========================================================================
66
+ # Agent Path
67
+ # =========================================================================
68
+
69
+ @property
70
+ def config_dir_name(self) -> str:
71
+ """Get platform-specific config directory name.
72
+
73
+ Returns:
74
+ Directory name ('.claude', '.opencode', '.cursor', '.iflow', or '.agents')
75
+ """
76
+ if self.platform == "opencode":
77
+ return ".opencode"
78
+ elif self.platform == "cursor":
79
+ return ".cursor"
80
+ elif self.platform == "iflow":
81
+ return ".iflow"
82
+ elif self.platform == "codex":
83
+ return ".agents"
84
+ else:
85
+ return ".claude"
86
+
87
+ def get_config_dir(self, project_root: Path) -> Path:
88
+ """Get platform-specific config directory.
89
+
90
+ Args:
91
+ project_root: Project root directory
92
+
93
+ Returns:
94
+ Path to config directory (.claude, .opencode, .cursor, .iflow, or .agents)
95
+ """
96
+ return project_root / self.config_dir_name
97
+
98
+ def get_agent_path(self, agent: str, project_root: Path) -> Path:
99
+ """Get path to agent definition file.
100
+
101
+ Args:
102
+ agent: Agent name (original, before mapping)
103
+ project_root: Project root directory
104
+
105
+ Returns:
106
+ Path to agent .md file
107
+ """
108
+ mapped_name = self.get_agent_name(agent)
109
+ return self.get_config_dir(project_root) / "agents" / f"{mapped_name}.md"
110
+
111
+ def get_commands_path(self, project_root: Path, *parts: str) -> Path:
112
+ """Get path to commands directory or specific command file.
113
+
114
+ Args:
115
+ project_root: Project root directory
116
+ *parts: Additional path parts (e.g., 'trellis', 'finish-work.md')
117
+
118
+ Returns:
119
+ Path to commands directory or file
120
+
121
+ Note:
122
+ Cursor uses prefix naming: .cursor/commands/trellis-<name>.md
123
+ Claude/OpenCode use subdirectory: .claude/commands/trellis/<name>.md
124
+ """
125
+ if not parts:
126
+ return self.get_config_dir(project_root) / "commands"
127
+
128
+ # Cursor uses prefix naming instead of subdirectory
129
+ if self.platform == "cursor" and len(parts) >= 2 and parts[0] == "trellis":
130
+ # Convert trellis/<name>.md to trellis-<name>.md
131
+ filename = parts[-1]
132
+ return self.get_config_dir(project_root) / "commands" / f"trellis-{filename}"
133
+
134
+ return self.get_config_dir(project_root) / "commands" / Path(*parts)
135
+
136
+ def get_trellis_command_path(self, name: str) -> str:
137
+ """Get relative path to a trellis command file.
138
+
139
+ Args:
140
+ name: Command name without extension (e.g., 'finish-work', 'check-backend')
141
+
142
+ Returns:
143
+ Relative path string for use in JSONL entries
144
+
145
+ Note:
146
+ Cursor: .cursor/commands/trellis-<name>.md
147
+ Codex: .agents/skills/<name>/SKILL.md
148
+ Others: .{platform}/commands/trellis/<name>.md
149
+ """
150
+ if self.platform == "cursor":
151
+ return f".cursor/commands/trellis-{name}.md"
152
+ elif self.platform == "codex":
153
+ return f".agents/skills/{name}/SKILL.md"
154
+ else:
155
+ return f"{self.config_dir_name}/commands/trellis/{name}.md"
156
+
157
+ # =========================================================================
158
+ # Environment Variables
159
+ # =========================================================================
160
+
161
+ def get_non_interactive_env(self) -> dict[str, str]:
162
+ """Get environment variables for non-interactive mode.
163
+
164
+ Returns:
165
+ Dict of environment variables to set
166
+ """
167
+ if self.platform == "opencode":
168
+ return {"OPENCODE_NON_INTERACTIVE": "1"}
169
+ elif self.platform == "codex":
170
+ return {"CODEX_NON_INTERACTIVE": "1"}
171
+ else:
172
+ return {"CLAUDE_NON_INTERACTIVE": "1"}
173
+
174
+ # =========================================================================
175
+ # CLI Command Building
176
+ # =========================================================================
177
+
178
+ def build_run_command(
179
+ self,
180
+ agent: str,
181
+ prompt: str,
182
+ session_id: str | None = None,
183
+ skip_permissions: bool = True,
184
+ verbose: bool = True,
185
+ json_output: bool = True,
186
+ ) -> list[str]:
187
+ """Build CLI command for running an agent.
188
+
189
+ Args:
190
+ agent: Agent name (will be mapped if needed)
191
+ prompt: Prompt to send to the agent
192
+ session_id: Optional session ID (Claude Code only for creation)
193
+ skip_permissions: Whether to skip permission prompts
194
+ verbose: Whether to enable verbose output
195
+ json_output: Whether to use JSON output format
196
+
197
+ Returns:
198
+ List of command arguments
199
+ """
200
+ mapped_agent = self.get_agent_name(agent)
201
+
202
+ if self.platform == "opencode":
203
+ cmd = ["opencode", "run"]
204
+ cmd.extend(["--agent", mapped_agent])
205
+
206
+ # Note: OpenCode 'run' mode is non-interactive by default
207
+ # No equivalent to Claude Code's --dangerously-skip-permissions
208
+ # See: https://github.com/anomalyco/opencode/issues/9070
209
+
210
+ if json_output:
211
+ cmd.extend(["--format", "json"])
212
+
213
+ if verbose:
214
+ cmd.extend(["--log-level", "DEBUG", "--print-logs"])
215
+
216
+ # Note: OpenCode doesn't support --session-id on creation
217
+ # Session ID must be extracted from logs after startup
218
+
219
+ cmd.append(prompt)
220
+
221
+ elif self.platform == "codex":
222
+ cmd = ["codex", "exec"]
223
+ cmd.append(prompt)
224
+
225
+ else: # claude
226
+ cmd = ["claude", "-p"]
227
+ cmd.extend(["--agent", mapped_agent])
228
+
229
+ if session_id:
230
+ cmd.extend(["--session-id", session_id])
231
+
232
+ if skip_permissions:
233
+ cmd.append("--dangerously-skip-permissions")
234
+
235
+ if json_output:
236
+ cmd.extend(["--output-format", "stream-json"])
237
+
238
+ if verbose:
239
+ cmd.append("--verbose")
240
+
241
+ cmd.append(prompt)
242
+
243
+ return cmd
244
+
245
+ def build_resume_command(self, session_id: str) -> list[str]:
246
+ """Build CLI command for resuming a session.
247
+
248
+ Args:
249
+ session_id: Session ID to resume
250
+
251
+ Returns:
252
+ List of command arguments
253
+ """
254
+ if self.platform == "opencode":
255
+ return ["opencode", "run", "--session", session_id]
256
+ elif self.platform == "codex":
257
+ return ["codex", "resume", session_id]
258
+ else:
259
+ return ["claude", "--resume", session_id]
260
+
261
+ def get_resume_command_str(self, session_id: str, cwd: str | None = None) -> str:
262
+ """Get human-readable resume command string.
263
+
264
+ Args:
265
+ session_id: Session ID to resume
266
+ cwd: Optional working directory to cd into
267
+
268
+ Returns:
269
+ Command string for display
270
+ """
271
+ cmd = self.build_resume_command(session_id)
272
+ cmd_str = " ".join(cmd)
273
+
274
+ if cwd:
275
+ return f"cd {cwd} && {cmd_str}"
276
+ return cmd_str
277
+
278
+ # =========================================================================
279
+ # Platform Detection Helpers
280
+ # =========================================================================
281
+
282
+ @property
283
+ def is_opencode(self) -> bool:
284
+ """Check if platform is OpenCode."""
285
+ return self.platform == "opencode"
286
+
287
+ @property
288
+ def is_claude(self) -> bool:
289
+ """Check if platform is Claude Code."""
290
+ return self.platform == "claude"
291
+
292
+ @property
293
+ def is_cursor(self) -> bool:
294
+ """Check if platform is Cursor."""
295
+ return self.platform == "cursor"
296
+
297
+ @property
298
+ def cli_name(self) -> str:
299
+ """Get CLI executable name.
300
+
301
+ Note: Cursor doesn't have a CLI tool, returns None-like value.
302
+ """
303
+ if self.is_opencode:
304
+ return "opencode"
305
+ elif self.is_cursor:
306
+ return "cursor" # Note: Cursor is IDE-only, no CLI
307
+ else:
308
+ return "claude"
309
+
310
+ @property
311
+ def supports_cli_agents(self) -> bool:
312
+ """Check if platform supports running agents via CLI.
313
+
314
+ Claude Code and OpenCode support CLI agent execution.
315
+ Cursor is IDE-only and doesn't support CLI agents.
316
+ """
317
+ return self.platform in ("claude", "opencode")
318
+
319
+ # =========================================================================
320
+ # Session ID Handling
321
+ # =========================================================================
322
+
323
+ @property
324
+ def supports_session_id_on_create(self) -> bool:
325
+ """Check if platform supports specifying session ID on creation.
326
+
327
+ Claude Code: Yes (--session-id)
328
+ OpenCode: No (auto-generated, extract from logs)
329
+ """
330
+ return self.platform == "claude"
331
+
332
+ def extract_session_id_from_log(self, log_content: str) -> str | None:
333
+ """Extract session ID from log output (OpenCode only).
334
+
335
+ OpenCode generates session IDs in format: ses_xxx
336
+
337
+ Args:
338
+ log_content: Log file content
339
+
340
+ Returns:
341
+ Session ID if found, None otherwise
342
+ """
343
+ import re
344
+
345
+ # OpenCode session ID pattern
346
+ match = re.search(r"ses_[a-zA-Z0-9]+", log_content)
347
+ if match:
348
+ return match.group(0)
349
+ return None
350
+
351
+
352
+ # =============================================================================
353
+ # Factory Function
354
+ # =============================================================================
355
+
356
+
357
+ def get_cli_adapter(platform: str = "claude") -> CLIAdapter:
358
+ """Get CLI adapter for the specified platform.
359
+
360
+ Args:
361
+ platform: Platform name ('claude', 'opencode', 'cursor', 'iflow', or 'codex')
362
+
363
+ Returns:
364
+ CLIAdapter instance
365
+
366
+ Raises:
367
+ ValueError: If platform is not supported
368
+ """
369
+ if platform not in ("claude", "opencode", "cursor", "iflow", "codex"):
370
+ raise ValueError(f"Unsupported platform: {platform} (must be 'claude', 'opencode', 'cursor', 'iflow', or 'codex')")
371
+
372
+ return CLIAdapter(platform=platform) # type: ignore
373
+
374
+
375
+ def detect_platform(project_root: Path) -> Platform:
376
+ """Auto-detect platform based on existing config directories.
377
+
378
+ Detection order:
379
+ 1. TRELLIS_PLATFORM environment variable (if set)
380
+ 2. .opencode directory exists → opencode
381
+ 3. .iflow directory exists → iflow
382
+ 4. .cursor directory exists (without .claude) → cursor
383
+ 5. .agents/skills exists and no other platform dirs → codex
384
+ 6. Default → claude
385
+
386
+ Args:
387
+ project_root: Project root directory
388
+
389
+ Returns:
390
+ Detected platform ('claude', 'opencode', 'cursor', 'iflow', or 'codex')
391
+ """
392
+ import os
393
+
394
+ # Check environment variable first
395
+ env_platform = os.environ.get("TRELLIS_PLATFORM", "").lower()
396
+ if env_platform in ("claude", "opencode", "cursor", "iflow", "codex"):
397
+ return env_platform # type: ignore
398
+
399
+ # Check for .opencode directory (OpenCode-specific)
400
+ # Note: .claude might exist in both platforms during migration
401
+ if (project_root / ".opencode").is_dir():
402
+ return "opencode"
403
+
404
+ # Check for .iflow directory (iFlow-specific)
405
+ # Note: .claude might exist in both platforms during migration
406
+ if (project_root / ".iflow").is_dir():
407
+ return "iflow"
408
+
409
+ # Check for .cursor directory (Cursor-specific)
410
+ # Only detect as cursor if .claude doesn't exist (to avoid confusion)
411
+ if (project_root / ".cursor").is_dir() and not (project_root / ".claude").is_dir():
412
+ return "cursor"
413
+
414
+ # Check for Codex skills directory only when no other platform config exists
415
+ other_platform_dirs = (".claude", ".cursor", ".iflow", ".opencode")
416
+ has_other_platform_config = any(
417
+ (project_root / directory).is_dir() for directory in other_platform_dirs
418
+ )
419
+ if (project_root / ".agents" / "skills").is_dir() and not has_other_platform_config:
420
+ return "codex"
421
+
422
+ return "claude"
423
+
424
+
425
+ def get_cli_adapter_auto(project_root: Path) -> CLIAdapter:
426
+ """Get CLI adapter with auto-detected platform.
427
+
428
+ Args:
429
+ project_root: Project root directory
430
+
431
+ Returns:
432
+ CLIAdapter instance for detected platform
433
+ """
434
+ platform = detect_platform(project_root)
435
+ return CLIAdapter(platform=platform)
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Developer management utilities.
4
+
5
+ Provides:
6
+ init_developer - Initialize developer
7
+ ensure_developer - Ensure developer is initialized (exit if not)
8
+ show_developer_info - Show developer information
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import sys
14
+ from datetime import datetime
15
+ from pathlib import Path
16
+
17
+ from .paths import (
18
+ DIR_WORKFLOW,
19
+ DIR_WORKSPACE,
20
+ DIR_TASKS,
21
+ FILE_DEVELOPER,
22
+ FILE_JOURNAL_PREFIX,
23
+ get_repo_root,
24
+ get_developer,
25
+ check_developer,
26
+ )
27
+
28
+
29
+ # =============================================================================
30
+ # Developer Initialization
31
+ # =============================================================================
32
+
33
+ def init_developer(name: str, repo_root: Path | None = None) -> bool:
34
+ """Initialize developer.
35
+
36
+ Creates:
37
+ - .trellis/.developer file with developer info
38
+ - .trellis/workspace/<name>/ directory structure
39
+ - Initial journal file and index.md
40
+
41
+ Args:
42
+ name: Developer name.
43
+ repo_root: Repository root path. Defaults to auto-detected.
44
+
45
+ Returns:
46
+ True on success, False on error.
47
+ """
48
+ if not name:
49
+ print("Error: developer name is required", file=sys.stderr)
50
+ return False
51
+
52
+ if repo_root is None:
53
+ repo_root = get_repo_root()
54
+
55
+ dev_file = repo_root / DIR_WORKFLOW / FILE_DEVELOPER
56
+ workspace_dir = repo_root / DIR_WORKFLOW / DIR_WORKSPACE / name
57
+
58
+ # Create .developer file
59
+ initialized_at = datetime.now().isoformat()
60
+ try:
61
+ dev_file.write_text(
62
+ f"name={name}\ninitialized_at={initialized_at}\n",
63
+ encoding="utf-8"
64
+ )
65
+ except (OSError, IOError) as e:
66
+ print(f"Error: Failed to create .developer file: {e}", file=sys.stderr)
67
+ return False
68
+
69
+ # Create workspace directory structure
70
+ try:
71
+ workspace_dir.mkdir(parents=True, exist_ok=True)
72
+ except (OSError, IOError) as e:
73
+ print(f"Error: Failed to create workspace directory: {e}", file=sys.stderr)
74
+ return False
75
+
76
+ # Create initial journal file
77
+ journal_file = workspace_dir / f"{FILE_JOURNAL_PREFIX}1.md"
78
+ if not journal_file.exists():
79
+ today = datetime.now().strftime("%Y-%m-%d")
80
+ journal_content = f"""# Journal - {name} (Part 1)
81
+
82
+ > AI development session journal
83
+ > Started: {today}
84
+
85
+ ---
86
+
87
+ """
88
+ try:
89
+ journal_file.write_text(journal_content, encoding="utf-8")
90
+ except (OSError, IOError) as e:
91
+ print(f"Error: Failed to create journal file: {e}", file=sys.stderr)
92
+ return False
93
+
94
+ # Create index.md with markers for auto-update
95
+ index_file = workspace_dir / "index.md"
96
+ if not index_file.exists():
97
+ index_content = f"""# Workspace Index - {name}
98
+
99
+ > Journal tracking for AI development sessions.
100
+
101
+ ---
102
+
103
+ ## Current Status
104
+
105
+ <!-- @@@auto:current-status -->
106
+ - **Active File**: `journal-1.md`
107
+ - **Total Sessions**: 0
108
+ - **Last Active**: -
109
+ <!-- @@@/auto:current-status -->
110
+
111
+ ---
112
+
113
+ ## Active Documents
114
+
115
+ <!-- @@@auto:active-documents -->
116
+ | File | Lines | Status |
117
+ |------|-------|--------|
118
+ | `journal-1.md` | ~0 | Active |
119
+ <!-- @@@/auto:active-documents -->
120
+
121
+ ---
122
+
123
+ ## Session History
124
+
125
+ <!-- @@@auto:session-history -->
126
+ | # | Date | Title | Commits |
127
+ |---|------|-------|---------|
128
+ <!-- @@@/auto:session-history -->
129
+
130
+ ---
131
+
132
+ ## Notes
133
+
134
+ - Sessions are appended to journal files
135
+ - New journal file created when current exceeds 2000 lines
136
+ - Use `add_session.py` to record sessions
137
+ """
138
+ try:
139
+ index_file.write_text(index_content, encoding="utf-8")
140
+ except (OSError, IOError) as e:
141
+ print(f"Error: Failed to create index.md: {e}", file=sys.stderr)
142
+ return False
143
+
144
+ print(f"Developer initialized: {name}")
145
+ print(f" .developer file: {dev_file}")
146
+ print(f" Workspace dir: {workspace_dir}")
147
+
148
+ return True
149
+
150
+
151
+ def ensure_developer(repo_root: Path | None = None) -> None:
152
+ """Ensure developer is initialized, exit if not.
153
+
154
+ Args:
155
+ repo_root: Repository root path. Defaults to auto-detected.
156
+ """
157
+ if repo_root is None:
158
+ repo_root = get_repo_root()
159
+
160
+ if not check_developer(repo_root):
161
+ print("Error: Developer not initialized.", file=sys.stderr)
162
+ print(f"Run: python3 ./{DIR_WORKFLOW}/scripts/init_developer.py <your-name>", file=sys.stderr)
163
+ sys.exit(1)
164
+
165
+
166
+ def show_developer_info(repo_root: Path | None = None) -> None:
167
+ """Show developer information.
168
+
169
+ Args:
170
+ repo_root: Repository root path. Defaults to auto-detected.
171
+ """
172
+ if repo_root is None:
173
+ repo_root = get_repo_root()
174
+
175
+ developer = get_developer(repo_root)
176
+
177
+ if not developer:
178
+ print("Developer: (not initialized)")
179
+ else:
180
+ print(f"Developer: {developer}")
181
+ print(f"Workspace: {DIR_WORKFLOW}/{DIR_WORKSPACE}/{developer}/")
182
+ print(f"Tasks: {DIR_WORKFLOW}/{DIR_TASKS}/")
183
+
184
+
185
+ # =============================================================================
186
+ # Main Entry (for testing)
187
+ # =============================================================================
188
+
189
+ if __name__ == "__main__":
190
+ show_developer_info()