@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,178 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Task utility functions.
4
+
5
+ Provides:
6
+ is_safe_task_path - Validate task path is safe to operate on
7
+ find_task_by_name - Find task directory by name
8
+ archive_task_dir - Archive task to monthly directory
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import shutil
14
+ import sys
15
+ from datetime import datetime
16
+ from pathlib import Path
17
+
18
+ from .paths import get_repo_root
19
+
20
+
21
+ # =============================================================================
22
+ # Path Safety
23
+ # =============================================================================
24
+
25
+ def is_safe_task_path(task_path: str, repo_root: Path | None = None) -> bool:
26
+ """Check if a relative task path is safe to operate on.
27
+
28
+ Args:
29
+ task_path: Task path (relative to repo_root).
30
+ repo_root: Repository root path. Defaults to auto-detected.
31
+
32
+ Returns:
33
+ True if safe, False if dangerous.
34
+ """
35
+ if repo_root is None:
36
+ repo_root = get_repo_root()
37
+
38
+ # Check empty or null
39
+ if not task_path or task_path == "null":
40
+ print("Error: empty or null task path", file=sys.stderr)
41
+ return False
42
+
43
+ # Reject absolute paths
44
+ if task_path.startswith("/"):
45
+ print(f"Error: absolute path not allowed: {task_path}", file=sys.stderr)
46
+ return False
47
+
48
+ # Reject ".", "..", paths starting with "./" or "../", or containing ".."
49
+ if task_path in (".", "..") or task_path.startswith("./") or task_path.startswith("../") or ".." in task_path:
50
+ print(f"Error: path traversal not allowed: {task_path}", file=sys.stderr)
51
+ return False
52
+
53
+ # Final check: ensure resolved path is not the repo root
54
+ abs_path = repo_root / task_path
55
+ if abs_path.exists():
56
+ try:
57
+ resolved = abs_path.resolve()
58
+ root_resolved = repo_root.resolve()
59
+ if resolved == root_resolved:
60
+ print(f"Error: path resolves to repo root: {task_path}", file=sys.stderr)
61
+ return False
62
+ except (OSError, IOError):
63
+ pass
64
+
65
+ return True
66
+
67
+
68
+ # =============================================================================
69
+ # Task Lookup
70
+ # =============================================================================
71
+
72
+ def find_task_by_name(task_name: str, tasks_dir: Path) -> Path | None:
73
+ """Find task directory by name (exact or suffix match).
74
+
75
+ Args:
76
+ task_name: Task name to find.
77
+ tasks_dir: Tasks directory path.
78
+
79
+ Returns:
80
+ Absolute path to task directory, or None if not found.
81
+ """
82
+ if not task_name or not tasks_dir or not tasks_dir.is_dir():
83
+ return None
84
+
85
+ # Try exact match first
86
+ exact_match = tasks_dir / task_name
87
+ if exact_match.is_dir():
88
+ return exact_match
89
+
90
+ # Try suffix match (e.g., "my-task" matches "01-21-my-task")
91
+ for d in tasks_dir.iterdir():
92
+ if d.is_dir() and d.name.endswith(f"-{task_name}"):
93
+ return d
94
+
95
+ return None
96
+
97
+
98
+ # =============================================================================
99
+ # Archive Operations
100
+ # =============================================================================
101
+
102
+ def archive_task_dir(task_dir_abs: Path, repo_root: Path | None = None) -> Path | None:
103
+ """Archive a task directory to archive/{YYYY-MM}/.
104
+
105
+ Args:
106
+ task_dir_abs: Absolute path to task directory.
107
+ repo_root: Repository root path. Defaults to auto-detected.
108
+
109
+ Returns:
110
+ Path to archived directory, or None on error.
111
+ """
112
+ if not task_dir_abs.is_dir():
113
+ print(f"Error: task directory not found: {task_dir_abs}", file=sys.stderr)
114
+ return None
115
+
116
+ # Get tasks directory (parent of the task)
117
+ tasks_dir = task_dir_abs.parent
118
+ archive_dir = tasks_dir / "archive"
119
+ year_month = datetime.now().strftime("%Y-%m")
120
+ month_dir = archive_dir / year_month
121
+
122
+ # Create archive directory
123
+ try:
124
+ month_dir.mkdir(parents=True, exist_ok=True)
125
+ except (OSError, IOError) as e:
126
+ print(f"Error: Failed to create archive directory: {e}", file=sys.stderr)
127
+ return None
128
+
129
+ # Move task to archive
130
+ task_name = task_dir_abs.name
131
+ dest = month_dir / task_name
132
+
133
+ try:
134
+ shutil.move(str(task_dir_abs), str(dest))
135
+ except (OSError, IOError, shutil.Error) as e:
136
+ print(f"Error: Failed to move task to archive: {e}", file=sys.stderr)
137
+ return None
138
+
139
+ return dest
140
+
141
+
142
+ def archive_task_complete(
143
+ task_dir_abs: Path,
144
+ repo_root: Path | None = None
145
+ ) -> dict[str, str]:
146
+ """Complete archive workflow: archive directory.
147
+
148
+ Args:
149
+ task_dir_abs: Absolute path to task directory.
150
+ repo_root: Repository root path. Defaults to auto-detected.
151
+
152
+ Returns:
153
+ Dict with archive result info.
154
+ """
155
+ if not task_dir_abs.is_dir():
156
+ print(f"Error: task directory not found: {task_dir_abs}", file=sys.stderr)
157
+ return {}
158
+
159
+ archive_dest = archive_task_dir(task_dir_abs, repo_root)
160
+ if archive_dest:
161
+ return {"archived_to": str(archive_dest)}
162
+
163
+ return {}
164
+
165
+
166
+ # =============================================================================
167
+ # Main Entry (for testing)
168
+ # =============================================================================
169
+
170
+ if __name__ == "__main__":
171
+ from .paths import get_tasks_dir
172
+
173
+ repo = get_repo_root()
174
+ tasks = get_tasks_dir(repo)
175
+
176
+ print(f"Tasks dir: {tasks}")
177
+ print(f"is_safe_task_path('.aim-studio/tasks/test'): {is_safe_task_path('.aim-studio/tasks/test', repo)}")
178
+ print(f"is_safe_task_path('../test'): {is_safe_task_path('../test', repo)}")
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Worktree utilities for Multi-Agent Pipeline.
4
+
5
+ Provides:
6
+ get_worktree_config - Get worktree.yaml path
7
+ get_worktree_base_dir - Get worktree storage directory
8
+ get_worktree_copy_files - Get files to copy list
9
+ get_worktree_post_create_hooks - Get post-create hooks
10
+ get_agents_dir - Get agents registry directory
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from pathlib import Path
16
+
17
+ from .paths import (
18
+ DIR_WORKFLOW,
19
+ get_repo_root,
20
+ get_workspace_dir,
21
+ )
22
+
23
+
24
+ # =============================================================================
25
+ # YAML Simple Parser (no dependencies)
26
+ # =============================================================================
27
+
28
+ def parse_simple_yaml(content: str) -> dict:
29
+ """Parse simple YAML (only supports key: value and lists).
30
+
31
+ Args:
32
+ content: YAML content string.
33
+
34
+ Returns:
35
+ Parsed dict.
36
+ """
37
+ result: dict = {}
38
+ current_list: list | None = None
39
+
40
+ for line in content.splitlines():
41
+ stripped = line.strip()
42
+ if not stripped or stripped.startswith("#"):
43
+ continue
44
+
45
+ if stripped.startswith("- "):
46
+ if current_list is not None:
47
+ current_list.append(stripped[2:].strip().strip('"').strip("'"))
48
+ elif ":" in stripped:
49
+ key, _, value = stripped.partition(":")
50
+ key = key.strip()
51
+ value = value.strip().strip('"').strip("'")
52
+ if value:
53
+ result[key] = value
54
+ _ = None
55
+ current_list = None
56
+ else:
57
+ _ = key
58
+ current_list = []
59
+ result[key] = current_list
60
+
61
+ return result
62
+
63
+
64
+ def _yaml_get_value(config_file: Path, key: str) -> str | None:
65
+ """Read simple value from worktree.yaml.
66
+
67
+ Args:
68
+ config_file: Path to config file.
69
+ key: Key to read.
70
+
71
+ Returns:
72
+ Value string or None.
73
+ """
74
+ try:
75
+ content = config_file.read_text(encoding="utf-8")
76
+ data = parse_simple_yaml(content)
77
+ value = data.get(key)
78
+ if isinstance(value, str):
79
+ return value
80
+ except (OSError, IOError):
81
+ pass
82
+ return None
83
+
84
+
85
+ def _yaml_get_list(config_file: Path, section: str) -> list[str]:
86
+ """Read list from worktree.yaml.
87
+
88
+ Args:
89
+ config_file: Path to config file.
90
+ section: Section name.
91
+
92
+ Returns:
93
+ List of items.
94
+ """
95
+ try:
96
+ content = config_file.read_text(encoding="utf-8")
97
+ data = parse_simple_yaml(content)
98
+ value = data.get(section)
99
+ if isinstance(value, list):
100
+ return [str(item) for item in value]
101
+ except (OSError, IOError):
102
+ pass
103
+ return []
104
+
105
+
106
+ # =============================================================================
107
+ # Worktree Configuration
108
+ # =============================================================================
109
+
110
+ # Worktree config file relative path (relative to repo root)
111
+ WORKTREE_CONFIG_PATH = f"{DIR_WORKFLOW}/worktree.yaml"
112
+
113
+
114
+ def get_worktree_config(repo_root: Path | None = None) -> Path:
115
+ """Get worktree.yaml config file path.
116
+
117
+ Args:
118
+ repo_root: Repository root path. Defaults to auto-detected.
119
+
120
+ Returns:
121
+ Absolute path to config file.
122
+ """
123
+ if repo_root is None:
124
+ repo_root = get_repo_root()
125
+ return repo_root / WORKTREE_CONFIG_PATH
126
+
127
+
128
+ def get_worktree_base_dir(repo_root: Path | None = None) -> Path:
129
+ """Get worktree base directory.
130
+
131
+ Args:
132
+ repo_root: Repository root path. Defaults to auto-detected.
133
+
134
+ Returns:
135
+ Absolute path to worktree base directory.
136
+ """
137
+ if repo_root is None:
138
+ repo_root = get_repo_root()
139
+
140
+ config = get_worktree_config(repo_root)
141
+ worktree_dir = _yaml_get_value(config, "worktree_dir")
142
+
143
+ # Default value
144
+ if not worktree_dir:
145
+ worktree_dir = "../worktrees"
146
+
147
+ # Handle relative path
148
+ if worktree_dir.startswith("../") or worktree_dir.startswith("./"):
149
+ # Relative to repo_root
150
+ return repo_root / worktree_dir
151
+ else:
152
+ # Absolute path
153
+ return Path(worktree_dir)
154
+
155
+
156
+ def get_worktree_copy_files(repo_root: Path | None = None) -> list[str]:
157
+ """Get files to copy list.
158
+
159
+ Args:
160
+ repo_root: Repository root path. Defaults to auto-detected.
161
+
162
+ Returns:
163
+ List of file paths to copy.
164
+ """
165
+ if repo_root is None:
166
+ repo_root = get_repo_root()
167
+ config = get_worktree_config(repo_root)
168
+ return _yaml_get_list(config, "copy")
169
+
170
+
171
+ def get_worktree_post_create_hooks(repo_root: Path | None = None) -> list[str]:
172
+ """Get post_create hooks.
173
+
174
+ Args:
175
+ repo_root: Repository root path. Defaults to auto-detected.
176
+
177
+ Returns:
178
+ List of commands to run.
179
+ """
180
+ if repo_root is None:
181
+ repo_root = get_repo_root()
182
+ config = get_worktree_config(repo_root)
183
+ return _yaml_get_list(config, "post_create")
184
+
185
+
186
+ # =============================================================================
187
+ # Agents Registry
188
+ # =============================================================================
189
+
190
+ def get_agents_dir(repo_root: Path | None = None) -> Path | None:
191
+ """Get agents directory for current developer.
192
+
193
+ Args:
194
+ repo_root: Repository root path. Defaults to auto-detected.
195
+
196
+ Returns:
197
+ Absolute path to agents directory, or None if no workspace.
198
+ """
199
+ if repo_root is None:
200
+ repo_root = get_repo_root()
201
+
202
+ workspace_dir = get_workspace_dir(repo_root)
203
+ if workspace_dir:
204
+ return workspace_dir / ".agents"
205
+ return None
206
+
207
+
208
+ # =============================================================================
209
+ # Main Entry (for testing)
210
+ # =============================================================================
211
+
212
+ if __name__ == "__main__":
213
+ repo = get_repo_root()
214
+ print(f"Repository root: {repo}")
215
+ print(f"Worktree config: {get_worktree_config(repo)}")
216
+ print(f"Worktree base dir: {get_worktree_base_dir(repo)}")
217
+ print(f"Copy files: {get_worktree_copy_files(repo)}")
218
+ print(f"Post create hooks: {get_worktree_post_create_hooks(repo)}")
219
+ print(f"Agents dir: {get_agents_dir(repo)}")
@@ -0,0 +1,290 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Create Bootstrap Task for First-Time Setup.
4
+
5
+ Creates a guided task to help users fill in project guidelines
6
+ after initializing AIM Studio for the first time.
7
+
8
+ Usage:
9
+ python3 create_bootstrap.py [project-type]
10
+
11
+ Arguments:
12
+ project-type: frontend | backend | fullstack (default: fullstack)
13
+
14
+ Prerequisites:
15
+ - .aim-studio/.developer must exist (run init_developer.py first)
16
+
17
+ Creates:
18
+ .aim-studio/tasks/00-bootstrap-guidelines/
19
+ - task.json # Task metadata
20
+ - prd.md # Task description and guidance
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import json
26
+ import sys
27
+ from datetime import datetime
28
+ from pathlib import Path
29
+
30
+ from common.paths import (
31
+ DIR_WORKFLOW,
32
+ DIR_SCRIPTS,
33
+ DIR_TASKS,
34
+ get_repo_root,
35
+ get_developer,
36
+ get_tasks_dir,
37
+ set_current_task,
38
+ )
39
+
40
+
41
+ # =============================================================================
42
+ # Constants
43
+ # =============================================================================
44
+
45
+ TASK_NAME = "00-bootstrap-guidelines"
46
+
47
+
48
+ # =============================================================================
49
+ # PRD Content
50
+ # =============================================================================
51
+
52
+ def write_prd_header() -> str:
53
+ """Write PRD header section."""
54
+ return """# Bootstrap: Fill Project Development Guidelines
55
+
56
+ ## Purpose
57
+
58
+ Welcome to AIM Studio! This is your first task.
59
+
60
+ AI agents use `.aim-studio/spec/` to understand YOUR project's coding conventions.
61
+ **Empty templates = AI writes generic code that doesn't match your project style.**
62
+
63
+ Filling these guidelines is a one-time setup that pays off for every future AI session.
64
+
65
+ ---
66
+
67
+ ## Your Task
68
+
69
+ Fill in the guideline files based on your **existing codebase**.
70
+ """
71
+
72
+
73
+ def write_prd_backend_section() -> str:
74
+ """Write PRD backend section."""
75
+ return """
76
+
77
+ ### Backend Guidelines
78
+
79
+ | File | What to Document |
80
+ |------|------------------|
81
+ | `.aim-studio/spec/backend/directory-structure.md` | Where different file types go (routes, services, utils) |
82
+ | `.aim-studio/spec/backend/database-guidelines.md` | ORM, migrations, query patterns, naming conventions |
83
+ | `.aim-studio/spec/backend/error-handling.md` | How errors are caught, logged, and returned |
84
+ | `.aim-studio/spec/backend/logging-guidelines.md` | Log levels, format, what to log |
85
+ | `.aim-studio/spec/backend/quality-guidelines.md` | Code review standards, testing requirements |
86
+ """
87
+
88
+
89
+ def write_prd_frontend_section() -> str:
90
+ """Write PRD frontend section."""
91
+ return """
92
+
93
+ ### Frontend Guidelines
94
+
95
+ | File | What to Document |
96
+ |------|------------------|
97
+ | `.aim-studio/spec/frontend/directory-structure.md` | Component/page/hook organization |
98
+ | `.aim-studio/spec/frontend/component-guidelines.md` | Component patterns, props conventions |
99
+ | `.aim-studio/spec/frontend/hook-guidelines.md` | Custom hook naming, patterns |
100
+ | `.aim-studio/spec/frontend/state-management.md` | State library, patterns, what goes where |
101
+ | `.aim-studio/spec/frontend/type-safety.md` | TypeScript conventions, type organization |
102
+ | `.aim-studio/spec/frontend/quality-guidelines.md` | Linting, testing, accessibility |
103
+ """
104
+
105
+
106
+ def write_prd_footer() -> str:
107
+ """Write PRD footer section."""
108
+ return """
109
+
110
+ ### Thinking Guides (Optional)
111
+
112
+ The `.aim-studio/spec/guides/` directory contains thinking guides that are already
113
+ filled with general best practices. You can customize them for your project if needed.
114
+
115
+ ---
116
+
117
+ ## How to Fill Guidelines
118
+
119
+ ### Principle: Document Reality, Not Ideals
120
+
121
+ Write what your codebase **actually does**, not what you wish it did.
122
+ AI needs to match existing patterns, not introduce new ones.
123
+
124
+ ### Steps
125
+
126
+ 1. **Look at existing code** - Find 2-3 examples of each pattern
127
+ 2. **Document the pattern** - Describe what you see
128
+ 3. **Include file paths** - Reference real files as examples
129
+ 4. **List anti-patterns** - What does your team avoid?
130
+
131
+ ---
132
+
133
+ ## Tips for Using AI
134
+
135
+ Ask AI to help analyze your codebase:
136
+
137
+ - "Look at my codebase and document the patterns you see"
138
+ - "Analyze my code structure and summarize the conventions"
139
+ - "Find error handling patterns and document them"
140
+
141
+ The AI will read your code and help you document it.
142
+
143
+ ---
144
+
145
+ ## Completion Checklist
146
+
147
+ - [ ] Guidelines filled for your project type
148
+ - [ ] At least 2-3 real code examples in each guideline
149
+ - [ ] Anti-patterns documented
150
+
151
+ When done:
152
+
153
+ ```bash
154
+ python3 ./.aim-studio/scripts/task.py finish
155
+ python3 ./.aim-studio/scripts/task.py archive 00-bootstrap-guidelines
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Why This Matters
161
+
162
+ After completing this task:
163
+
164
+ 1. AI will write code that matches your project style
165
+ 2. Relevant `/trellis:before-*-dev` commands will inject real context
166
+ 3. `/trellis:check-*` commands will validate against your actual standards
167
+ 4. Future developers (human or AI) will onboard faster
168
+ """
169
+
170
+
171
+ def write_prd(task_dir: Path, project_type: str) -> None:
172
+ """Write prd.md file."""
173
+ content = write_prd_header()
174
+
175
+ if project_type == "frontend":
176
+ content += write_prd_frontend_section()
177
+ elif project_type == "backend":
178
+ content += write_prd_backend_section()
179
+ else: # fullstack
180
+ content += write_prd_backend_section()
181
+ content += write_prd_frontend_section()
182
+
183
+ content += write_prd_footer()
184
+
185
+ prd_file = task_dir / "prd.md"
186
+ prd_file.write_text(content, encoding="utf-8")
187
+
188
+
189
+ # =============================================================================
190
+ # Task JSON
191
+ # =============================================================================
192
+
193
+ def write_task_json(task_dir: Path, developer: str, project_type: str) -> None:
194
+ """Write task.json file."""
195
+ today = datetime.now().strftime("%Y-%m-%d")
196
+
197
+ # Generate subtasks and related files based on project type
198
+ if project_type == "frontend":
199
+ subtasks = [
200
+ {"name": "Fill frontend guidelines", "status": "pending"},
201
+ {"name": "Add code examples", "status": "pending"},
202
+ ]
203
+ related_files = [".aim-studio/spec/frontend/"]
204
+ elif project_type == "backend":
205
+ subtasks = [
206
+ {"name": "Fill backend guidelines", "status": "pending"},
207
+ {"name": "Add code examples", "status": "pending"},
208
+ ]
209
+ related_files = [".aim-studio/spec/backend/"]
210
+ else: # fullstack
211
+ subtasks = [
212
+ {"name": "Fill backend guidelines", "status": "pending"},
213
+ {"name": "Fill frontend guidelines", "status": "pending"},
214
+ {"name": "Add code examples", "status": "pending"},
215
+ ]
216
+ related_files = [".aim-studio/spec/backend/", ".aim-studio/spec/frontend/"]
217
+
218
+ task_data = {
219
+ "id": TASK_NAME,
220
+ "name": "Bootstrap Guidelines",
221
+ "description": "Fill in project development guidelines for AI agents",
222
+ "status": "in_progress",
223
+ "dev_type": "docs",
224
+ "priority": "P1",
225
+ "creator": developer,
226
+ "assignee": developer,
227
+ "createdAt": today,
228
+ "completedAt": None,
229
+ "commit": None,
230
+ "subtasks": subtasks,
231
+ "relatedFiles": related_files,
232
+ "notes": f"First-time setup task created by trellis init ({project_type} project)",
233
+ }
234
+
235
+ task_json = task_dir / "task.json"
236
+ task_json.write_text(json.dumps(task_data, indent=2, ensure_ascii=False), encoding="utf-8")
237
+
238
+
239
+ # =============================================================================
240
+ # Main
241
+ # =============================================================================
242
+
243
+ def main() -> int:
244
+ """Main entry point."""
245
+ # Parse project type argument
246
+ project_type = "fullstack"
247
+ if len(sys.argv) > 1:
248
+ project_type = sys.argv[1]
249
+
250
+ # Validate project type
251
+ if project_type not in ("frontend", "backend", "fullstack"):
252
+ print(f"Unknown project type: {project_type}, defaulting to fullstack")
253
+ project_type = "fullstack"
254
+
255
+ repo_root = get_repo_root()
256
+ developer = get_developer(repo_root)
257
+
258
+ # Check developer initialized
259
+ if not developer:
260
+ print("Error: Developer not initialized")
261
+ print(f"Run: python3 ./{DIR_WORKFLOW}/{DIR_SCRIPTS}/init_developer.py <your-name>")
262
+ return 1
263
+
264
+ tasks_dir = get_tasks_dir(repo_root)
265
+ task_dir = tasks_dir / TASK_NAME
266
+ relative_path = f"{DIR_WORKFLOW}/{DIR_TASKS}/{TASK_NAME}"
267
+
268
+ # Check if already exists
269
+ if task_dir.exists():
270
+ print(f"Bootstrap task already exists: {relative_path}")
271
+ return 0
272
+
273
+ # Create task directory
274
+ task_dir.mkdir(parents=True, exist_ok=True)
275
+
276
+ # Write files
277
+ write_task_json(task_dir, developer, project_type)
278
+ write_prd(task_dir, project_type)
279
+
280
+ # Set as current task
281
+ set_current_task(relative_path, repo_root)
282
+
283
+ # Silent output - init command handles user-facing messages
284
+ # Only output the task path for programmatic use
285
+ print(relative_path)
286
+ return 0
287
+
288
+
289
+ if __name__ == "__main__":
290
+ sys.exit(main())
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Get Session Context for AI Agent.
4
+
5
+ Usage:
6
+ python3 get_context.py Output context in text format
7
+ python3 get_context.py --json Output context in JSON format
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from common.git_context import main
13
+
14
+
15
+ if __name__ == "__main__":
16
+ main()