@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,392 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Add a new session to journal file and update index.md.
5
+
6
+ Usage:
7
+ python3 add_session.py --title "Title" --commit "hash" --summary "Summary"
8
+ echo "content" | python3 add_session.py --title "Title" --commit "hash"
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import sys
14
+
15
+ # IMPORTANT: Force stdout to use UTF-8 on Windows
16
+ # This fixes UnicodeEncodeError when outputting non-ASCII characters
17
+ if sys.platform == "win32":
18
+ import io as _io
19
+ if hasattr(sys.stdout, "reconfigure"):
20
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
21
+ elif hasattr(sys.stdout, "detach"):
22
+ sys.stdout = _io.TextIOWrapper(sys.stdout.detach(), encoding="utf-8", errors="replace") # type: ignore[union-attr]
23
+
24
+ import argparse
25
+ import re
26
+ import sys
27
+ from datetime import datetime
28
+ from pathlib import Path
29
+
30
+ from common.paths import (
31
+ FILE_JOURNAL_PREFIX,
32
+ get_repo_root,
33
+ get_developer,
34
+ get_workspace_dir,
35
+ )
36
+ from common.developer import ensure_developer
37
+
38
+
39
+ MAX_LINES = 2000
40
+
41
+
42
+ # =============================================================================
43
+ # Helper Functions
44
+ # =============================================================================
45
+
46
+ def get_latest_journal_info(dev_dir: Path) -> tuple[Path | None, int, int]:
47
+ """Get latest journal file info.
48
+
49
+ Returns:
50
+ Tuple of (file_path, file_number, line_count).
51
+ """
52
+ latest_file: Path | None = None
53
+ latest_num = -1
54
+
55
+ for f in dev_dir.glob(f"{FILE_JOURNAL_PREFIX}*.md"):
56
+ if not f.is_file():
57
+ continue
58
+
59
+ match = re.search(r"(\d+)$", f.stem)
60
+ if match:
61
+ num = int(match.group(1))
62
+ if num > latest_num:
63
+ latest_num = num
64
+ latest_file = f
65
+
66
+ if latest_file:
67
+ lines = len(latest_file.read_text(encoding="utf-8").splitlines())
68
+ return latest_file, latest_num, lines
69
+
70
+ return None, 0, 0
71
+
72
+
73
+ def get_current_session(index_file: Path) -> int:
74
+ """Get current session number from index.md."""
75
+ if not index_file.is_file():
76
+ return 0
77
+
78
+ content = index_file.read_text(encoding="utf-8")
79
+ for line in content.splitlines():
80
+ if "Total Sessions" in line:
81
+ match = re.search(r":\s*(\d+)", line)
82
+ if match:
83
+ return int(match.group(1))
84
+ return 0
85
+
86
+
87
+ def _extract_journal_num(filename: str) -> int:
88
+ """Extract journal number from filename for sorting."""
89
+ match = re.search(r"(\d+)", filename)
90
+ return int(match.group(1)) if match else 0
91
+
92
+
93
+ def count_journal_files(dev_dir: Path, active_num: int) -> str:
94
+ """Count journal files and return table rows."""
95
+ active_file = f"{FILE_JOURNAL_PREFIX}{active_num}.md"
96
+ result_lines = []
97
+
98
+ files = sorted(
99
+ [f for f in dev_dir.glob(f"{FILE_JOURNAL_PREFIX}*.md") if f.is_file()],
100
+ key=lambda f: _extract_journal_num(f.stem),
101
+ reverse=True
102
+ )
103
+
104
+ for f in files:
105
+ filename = f.name
106
+ lines = len(f.read_text(encoding="utf-8").splitlines())
107
+ status = "Active" if filename == active_file else "Archived"
108
+ result_lines.append(f"| `{filename}` | ~{lines} | {status} |")
109
+
110
+ return "\n".join(result_lines)
111
+
112
+
113
+ def create_new_journal_file(dev_dir: Path, num: int, developer: str, today: str) -> Path:
114
+ """Create a new journal file."""
115
+ prev_num = num - 1
116
+ new_file = dev_dir / f"{FILE_JOURNAL_PREFIX}{num}.md"
117
+
118
+ content = f"""# Journal - {developer} (Part {num})
119
+
120
+ > Continuation from `{FILE_JOURNAL_PREFIX}{prev_num}.md` (archived at ~{MAX_LINES} lines)
121
+ > Started: {today}
122
+
123
+ ---
124
+
125
+ """
126
+ new_file.write_text(content, encoding="utf-8")
127
+ return new_file
128
+
129
+
130
+ def generate_session_content(
131
+ session_num: int,
132
+ title: str,
133
+ commit: str,
134
+ summary: str,
135
+ extra_content: str,
136
+ today: str
137
+ ) -> str:
138
+ """Generate session content."""
139
+ if commit and commit != "-":
140
+ commit_table = """| Hash | Message |
141
+ |------|---------|"""
142
+ for c in commit.split(","):
143
+ c = c.strip()
144
+ commit_table += f"\n| `{c}` | (see git log) |"
145
+ else:
146
+ commit_table = "(No commits - planning session)"
147
+
148
+ return f"""
149
+
150
+ ## Session {session_num}: {title}
151
+
152
+ **Date**: {today}
153
+ **Task**: {title}
154
+
155
+ ### Summary
156
+
157
+ {summary}
158
+
159
+ ### Main Changes
160
+
161
+ {extra_content}
162
+
163
+ ### Git Commits
164
+
165
+ {commit_table}
166
+
167
+ ### Testing
168
+
169
+ - [OK] (Add test results)
170
+
171
+ ### Status
172
+
173
+ [OK] **Completed**
174
+
175
+ ### Next Steps
176
+
177
+ - None - task complete
178
+ """
179
+
180
+
181
+ def update_index(
182
+ index_file: Path,
183
+ dev_dir: Path,
184
+ title: str,
185
+ commit: str,
186
+ new_session: int,
187
+ active_file: str,
188
+ today: str
189
+ ) -> bool:
190
+ """Update index.md with new session info."""
191
+ # Format commit for display
192
+ commit_display = "-"
193
+ if commit and commit != "-":
194
+ commit_display = re.sub(r"([a-f0-9]{7,})", r"`\1`", commit.replace(",", ", "))
195
+
196
+ # Get file number from active_file name
197
+ match = re.search(r"(\d+)", active_file)
198
+ active_num = int(match.group(1)) if match else 0
199
+ files_table = count_journal_files(dev_dir, active_num)
200
+
201
+ print(f"Updating index.md for session {new_session}...")
202
+ print(f" Title: {title}")
203
+ print(f" Commit: {commit_display}")
204
+ print(f" Active File: {active_file}")
205
+ print()
206
+
207
+ content = index_file.read_text(encoding="utf-8")
208
+
209
+ if "@@@auto:current-status" not in content:
210
+ print("Error: Markers not found in index.md. Please ensure markers exist.", file=sys.stderr)
211
+ return False
212
+
213
+ # Process sections
214
+ lines = content.splitlines()
215
+ new_lines = []
216
+
217
+ in_current_status = False
218
+ in_active_documents = False
219
+ in_session_history = False
220
+ header_written = False
221
+
222
+ for line in lines:
223
+ if "@@@auto:current-status" in line:
224
+ new_lines.append(line)
225
+ in_current_status = True
226
+ new_lines.append(f"- **Active File**: `{active_file}`")
227
+ new_lines.append(f"- **Total Sessions**: {new_session}")
228
+ new_lines.append(f"- **Last Active**: {today}")
229
+ continue
230
+
231
+ if "@@@/auto:current-status" in line:
232
+ in_current_status = False
233
+ new_lines.append(line)
234
+ continue
235
+
236
+ if "@@@auto:active-documents" in line:
237
+ new_lines.append(line)
238
+ in_active_documents = True
239
+ new_lines.append("| File | Lines | Status |")
240
+ new_lines.append("|------|-------|--------|")
241
+ new_lines.append(files_table)
242
+ continue
243
+
244
+ if "@@@/auto:active-documents" in line:
245
+ in_active_documents = False
246
+ new_lines.append(line)
247
+ continue
248
+
249
+ if "@@@auto:session-history" in line:
250
+ new_lines.append(line)
251
+ in_session_history = True
252
+ header_written = False
253
+ continue
254
+
255
+ if "@@@/auto:session-history" in line:
256
+ in_session_history = False
257
+ new_lines.append(line)
258
+ continue
259
+
260
+ if in_current_status:
261
+ continue
262
+
263
+ if in_active_documents:
264
+ continue
265
+
266
+ if in_session_history:
267
+ new_lines.append(line)
268
+ if line.startswith("|---") and not header_written:
269
+ new_lines.append(f"| {new_session} | {today} | {title} | {commit_display} |")
270
+ header_written = True
271
+ continue
272
+
273
+ new_lines.append(line)
274
+
275
+ index_file.write_text("\n".join(new_lines), encoding="utf-8")
276
+ print("[OK] Updated index.md successfully!")
277
+ return True
278
+
279
+
280
+ # =============================================================================
281
+ # Main Function
282
+ # =============================================================================
283
+
284
+ def add_session(
285
+ title: str,
286
+ commit: str = "-",
287
+ summary: str = "(Add summary)",
288
+ extra_content: str = "(Add details)"
289
+ ) -> int:
290
+ """Add a new session."""
291
+ repo_root = get_repo_root()
292
+ ensure_developer(repo_root)
293
+
294
+ developer = get_developer(repo_root)
295
+ if not developer:
296
+ print("Error: Developer not initialized", file=sys.stderr)
297
+ return 1
298
+
299
+ dev_dir = get_workspace_dir(repo_root)
300
+ if not dev_dir:
301
+ print("Error: Workspace directory not found", file=sys.stderr)
302
+ return 1
303
+
304
+ index_file = dev_dir / "index.md"
305
+ today = datetime.now().strftime("%Y-%m-%d")
306
+
307
+ journal_file, current_num, current_lines = get_latest_journal_info(dev_dir)
308
+ current_session = get_current_session(index_file)
309
+ new_session = current_session + 1
310
+
311
+ session_content = generate_session_content(
312
+ new_session, title, commit, summary, extra_content, today
313
+ )
314
+ content_lines = len(session_content.splitlines())
315
+
316
+ print("========================================", file=sys.stderr)
317
+ print("ADD SESSION", file=sys.stderr)
318
+ print("========================================", file=sys.stderr)
319
+ print("", file=sys.stderr)
320
+ print(f"Session: {new_session}", file=sys.stderr)
321
+ print(f"Title: {title}", file=sys.stderr)
322
+ print(f"Commit: {commit}", file=sys.stderr)
323
+ print("", file=sys.stderr)
324
+ print(f"Current journal file: {FILE_JOURNAL_PREFIX}{current_num}.md", file=sys.stderr)
325
+ print(f"Current lines: {current_lines}", file=sys.stderr)
326
+ print(f"New content lines: {content_lines}", file=sys.stderr)
327
+ print(f"Total after append: {current_lines + content_lines}", file=sys.stderr)
328
+ print("", file=sys.stderr)
329
+
330
+ target_file = journal_file
331
+ target_num = current_num
332
+
333
+ if current_lines + content_lines > MAX_LINES:
334
+ target_num = current_num + 1
335
+ print(f"[!] Exceeds {MAX_LINES} lines, creating {FILE_JOURNAL_PREFIX}{target_num}.md", file=sys.stderr)
336
+ target_file = create_new_journal_file(dev_dir, target_num, developer, today)
337
+ print(f"Created: {target_file}", file=sys.stderr)
338
+
339
+ # Append session content
340
+ if target_file:
341
+ with target_file.open("a", encoding="utf-8") as f:
342
+ f.write(session_content)
343
+ print(f"[OK] Appended session to {target_file.name}", file=sys.stderr)
344
+
345
+ print("", file=sys.stderr)
346
+
347
+ # Update index.md
348
+ active_file = f"{FILE_JOURNAL_PREFIX}{target_num}.md"
349
+ if not update_index(index_file, dev_dir, title, commit, new_session, active_file, today):
350
+ return 1
351
+
352
+ print("", file=sys.stderr)
353
+ print("========================================", file=sys.stderr)
354
+ print(f"[OK] Session {new_session} added successfully!", file=sys.stderr)
355
+ print("========================================", file=sys.stderr)
356
+ print("", file=sys.stderr)
357
+ print("Files updated:", file=sys.stderr)
358
+ print(f" - {target_file.name if target_file else 'journal'}", file=sys.stderr)
359
+ print(" - index.md", file=sys.stderr)
360
+
361
+ return 0
362
+
363
+
364
+ # =============================================================================
365
+ # Main Entry
366
+ # =============================================================================
367
+
368
+ def main() -> int:
369
+ """CLI entry point."""
370
+ parser = argparse.ArgumentParser(
371
+ description="Add a new session to journal file and update index.md"
372
+ )
373
+ parser.add_argument("--title", required=True, help="Session title")
374
+ parser.add_argument("--commit", default="-", help="Comma-separated commit hashes")
375
+ parser.add_argument("--summary", default="(Add summary)", help="Brief summary")
376
+ parser.add_argument("--content-file", help="Path to file with detailed content")
377
+
378
+ args = parser.parse_args()
379
+
380
+ extra_content = "(Add details)"
381
+ if args.content_file:
382
+ content_path = Path(args.content_file)
383
+ if content_path.is_file():
384
+ extra_content = content_path.read_text(encoding="utf-8")
385
+ elif not sys.stdin.isatty():
386
+ extra_content = sys.stdin.read()
387
+
388
+ return add_session(args.title, args.commit, args.summary, extra_content)
389
+
390
+
391
+ if __name__ == "__main__":
392
+ sys.exit(main())
@@ -0,0 +1,80 @@
1
+ """
2
+ Common utilities for AIM Studio workflow scripts.
3
+
4
+ This module provides shared functionality used by other AIM Studio scripts.
5
+ """
6
+
7
+ import io
8
+ import sys
9
+
10
+ # =============================================================================
11
+ # Windows Encoding Fix (MUST be at top, before any other output)
12
+ # =============================================================================
13
+ # On Windows, stdout defaults to the system code page (often GBK/CP936).
14
+ # This causes UnicodeEncodeError when printing non-ASCII characters.
15
+ #
16
+ # Any script that imports from common will automatically get this fix.
17
+ # =============================================================================
18
+
19
+
20
+ def _configure_stream(stream: object) -> object:
21
+ """Configure a stream for UTF-8 encoding on Windows."""
22
+ # Try reconfigure() first (Python 3.7+, more reliable)
23
+ if hasattr(stream, "reconfigure"):
24
+ stream.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
25
+ return stream
26
+ # Fallback: detach and rewrap with TextIOWrapper
27
+ elif hasattr(stream, "detach"):
28
+ return io.TextIOWrapper(
29
+ stream.detach(), # type: ignore[union-attr]
30
+ encoding="utf-8",
31
+ errors="replace",
32
+ )
33
+ return stream
34
+
35
+
36
+ if sys.platform == "win32":
37
+ sys.stdout = _configure_stream(sys.stdout) # type: ignore[assignment]
38
+ sys.stderr = _configure_stream(sys.stderr) # type: ignore[assignment]
39
+
40
+
41
+ def configure_encoding() -> None:
42
+ """
43
+ Configure stdout/stderr for UTF-8 encoding on Windows.
44
+
45
+ This is automatically called when importing from common,
46
+ but can be called manually for scripts that don't import common.
47
+
48
+ Safe to call multiple times.
49
+ """
50
+ global sys
51
+ if sys.platform == "win32":
52
+ sys.stdout = _configure_stream(sys.stdout) # type: ignore[assignment]
53
+ sys.stderr = _configure_stream(sys.stderr) # type: ignore[assignment]
54
+
55
+
56
+ from .paths import (
57
+ DIR_WORKFLOW,
58
+ DIR_WORKSPACE,
59
+ DIR_TASKS,
60
+ DIR_ARCHIVE,
61
+ DIR_SPEC,
62
+ DIR_SCRIPTS,
63
+ FILE_DEVELOPER,
64
+ FILE_CURRENT_TASK,
65
+ FILE_TASK_JSON,
66
+ FILE_JOURNAL_PREFIX,
67
+ get_repo_root,
68
+ get_developer,
69
+ check_developer,
70
+ get_tasks_dir,
71
+ get_workspace_dir,
72
+ get_active_journal_file,
73
+ count_lines,
74
+ get_current_task,
75
+ get_current_task_abs,
76
+ set_current_task,
77
+ clear_current_task,
78
+ has_current_task,
79
+ generate_task_date_prefix,
80
+ )