@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,1275 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import chalk from "chalk";
4
+ import inquirer from "inquirer";
5
+ import { PATHS, DIR_NAMES } from "../constants/paths.js";
6
+ import { VERSION, PACKAGE_NAME } from "../constants/version.js";
7
+ import { getMigrationsForVersion, getAllMigrations, getMigrationMetadata, } from "../migrations/index.js";
8
+ import { loadHashes, saveHashes, updateHashes, isTemplateModified, removeHash, renameHash, computeHash, } from "../utils/template-hash.js";
9
+ import { compareVersions } from "../utils/compare-versions.js";
10
+ // Import templates for comparison
11
+ import {
12
+ // Python scripts - package init
13
+ scriptsInit,
14
+ // Python scripts - common
15
+ commonInit, commonPaths, commonDeveloper, commonGitContext, commonWorktree, commonTaskQueue, commonTaskUtils, commonPhase, commonRegistry, commonCliAdapter,
16
+ // Python scripts - multi_agent
17
+ multiAgentInit, multiAgentStart, multiAgentCleanup, multiAgentStatus, multiAgentCreatePr, multiAgentPlan,
18
+ // Python scripts - main
19
+ getDeveloperScript, initDeveloperScript, taskScript, getContextScript, addSessionScript, createBootstrapScript,
20
+ // Configuration
21
+ worktreeYamlTemplate, workflowMdTemplate, gitignoreTemplate, } from "../templates/aim/index.js";
22
+ import { guidesIndexContent, guidesCrossLayerThinkingGuideContent, guidesCodeReuseThinkingGuideContent,
23
+ // Backend structure (multi-doc)
24
+ backendIndexContent, backendDirectoryStructureContent, backendDatabaseGuidelinesContent, backendLoggingGuidelinesContent, backendQualityGuidelinesContent, backendErrorHandlingContent,
25
+ // Frontend structure (multi-doc)
26
+ frontendIndexContent, frontendDirectoryStructureContent, frontendTypeSafetyContent, frontendHookGuidelinesContent, frontendComponentGuidelinesContent, frontendQualityGuidelinesContent, frontendStateManagementContent,
27
+ // Workspace
28
+ workspaceIndexContent, } from "../templates/markdown/index.js";
29
+ import { ALL_MANAGED_DIRS, getConfiguredPlatforms, collectPlatformTemplates, isManagedPath, isManagedRootDir, } from "../configurators/index.js";
30
+ // Paths that should never be touched (true user data)
31
+ // Note: frontend/backend spec dirs removed - they should be created if missing,
32
+ // and existing files are protected by hash-based modification tracking
33
+ const PROTECTED_PATHS = [
34
+ `${DIR_NAMES.WORKFLOW}/${DIR_NAMES.WORKSPACE}`, // workspace/
35
+ `${DIR_NAMES.WORKFLOW}/${DIR_NAMES.TASKS}`, // tasks/
36
+ `${DIR_NAMES.WORKFLOW}/.developer`,
37
+ `${DIR_NAMES.WORKFLOW}/.current-task`,
38
+ ];
39
+ /**
40
+ * Collect all template files that should be managed by update
41
+ * Only collects templates for platforms that are already configured (have directories)
42
+ */
43
+ function collectTemplateFiles(cwd) {
44
+ const files = new Map();
45
+ const platforms = getConfiguredPlatforms(cwd);
46
+ // Python scripts - package init
47
+ files.set(`${PATHS.SCRIPTS}/__init__.py`, scriptsInit);
48
+ // Python scripts - common
49
+ files.set(`${PATHS.SCRIPTS}/common/__init__.py`, commonInit);
50
+ files.set(`${PATHS.SCRIPTS}/common/paths.py`, commonPaths);
51
+ files.set(`${PATHS.SCRIPTS}/common/developer.py`, commonDeveloper);
52
+ files.set(`${PATHS.SCRIPTS}/common/git_context.py`, commonGitContext);
53
+ files.set(`${PATHS.SCRIPTS}/common/worktree.py`, commonWorktree);
54
+ files.set(`${PATHS.SCRIPTS}/common/task_queue.py`, commonTaskQueue);
55
+ files.set(`${PATHS.SCRIPTS}/common/task_utils.py`, commonTaskUtils);
56
+ files.set(`${PATHS.SCRIPTS}/common/phase.py`, commonPhase);
57
+ files.set(`${PATHS.SCRIPTS}/common/registry.py`, commonRegistry);
58
+ files.set(`${PATHS.SCRIPTS}/common/cli_adapter.py`, commonCliAdapter);
59
+ // Python scripts - multi_agent
60
+ files.set(`${PATHS.SCRIPTS}/multi_agent/__init__.py`, multiAgentInit);
61
+ files.set(`${PATHS.SCRIPTS}/multi_agent/start.py`, multiAgentStart);
62
+ files.set(`${PATHS.SCRIPTS}/multi_agent/cleanup.py`, multiAgentCleanup);
63
+ files.set(`${PATHS.SCRIPTS}/multi_agent/status.py`, multiAgentStatus);
64
+ files.set(`${PATHS.SCRIPTS}/multi_agent/create_pr.py`, multiAgentCreatePr);
65
+ files.set(`${PATHS.SCRIPTS}/multi_agent/plan.py`, multiAgentPlan);
66
+ // Python scripts - main
67
+ files.set(`${PATHS.SCRIPTS}/init_developer.py`, initDeveloperScript);
68
+ files.set(`${PATHS.SCRIPTS}/get_developer.py`, getDeveloperScript);
69
+ files.set(`${PATHS.SCRIPTS}/task.py`, taskScript);
70
+ files.set(`${PATHS.SCRIPTS}/get_context.py`, getContextScript);
71
+ files.set(`${PATHS.SCRIPTS}/add_session.py`, addSessionScript);
72
+ files.set(`${PATHS.SCRIPTS}/create_bootstrap.py`, createBootstrapScript);
73
+ // Configuration
74
+ files.set(`${DIR_NAMES.WORKFLOW}/worktree.yaml`, worktreeYamlTemplate);
75
+ files.set(`${DIR_NAMES.WORKFLOW}/.gitignore`, gitignoreTemplate);
76
+ files.set(PATHS.WORKFLOW_GUIDE_FILE, workflowMdTemplate);
77
+ // Workspace index (template file, not user data)
78
+ files.set(`${PATHS.WORKSPACE}/index.md`, workspaceIndexContent);
79
+ // Spec - guides
80
+ files.set(`${PATHS.SPEC}/guides/index.md`, guidesIndexContent);
81
+ files.set(`${PATHS.SPEC}/guides/cross-layer-thinking-guide.md`, guidesCrossLayerThinkingGuideContent);
82
+ files.set(`${PATHS.SPEC}/guides/code-reuse-thinking-guide.md`, guidesCodeReuseThinkingGuideContent);
83
+ // Spec - backend (created if missing, protected by hash tracking if modified)
84
+ files.set(`${PATHS.SPEC}/backend/index.md`, backendIndexContent);
85
+ files.set(`${PATHS.SPEC}/backend/directory-structure.md`, backendDirectoryStructureContent);
86
+ files.set(`${PATHS.SPEC}/backend/database-guidelines.md`, backendDatabaseGuidelinesContent);
87
+ files.set(`${PATHS.SPEC}/backend/logging-guidelines.md`, backendLoggingGuidelinesContent);
88
+ files.set(`${PATHS.SPEC}/backend/quality-guidelines.md`, backendQualityGuidelinesContent);
89
+ files.set(`${PATHS.SPEC}/backend/error-handling.md`, backendErrorHandlingContent);
90
+ // Spec - frontend (created if missing, protected by hash tracking if modified)
91
+ files.set(`${PATHS.SPEC}/frontend/index.md`, frontendIndexContent);
92
+ files.set(`${PATHS.SPEC}/frontend/directory-structure.md`, frontendDirectoryStructureContent);
93
+ files.set(`${PATHS.SPEC}/frontend/type-safety.md`, frontendTypeSafetyContent);
94
+ files.set(`${PATHS.SPEC}/frontend/hook-guidelines.md`, frontendHookGuidelinesContent);
95
+ files.set(`${PATHS.SPEC}/frontend/component-guidelines.md`, frontendComponentGuidelinesContent);
96
+ files.set(`${PATHS.SPEC}/frontend/quality-guidelines.md`, frontendQualityGuidelinesContent);
97
+ files.set(`${PATHS.SPEC}/frontend/state-management.md`, frontendStateManagementContent);
98
+ // Platform-specific templates (only for configured platforms)
99
+ for (const platformId of platforms) {
100
+ const platformFiles = collectPlatformTemplates(platformId);
101
+ if (platformFiles) {
102
+ for (const [filePath, content] of platformFiles) {
103
+ files.set(filePath, content);
104
+ }
105
+ }
106
+ }
107
+ return files;
108
+ }
109
+ /**
110
+ * Analyze changes between current files and templates
111
+ *
112
+ * Uses hash tracking to distinguish between:
113
+ * - User didn't modify + template same = skip (unchangedFiles)
114
+ * - User didn't modify + template updated = auto-update (autoUpdateFiles)
115
+ * - User modified = needs confirmation (changedFiles)
116
+ */
117
+ function analyzeChanges(cwd, hashes, templates) {
118
+ const result = {
119
+ newFiles: [],
120
+ unchangedFiles: [],
121
+ autoUpdateFiles: [],
122
+ changedFiles: [],
123
+ protectedPaths: PROTECTED_PATHS,
124
+ };
125
+ for (const [relativePath, newContent] of templates) {
126
+ const fullPath = path.join(cwd, relativePath);
127
+ const exists = fs.existsSync(fullPath);
128
+ const change = {
129
+ path: fullPath,
130
+ relativePath,
131
+ newContent,
132
+ status: "new",
133
+ };
134
+ if (!exists) {
135
+ change.status = "new";
136
+ result.newFiles.push(change);
137
+ }
138
+ else {
139
+ const existingContent = fs.readFileSync(fullPath, "utf-8");
140
+ if (existingContent === newContent) {
141
+ // Content same as template - already up to date
142
+ change.status = "unchanged";
143
+ result.unchangedFiles.push(change);
144
+ }
145
+ else {
146
+ // Content differs - check if user modified or template updated
147
+ const storedHash = hashes[relativePath];
148
+ const currentHash = computeHash(existingContent);
149
+ if (storedHash && storedHash === currentHash) {
150
+ // Hash matches stored hash - user didn't modify, template was updated
151
+ // Safe to auto-update
152
+ change.status = "changed";
153
+ result.autoUpdateFiles.push(change);
154
+ }
155
+ else {
156
+ // Hash differs (or no stored hash) - user modified the file
157
+ // Needs confirmation
158
+ change.status = "changed";
159
+ result.changedFiles.push(change);
160
+ }
161
+ }
162
+ }
163
+ }
164
+ return result;
165
+ }
166
+ /**
167
+ * Print change summary
168
+ */
169
+ function printChangeSummary(changes) {
170
+ console.log("\nScanning for changes...\n");
171
+ if (changes.newFiles.length > 0) {
172
+ console.log(chalk.green(" New files (will add):"));
173
+ for (const file of changes.newFiles) {
174
+ console.log(chalk.green(` + ${file.relativePath}`));
175
+ }
176
+ console.log("");
177
+ }
178
+ if (changes.autoUpdateFiles.length > 0) {
179
+ console.log(chalk.cyan(" Template updated (will auto-update):"));
180
+ for (const file of changes.autoUpdateFiles) {
181
+ console.log(chalk.cyan(` ↑ ${file.relativePath}`));
182
+ }
183
+ console.log("");
184
+ }
185
+ if (changes.unchangedFiles.length > 0) {
186
+ console.log(chalk.gray(" Unchanged files (will skip):"));
187
+ for (const file of changes.unchangedFiles.slice(0, 5)) {
188
+ console.log(chalk.gray(` ā—‹ ${file.relativePath}`));
189
+ }
190
+ if (changes.unchangedFiles.length > 5) {
191
+ console.log(chalk.gray(` ... and ${changes.unchangedFiles.length - 5} more`));
192
+ }
193
+ console.log("");
194
+ }
195
+ if (changes.changedFiles.length > 0) {
196
+ console.log(chalk.yellow(" Modified by you (need your decision):"));
197
+ for (const file of changes.changedFiles) {
198
+ console.log(chalk.yellow(` ? ${file.relativePath}`));
199
+ }
200
+ console.log("");
201
+ }
202
+ // Only show protected paths that actually exist
203
+ const existingProtectedPaths = changes.protectedPaths.filter((p) => {
204
+ const fullPath = path.join(process.cwd(), p);
205
+ return fs.existsSync(fullPath);
206
+ });
207
+ if (existingProtectedPaths.length > 0) {
208
+ console.log(chalk.gray(" User data (preserved):"));
209
+ for (const protectedPath of existingProtectedPaths) {
210
+ console.log(chalk.gray(` ā—‹ ${protectedPath}/`));
211
+ }
212
+ console.log("");
213
+ }
214
+ }
215
+ /**
216
+ * Prompt user for conflict resolution
217
+ */
218
+ async function promptConflictResolution(file, options, applyToAll) {
219
+ // If we have a batch action, use it
220
+ if (applyToAll.action) {
221
+ return applyToAll.action;
222
+ }
223
+ // Check command-line options
224
+ if (options.force) {
225
+ return "overwrite";
226
+ }
227
+ if (options.skipAll) {
228
+ return "skip";
229
+ }
230
+ if (options.createNew) {
231
+ return "create-new";
232
+ }
233
+ // Interactive prompt
234
+ const { action } = await inquirer.prompt([
235
+ {
236
+ type: "list",
237
+ name: "action",
238
+ message: `${file.relativePath} has changes.`,
239
+ choices: [
240
+ {
241
+ name: "[1] Overwrite - Replace with new version",
242
+ value: "overwrite",
243
+ },
244
+ { name: "[2] Skip - Keep your current version", value: "skip" },
245
+ {
246
+ name: "[3] Create copy - Save new version as .new",
247
+ value: "create-new",
248
+ },
249
+ { name: "[a] Apply Overwrite to all", value: "overwrite-all" },
250
+ { name: "[s] Apply Skip to all", value: "skip-all" },
251
+ { name: "[n] Apply Create copy to all", value: "create-new-all" },
252
+ ],
253
+ default: "skip",
254
+ },
255
+ ]);
256
+ if (action === "overwrite-all") {
257
+ applyToAll.action = "overwrite";
258
+ return "overwrite";
259
+ }
260
+ if (action === "skip-all") {
261
+ applyToAll.action = "skip";
262
+ return "skip";
263
+ }
264
+ if (action === "create-new-all") {
265
+ applyToAll.action = "create-new";
266
+ return "create-new";
267
+ }
268
+ return action;
269
+ }
270
+ /**
271
+ * Create a timestamped backup directory path
272
+ */
273
+ function createBackupDirPath(cwd) {
274
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
275
+ return path.join(cwd, DIR_NAMES.WORKFLOW, `.backup-${timestamp}`);
276
+ }
277
+ /**
278
+ * Backup a single file to the backup directory
279
+ */
280
+ function backupFile(cwd, backupDir, relativePath) {
281
+ const srcPath = path.join(cwd, relativePath);
282
+ if (!fs.existsSync(srcPath))
283
+ return;
284
+ const backupPath = path.join(backupDir, relativePath);
285
+ fs.mkdirSync(path.dirname(backupPath), { recursive: true });
286
+ fs.copyFileSync(srcPath, backupPath);
287
+ }
288
+ /**
289
+ * Directories to backup as complete snapshot (derived from platform registry)
290
+ */
291
+ const BACKUP_DIRS = ALL_MANAGED_DIRS;
292
+ /**
293
+ * Patterns to exclude from backup (user data that shouldn't be backed up)
294
+ */
295
+ const BACKUP_EXCLUDE_PATTERNS = [
296
+ ".backup-", // Previous backups
297
+ "/workspace/", // Developer workspace (user data)
298
+ "/tasks/", // Task data (user data)
299
+ "/backlog/", // Backlog data (user data)
300
+ "/agent-traces/", // Agent traces (user data, legacy name)
301
+ ];
302
+ /**
303
+ * Check if a path should be excluded from backup
304
+ */
305
+ function shouldExcludeFromBackup(relativePath) {
306
+ for (const pattern of BACKUP_EXCLUDE_PATTERNS) {
307
+ if (relativePath.includes(pattern)) {
308
+ return true;
309
+ }
310
+ }
311
+ return false;
312
+ }
313
+ /**
314
+ * Create complete snapshot backup of all managed directories
315
+ * Backs up .aim-studio, .claude, .cursor, .iflow, .opencode directories entirely
316
+ * (excluding user data like workspace/, tasks/, backlog/)
317
+ */
318
+ function createFullBackup(cwd) {
319
+ const backupDir = createBackupDirPath(cwd);
320
+ let hasFiles = false;
321
+ for (const dir of BACKUP_DIRS) {
322
+ const dirPath = path.join(cwd, dir);
323
+ if (!fs.existsSync(dirPath))
324
+ continue;
325
+ const files = collectAllFiles(dirPath);
326
+ for (const fullPath of files) {
327
+ const relativePath = path.relative(cwd, fullPath);
328
+ // Skip excluded paths
329
+ if (shouldExcludeFromBackup(relativePath))
330
+ continue;
331
+ // Create backup
332
+ if (!hasFiles) {
333
+ fs.mkdirSync(backupDir, { recursive: true });
334
+ hasFiles = true;
335
+ }
336
+ backupFile(cwd, backupDir, relativePath);
337
+ }
338
+ }
339
+ return hasFiles ? backupDir : null;
340
+ }
341
+ /**
342
+ * Update version file
343
+ */
344
+ function updateVersionFile(cwd) {
345
+ const versionPath = path.join(cwd, DIR_NAMES.WORKFLOW, ".version");
346
+ fs.writeFileSync(versionPath, VERSION);
347
+ }
348
+ /**
349
+ * Get current installed version
350
+ */
351
+ function getInstalledVersion(cwd) {
352
+ const versionPath = path.join(cwd, DIR_NAMES.WORKFLOW, ".version");
353
+ if (fs.existsSync(versionPath)) {
354
+ return fs.readFileSync(versionPath, "utf-8").trim();
355
+ }
356
+ return "unknown";
357
+ }
358
+ /**
359
+ * Fetch latest version from npm registry
360
+ */
361
+ async function getLatestNpmVersion() {
362
+ try {
363
+ const response = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`);
364
+ if (!response.ok) {
365
+ return null;
366
+ }
367
+ const data = (await response.json());
368
+ return data.version ?? null;
369
+ }
370
+ catch {
371
+ return null;
372
+ }
373
+ }
374
+ /**
375
+ * Recursively collect all files in a directory
376
+ */
377
+ function collectAllFiles(dirPath) {
378
+ if (!fs.existsSync(dirPath))
379
+ return [];
380
+ const files = [];
381
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
382
+ for (const entry of entries) {
383
+ const fullPath = path.join(dirPath, entry.name);
384
+ if (entry.isDirectory()) {
385
+ files.push(...collectAllFiles(fullPath));
386
+ }
387
+ else if (entry.isFile()) {
388
+ files.push(fullPath);
389
+ }
390
+ }
391
+ return files;
392
+ }
393
+ /**
394
+ * Check if a directory only contains unmodified template files
395
+ * Returns true if safe to delete:
396
+ * - All files are tracked and unmodified, OR
397
+ * - All files match current template content (even if not tracked)
398
+ */
399
+ function isDirectorySafeToReplace(cwd, dirRelativePath, hashes, templates) {
400
+ const dirFullPath = path.join(cwd, dirRelativePath);
401
+ if (!fs.existsSync(dirFullPath))
402
+ return true;
403
+ const files = collectAllFiles(dirFullPath);
404
+ if (files.length === 0)
405
+ return true; // Empty directory is safe
406
+ for (const fullPath of files) {
407
+ const relativePath = path.relative(cwd, fullPath);
408
+ const storedHash = hashes[relativePath];
409
+ const templateContent = templates.get(relativePath);
410
+ // Check if file matches template content (handles untracked files)
411
+ if (templateContent) {
412
+ const currentContent = fs.readFileSync(fullPath, "utf-8");
413
+ if (currentContent === templateContent) {
414
+ // File matches template - safe
415
+ continue;
416
+ }
417
+ }
418
+ // Check if file is tracked and unmodified
419
+ if (storedHash && !isTemplateModified(cwd, relativePath, hashes)) {
420
+ // Tracked and unmodified - safe
421
+ continue;
422
+ }
423
+ // File is either user-created or user-modified - not safe
424
+ return false;
425
+ }
426
+ return true;
427
+ }
428
+ /**
429
+ * Recursively delete a directory
430
+ */
431
+ function removeDirectoryRecursive(dirPath) {
432
+ if (!fs.existsSync(dirPath))
433
+ return;
434
+ fs.rmSync(dirPath, { recursive: true, force: true });
435
+ }
436
+ /**
437
+ * Check if a file is safe to overwrite (matches template content)
438
+ */
439
+ function isFileSafeToReplace(cwd, relativePath, templates) {
440
+ const fullPath = path.join(cwd, relativePath);
441
+ if (!fs.existsSync(fullPath))
442
+ return true;
443
+ const templateContent = templates.get(relativePath);
444
+ if (!templateContent)
445
+ return false; // Not a template file
446
+ const currentContent = fs.readFileSync(fullPath, "utf-8");
447
+ return currentContent === templateContent;
448
+ }
449
+ /**
450
+ * Classify migrations based on file state and user modifications
451
+ */
452
+ function classifyMigrations(migrations, cwd, hashes, templates) {
453
+ const result = {
454
+ auto: [],
455
+ confirm: [],
456
+ conflict: [],
457
+ skip: [],
458
+ };
459
+ for (const item of migrations) {
460
+ const oldPath = path.join(cwd, item.from);
461
+ const oldExists = fs.existsSync(oldPath);
462
+ if (!oldExists) {
463
+ // Old file doesn't exist, nothing to migrate
464
+ result.skip.push(item);
465
+ continue;
466
+ }
467
+ if (item.type === "rename" && item.to) {
468
+ const newPath = path.join(cwd, item.to);
469
+ const newExists = fs.existsSync(newPath);
470
+ if (newExists) {
471
+ // Both exist - check if new file matches template (safe to overwrite)
472
+ if (isFileSafeToReplace(cwd, item.to, templates)) {
473
+ // New file is just template content - safe to delete and rename
474
+ result.auto.push(item);
475
+ }
476
+ else {
477
+ // New file has user content - conflict
478
+ result.conflict.push(item);
479
+ }
480
+ }
481
+ else if (isTemplateModified(cwd, item.from, hashes)) {
482
+ // User has modified the file - needs confirmation
483
+ result.confirm.push(item);
484
+ }
485
+ else {
486
+ // Unmodified template - safe to auto-migrate
487
+ result.auto.push(item);
488
+ }
489
+ }
490
+ else if (item.type === "rename-dir" && item.to) {
491
+ const newPath = path.join(cwd, item.to);
492
+ const newExists = fs.existsSync(newPath);
493
+ if (newExists) {
494
+ // Target exists - check if it only contains unmodified template files
495
+ if (isDirectorySafeToReplace(cwd, item.to, hashes, templates)) {
496
+ // Safe to delete target and rename source
497
+ result.auto.push(item);
498
+ }
499
+ else {
500
+ // Target has user modifications - conflict
501
+ result.conflict.push(item);
502
+ }
503
+ }
504
+ else {
505
+ // Directory rename - always auto (includes user files)
506
+ result.auto.push(item);
507
+ }
508
+ }
509
+ else if (item.type === "delete") {
510
+ if (isTemplateModified(cwd, item.from, hashes)) {
511
+ // User has modified - needs confirmation before delete
512
+ result.confirm.push(item);
513
+ }
514
+ else {
515
+ // Unmodified - safe to auto-delete
516
+ result.auto.push(item);
517
+ }
518
+ }
519
+ }
520
+ return result;
521
+ }
522
+ /**
523
+ * Print migration summary
524
+ */
525
+ function printMigrationSummary(classified) {
526
+ const total = classified.auto.length +
527
+ classified.confirm.length +
528
+ classified.conflict.length +
529
+ classified.skip.length;
530
+ if (total === 0) {
531
+ console.log(chalk.gray(" No migrations to apply.\n"));
532
+ return;
533
+ }
534
+ if (classified.auto.length > 0) {
535
+ console.log(chalk.green(" āœ“ Auto-migrate (unmodified):"));
536
+ for (const item of classified.auto) {
537
+ if (item.type === "rename") {
538
+ console.log(chalk.green(` ${item.from} → ${item.to}`));
539
+ }
540
+ else if (item.type === "rename-dir") {
541
+ console.log(chalk.green(` [dir] ${item.from}/ → ${item.to}/`));
542
+ }
543
+ else {
544
+ console.log(chalk.green(` āœ• ${item.from}`));
545
+ }
546
+ }
547
+ console.log("");
548
+ }
549
+ if (classified.confirm.length > 0) {
550
+ console.log(chalk.yellow(" ⚠ Requires confirmation (modified by user):"));
551
+ for (const item of classified.confirm) {
552
+ if (item.type === "rename") {
553
+ console.log(chalk.yellow(` ${item.from} → ${item.to}`));
554
+ }
555
+ else {
556
+ console.log(chalk.yellow(` āœ• ${item.from}`));
557
+ }
558
+ }
559
+ console.log("");
560
+ }
561
+ if (classified.conflict.length > 0) {
562
+ console.log(chalk.red(" ⊘ Conflict (both old and new exist):"));
563
+ for (const item of classified.conflict) {
564
+ if (item.type === "rename-dir") {
565
+ console.log(chalk.red(` [dir] ${item.from}/ ↔ ${item.to}/`));
566
+ }
567
+ else {
568
+ console.log(chalk.red(` ${item.from} ↔ ${item.to}`));
569
+ }
570
+ }
571
+ console.log(chalk.gray(" → Resolve manually: merge or delete one, then re-run update"));
572
+ console.log("");
573
+ }
574
+ if (classified.skip.length > 0) {
575
+ console.log(chalk.gray(" ā—‹ Skipping (old file not found):"));
576
+ for (const item of classified.skip.slice(0, 3)) {
577
+ console.log(chalk.gray(` ${item.from}`));
578
+ }
579
+ if (classified.skip.length > 3) {
580
+ console.log(chalk.gray(` ... and ${classified.skip.length - 3} more`));
581
+ }
582
+ console.log("");
583
+ }
584
+ }
585
+ /**
586
+ * Prompt user for migration action on a single item
587
+ */
588
+ async function promptMigrationAction(item) {
589
+ const action = item.type === "rename"
590
+ ? `${item.from} → ${item.to}`
591
+ : `Delete ${item.from}`;
592
+ const { choice } = await inquirer.prompt([
593
+ {
594
+ type: "list",
595
+ name: "choice",
596
+ message: `${action}\nThis file has been modified. What would you like to do?`,
597
+ choices: [
598
+ {
599
+ name: item.type === "rename" ? "[r] Rename anyway" : "[d] Delete anyway",
600
+ value: "rename",
601
+ },
602
+ {
603
+ name: "[b] Backup original, then proceed",
604
+ value: "backup-rename",
605
+ },
606
+ {
607
+ name: "[s] Skip this migration",
608
+ value: "skip",
609
+ },
610
+ ],
611
+ default: "skip",
612
+ },
613
+ ]);
614
+ return choice;
615
+ }
616
+ /**
617
+ * Clean up empty directories after file migration
618
+ * Recursively removes empty parent directories up to .aim-studio root
619
+ */
620
+ /** @internal Exported for testing only */
621
+ export function cleanupEmptyDirs(cwd, dirPath) {
622
+ const fullPath = path.join(cwd, dirPath);
623
+ // Safety: don't delete outside of managed directories
624
+ if (!isManagedPath(dirPath)) {
625
+ return;
626
+ }
627
+ // Safety: never delete managed root directories themselves (e.g., .claude, .aim-studio)
628
+ if (isManagedRootDir(dirPath)) {
629
+ return;
630
+ }
631
+ // Check if directory exists and is empty
632
+ if (!fs.existsSync(fullPath))
633
+ return;
634
+ try {
635
+ const stat = fs.statSync(fullPath);
636
+ if (!stat.isDirectory())
637
+ return;
638
+ const contents = fs.readdirSync(fullPath);
639
+ if (contents.length === 0) {
640
+ fs.rmdirSync(fullPath);
641
+ // Recursively check parent (but stop at root directories)
642
+ const parent = path.dirname(dirPath);
643
+ if (parent !== "." &&
644
+ parent !== dirPath &&
645
+ !isManagedRootDir(parent)) {
646
+ cleanupEmptyDirs(cwd, parent);
647
+ }
648
+ }
649
+ }
650
+ catch {
651
+ // Ignore errors (permission issues, etc.)
652
+ }
653
+ }
654
+ /**
655
+ * Sort migrations for safe execution order
656
+ * - rename-dir with deeper paths first (to handle nested directories)
657
+ * - rename-dir before rename/delete
658
+ */
659
+ /** @internal Exported for testing only */
660
+ export function sortMigrationsForExecution(migrations) {
661
+ return [...migrations].sort((a, b) => {
662
+ // rename-dir should be sorted by path depth (deeper first)
663
+ if (a.type === "rename-dir" && b.type === "rename-dir") {
664
+ const aDepth = a.from.split("/").length;
665
+ const bDepth = b.from.split("/").length;
666
+ return bDepth - aDepth; // Deeper paths first
667
+ }
668
+ // rename-dir before rename/delete (directories first)
669
+ if (a.type === "rename-dir" && b.type !== "rename-dir")
670
+ return -1;
671
+ if (a.type !== "rename-dir" && b.type === "rename-dir")
672
+ return 1;
673
+ return 0;
674
+ });
675
+ }
676
+ /**
677
+ * Execute classified migrations
678
+ *
679
+ * @param options.force - Force migrate modified files without asking
680
+ * @param options.skipAll - Skip all modified files without asking
681
+ * If neither is set, prompts interactively for modified files
682
+ */
683
+ async function executeMigrations(classified, cwd, options) {
684
+ const result = {
685
+ renamed: 0,
686
+ deleted: 0,
687
+ skipped: 0,
688
+ conflicts: classified.conflict.length,
689
+ };
690
+ // Sort migrations for safe execution order
691
+ const sortedAuto = sortMigrationsForExecution(classified.auto);
692
+ // 1. Execute auto migrations (unmodified files and directories)
693
+ for (const item of sortedAuto) {
694
+ if (item.type === "rename" && item.to) {
695
+ const oldPath = path.join(cwd, item.from);
696
+ const newPath = path.join(cwd, item.to);
697
+ // Ensure target directory exists
698
+ fs.mkdirSync(path.dirname(newPath), { recursive: true });
699
+ fs.renameSync(oldPath, newPath);
700
+ // Update hash tracking
701
+ renameHash(cwd, item.from, item.to);
702
+ // Make executable if it's a script
703
+ if (item.to.endsWith(".sh") || item.to.endsWith(".py")) {
704
+ fs.chmodSync(newPath, "755");
705
+ }
706
+ // Clean up empty source directory
707
+ cleanupEmptyDirs(cwd, path.dirname(item.from));
708
+ result.renamed++;
709
+ }
710
+ else if (item.type === "rename-dir" && item.to) {
711
+ const oldPath = path.join(cwd, item.from);
712
+ const newPath = path.join(cwd, item.to);
713
+ // If target exists (safe to replace, already checked in classification)
714
+ // delete it first before renaming
715
+ if (fs.existsSync(newPath)) {
716
+ removeDirectoryRecursive(newPath);
717
+ }
718
+ // Ensure parent directory exists
719
+ fs.mkdirSync(path.dirname(newPath), { recursive: true });
720
+ // Rename the entire directory (includes all user files)
721
+ fs.renameSync(oldPath, newPath);
722
+ // Batch update hash tracking for all files in the directory
723
+ const hashes = loadHashes(cwd);
724
+ const oldPrefix = item.from.endsWith("/") ? item.from : item.from + "/";
725
+ const newPrefix = item.to.endsWith("/") ? item.to : item.to + "/";
726
+ const updatedHashes = {};
727
+ for (const [hashPath, hashValue] of Object.entries(hashes)) {
728
+ if (hashPath.startsWith(oldPrefix)) {
729
+ // Rename path: old prefix -> new prefix
730
+ const newHashPath = newPrefix + hashPath.slice(oldPrefix.length);
731
+ updatedHashes[newHashPath] = hashValue;
732
+ }
733
+ else if (hashPath.startsWith(newPrefix)) {
734
+ // Skip old hashes from deleted target directory
735
+ // (they will be replaced by renamed source files)
736
+ continue;
737
+ }
738
+ else {
739
+ // Keep unchanged
740
+ updatedHashes[hashPath] = hashValue;
741
+ }
742
+ }
743
+ saveHashes(cwd, updatedHashes);
744
+ result.renamed++;
745
+ }
746
+ else if (item.type === "delete") {
747
+ const filePath = path.join(cwd, item.from);
748
+ fs.unlinkSync(filePath);
749
+ // Remove from hash tracking
750
+ removeHash(cwd, item.from);
751
+ // Clean up empty directory
752
+ cleanupEmptyDirs(cwd, path.dirname(item.from));
753
+ result.deleted++;
754
+ }
755
+ }
756
+ // 2. Handle confirm items (modified files)
757
+ // Note: All files are already backed up by createMigrationBackup before execution
758
+ for (const item of classified.confirm) {
759
+ let action;
760
+ if (options.force) {
761
+ // Force mode: proceed (already backed up)
762
+ action = "rename";
763
+ }
764
+ else if (options.skipAll) {
765
+ // Skip mode: skip all modified files
766
+ action = "skip";
767
+ }
768
+ else {
769
+ // Default: interactive prompt
770
+ action = await promptMigrationAction(item);
771
+ }
772
+ if (action === "skip") {
773
+ result.skipped++;
774
+ continue;
775
+ }
776
+ // For backup-rename, just proceed (backup already done)
777
+ // Proceed with rename or delete
778
+ if (item.type === "rename" && item.to) {
779
+ const oldPath = path.join(cwd, item.from);
780
+ const newPath = path.join(cwd, item.to);
781
+ fs.mkdirSync(path.dirname(newPath), { recursive: true });
782
+ fs.renameSync(oldPath, newPath);
783
+ renameHash(cwd, item.from, item.to);
784
+ if (item.to.endsWith(".sh") || item.to.endsWith(".py")) {
785
+ fs.chmodSync(newPath, "755");
786
+ }
787
+ // Clean up empty source directory
788
+ cleanupEmptyDirs(cwd, path.dirname(item.from));
789
+ result.renamed++;
790
+ }
791
+ else if (item.type === "delete") {
792
+ const filePath = path.join(cwd, item.from);
793
+ fs.unlinkSync(filePath);
794
+ removeHash(cwd, item.from);
795
+ // Clean up empty directory
796
+ cleanupEmptyDirs(cwd, path.dirname(item.from));
797
+ result.deleted++;
798
+ }
799
+ }
800
+ // 3. Skip count already tracked (old files not found)
801
+ result.skipped += classified.skip.length;
802
+ return result;
803
+ }
804
+ /**
805
+ * Print migration result summary
806
+ */
807
+ function printMigrationResult(result) {
808
+ const parts = [];
809
+ if (result.renamed > 0) {
810
+ parts.push(`${result.renamed} renamed`);
811
+ }
812
+ if (result.deleted > 0) {
813
+ parts.push(`${result.deleted} deleted`);
814
+ }
815
+ if (result.skipped > 0) {
816
+ parts.push(`${result.skipped} skipped`);
817
+ }
818
+ if (result.conflicts > 0) {
819
+ parts.push(`${result.conflicts} conflict${result.conflicts > 1 ? "s" : ""}`);
820
+ }
821
+ if (parts.length > 0) {
822
+ console.log(chalk.cyan(`Migration complete: ${parts.join(", ")}`));
823
+ }
824
+ }
825
+ /**
826
+ * Main update command
827
+ */
828
+ export async function update(options) {
829
+ const cwd = process.cwd();
830
+ // Check if AIM Studio is initialized
831
+ if (!fs.existsSync(path.join(cwd, DIR_NAMES.WORKFLOW))) {
832
+ console.log(chalk.red("Error: AIM Studio not initialized in this directory."));
833
+ console.log(chalk.gray("Run 'aim init' first."));
834
+ return;
835
+ }
836
+ console.log(chalk.cyan("\nAIM Studio Update"));
837
+ console.log(chalk.cyan("═════════════════\n"));
838
+ // Get versions
839
+ const projectVersion = getInstalledVersion(cwd);
840
+ const cliVersion = VERSION;
841
+ const latestNpmVersion = await getLatestNpmVersion();
842
+ // Version comparison
843
+ const cliVsProject = compareVersions(cliVersion, projectVersion);
844
+ const cliVsNpm = latestNpmVersion
845
+ ? compareVersions(cliVersion, latestNpmVersion)
846
+ : 0;
847
+ // Display versions with context
848
+ console.log(`Project version: ${chalk.white(projectVersion)}`);
849
+ console.log(`CLI version: ${chalk.white(cliVersion)}`);
850
+ if (latestNpmVersion) {
851
+ console.log(`Latest on npm: ${chalk.white(latestNpmVersion)}`);
852
+ }
853
+ else {
854
+ console.log(chalk.gray("Latest on npm: (unable to fetch)"));
855
+ }
856
+ console.log("");
857
+ // Check if CLI is outdated compared to npm
858
+ if (cliVsNpm < 0 && latestNpmVersion) {
859
+ console.log(chalk.yellow(`āš ļø Your CLI (${cliVersion}) is behind npm (${latestNpmVersion}).`));
860
+ console.log(chalk.yellow(` Run: npm install -g ${PACKAGE_NAME}\n`));
861
+ }
862
+ // Check for downgrade situation
863
+ if (cliVsProject < 0) {
864
+ console.log(chalk.red(`āŒ Cannot update: CLI version (${cliVersion}) < project version (${projectVersion})`));
865
+ console.log(chalk.red(` This would DOWNGRADE your project!\n`));
866
+ if (!options.allowDowngrade) {
867
+ console.log(chalk.gray("Solutions:"));
868
+ console.log(chalk.gray(` 1. Update your CLI: npm install -g ${PACKAGE_NAME}`));
869
+ console.log(chalk.gray(` 2. Force downgrade: aim update --allow-downgrade\n`));
870
+ return;
871
+ }
872
+ console.log(chalk.yellow("āš ļø --allow-downgrade flag set. Proceeding with downgrade...\n"));
873
+ }
874
+ // Migration metadata is displayed at the end to prevent scrolling off screen
875
+ // Load template hashes for modification detection
876
+ const hashes = loadHashes(cwd);
877
+ const isFirstHashTracking = Object.keys(hashes).length === 0;
878
+ // Handle unknown version - skip migrations but continue with template updates
879
+ const isUnknownVersion = projectVersion === "unknown";
880
+ if (isUnknownVersion) {
881
+ console.log(chalk.yellow("āš ļø No version file found. Skipping migrations."));
882
+ console.log(chalk.gray(" Template updates will still be applied."));
883
+ console.log(chalk.gray(" If your project used old file paths, you may need to rename them manually.\n"));
884
+ }
885
+ // Collect templates (used for both migration classification and change analysis)
886
+ const templates = collectTemplateFiles(cwd);
887
+ // Check for pending migrations (skip if unknown version)
888
+ let pendingMigrations = isUnknownVersion
889
+ ? []
890
+ : getMigrationsForVersion(projectVersion, cliVersion);
891
+ // Also check for "orphaned" migrations - where source still exists but version says we shouldn't migrate
892
+ // This handles cases where version was updated but migrations weren't applied
893
+ const allMigrations = getAllMigrations();
894
+ const orphanedMigrations = allMigrations.filter((item) => {
895
+ // Only check rename and rename-dir migrations
896
+ if (item.type !== "rename" && item.type !== "rename-dir")
897
+ return false;
898
+ if (!item.from || !item.to)
899
+ return false;
900
+ const oldPath = path.join(cwd, item.from);
901
+ const newPath = path.join(cwd, item.to);
902
+ // Orphaned if: source exists AND target doesn't exist
903
+ // AND this migration isn't already in pendingMigrations
904
+ const sourceExists = fs.existsSync(oldPath);
905
+ const targetExists = fs.existsSync(newPath);
906
+ const alreadyPending = pendingMigrations.some((m) => m.from === item.from && m.to === item.to);
907
+ return sourceExists && !targetExists && !alreadyPending;
908
+ });
909
+ // Add orphaned migrations to pending (they need to be applied)
910
+ if (orphanedMigrations.length > 0) {
911
+ console.log(chalk.yellow("āš ļø Detected incomplete migrations from previous updates:"));
912
+ for (const item of orphanedMigrations) {
913
+ console.log(chalk.yellow(` ${item.from} → ${item.to}`));
914
+ }
915
+ console.log("");
916
+ pendingMigrations = [...pendingMigrations, ...orphanedMigrations];
917
+ }
918
+ const hasMigrations = pendingMigrations.length > 0;
919
+ // Classify migrations (stored for later backup creation)
920
+ let classifiedMigrations = null;
921
+ if (hasMigrations) {
922
+ console.log(chalk.cyan("Analyzing migrations...\n"));
923
+ classifiedMigrations = classifyMigrations(pendingMigrations, cwd, hashes, templates);
924
+ printMigrationSummary(classifiedMigrations);
925
+ // Show hint about --migrate flag (execution happens later after backup)
926
+ if (!options.migrate) {
927
+ const autoCount = classifiedMigrations.auto.length;
928
+ const confirmCount = classifiedMigrations.confirm.length;
929
+ if (autoCount > 0 || confirmCount > 0) {
930
+ console.log(chalk.gray(`Tip: Use --migrate to apply migrations (prompts for modified files).`));
931
+ if (confirmCount > 0) {
932
+ console.log(chalk.gray(` Use --migrate -f to force all, or --migrate -s to skip modified.\n`));
933
+ }
934
+ else {
935
+ console.log("");
936
+ }
937
+ }
938
+ }
939
+ }
940
+ // Analyze changes (pass hashes for modification detection)
941
+ const changes = analyzeChanges(cwd, hashes, templates);
942
+ // Print summary
943
+ printChangeSummary(changes);
944
+ // First-time hash tracking hint
945
+ if (isFirstHashTracking && changes.changedFiles.length > 0) {
946
+ console.log(chalk.cyan("ā„¹ļø First update with hash tracking enabled."));
947
+ console.log(chalk.gray(" Changed files shown above may not be actual user modifications."));
948
+ console.log(chalk.gray(" After this update, hash tracking will accurately detect changes.\n"));
949
+ }
950
+ // Check if there's anything to do
951
+ const isUpgrade = cliVsProject > 0;
952
+ const isDowngrade = cliVsProject < 0;
953
+ const isSameVersion = cliVsProject === 0;
954
+ // Check if we have pending migrations that need to be applied
955
+ const hasPendingMigrations = options.migrate &&
956
+ classifiedMigrations &&
957
+ (classifiedMigrations.auto.length > 0 ||
958
+ classifiedMigrations.confirm.length > 0);
959
+ if (changes.newFiles.length === 0 &&
960
+ changes.autoUpdateFiles.length === 0 &&
961
+ changes.changedFiles.length === 0 &&
962
+ !hasPendingMigrations) {
963
+ if (isSameVersion) {
964
+ console.log(chalk.green("āœ“ Already up to date!"));
965
+ }
966
+ else if (isUpgrade) {
967
+ console.log(chalk.green(`āœ“ No file changes needed for ${projectVersion} → ${cliVersion}`));
968
+ }
969
+ return;
970
+ }
971
+ // Show what this operation will do
972
+ if (isUpgrade) {
973
+ console.log(chalk.green(`This will UPGRADE: ${projectVersion} → ${cliVersion}\n`));
974
+ }
975
+ else if (isDowngrade) {
976
+ console.log(chalk.red(`āš ļø This will DOWNGRADE: ${projectVersion} → ${cliVersion}\n`));
977
+ }
978
+ // Show breaking change warning before confirm
979
+ if (cliVsProject > 0 && projectVersion !== "unknown") {
980
+ const preConfirmMetadata = getMigrationMetadata(projectVersion, cliVersion);
981
+ if (preConfirmMetadata.breaking) {
982
+ console.log(chalk.cyan("═".repeat(60)));
983
+ console.log(chalk.bgRed.white.bold(" āš ļø BREAKING CHANGES ") +
984
+ chalk.red.bold(" Review the changes above carefully!"));
985
+ if (preConfirmMetadata.changelog.length > 0) {
986
+ console.log("");
987
+ console.log(chalk.white(preConfirmMetadata.changelog[0]));
988
+ }
989
+ if (preConfirmMetadata.recommendMigrate && !options.migrate) {
990
+ console.log("");
991
+ console.log(chalk.bgGreen.black.bold(" šŸ’” RECOMMENDED ") +
992
+ chalk.green.bold(" Run with --migrate to complete the migration"));
993
+ }
994
+ console.log(chalk.cyan("═".repeat(60)));
995
+ console.log("");
996
+ }
997
+ }
998
+ // Dry run mode
999
+ if (options.dryRun) {
1000
+ console.log(chalk.gray("[Dry run] No changes made."));
1001
+ return;
1002
+ }
1003
+ // Confirm
1004
+ const { proceed } = await inquirer.prompt([
1005
+ {
1006
+ type: "confirm",
1007
+ name: "proceed",
1008
+ message: "Proceed?",
1009
+ default: true,
1010
+ },
1011
+ ]);
1012
+ if (!proceed) {
1013
+ console.log(chalk.yellow("Update cancelled."));
1014
+ return;
1015
+ }
1016
+ // Create complete backup of .aim-studio, .claude, .cursor, .iflow, .opencode directories
1017
+ const backupDir = createFullBackup(cwd);
1018
+ if (backupDir) {
1019
+ console.log(chalk.gray(`\nBackup created: ${path.relative(cwd, backupDir)}/`));
1020
+ }
1021
+ // Execute migrations if --migrate flag is set
1022
+ if (options.migrate && classifiedMigrations) {
1023
+ const migrationResult = await executeMigrations(classifiedMigrations, cwd, {
1024
+ force: options.force,
1025
+ skipAll: options.skipAll,
1026
+ });
1027
+ printMigrationResult(migrationResult);
1028
+ // Hardcoded: Rename traces-*.md to journal-*.md in workspace directories
1029
+ // Why hardcoded: The migration system only supports fixed path renames, not pattern-based.
1030
+ // traces-*.md files are in .aim-studio/workspace/{developer}/ with variable developer names
1031
+ // and variable file numbers (traces-1.md, traces-2.md, etc.), so we can't enumerate them
1032
+ // in the migration manifest. This is a one-time migration for the 0.2.0 naming redesign.
1033
+ const workspaceDir = path.join(cwd, PATHS.WORKSPACE);
1034
+ if (fs.existsSync(workspaceDir)) {
1035
+ let journalRenamed = 0;
1036
+ const devDirs = fs.readdirSync(workspaceDir);
1037
+ for (const dev of devDirs) {
1038
+ const devPath = path.join(workspaceDir, dev);
1039
+ if (!fs.statSync(devPath).isDirectory())
1040
+ continue;
1041
+ const files = fs.readdirSync(devPath);
1042
+ for (const file of files) {
1043
+ if (file.startsWith("traces-") && file.endsWith(".md")) {
1044
+ const oldPath = path.join(devPath, file);
1045
+ const newFile = file.replace("traces-", "journal-");
1046
+ const newPath = path.join(devPath, newFile);
1047
+ fs.renameSync(oldPath, newPath);
1048
+ journalRenamed++;
1049
+ }
1050
+ }
1051
+ }
1052
+ if (journalRenamed > 0) {
1053
+ console.log(chalk.cyan(`Renamed ${journalRenamed} traces file(s) to journal`));
1054
+ }
1055
+ }
1056
+ }
1057
+ // Track results
1058
+ let added = 0;
1059
+ let autoUpdated = 0;
1060
+ let updated = 0;
1061
+ let skipped = 0;
1062
+ let createdNew = 0;
1063
+ // Add new files
1064
+ if (changes.newFiles.length > 0) {
1065
+ console.log(chalk.blue("\nAdding new files..."));
1066
+ for (const file of changes.newFiles) {
1067
+ const dir = path.dirname(file.path);
1068
+ fs.mkdirSync(dir, { recursive: true });
1069
+ fs.writeFileSync(file.path, file.newContent);
1070
+ // Make scripts executable
1071
+ if (file.relativePath.endsWith(".sh") || file.relativePath.endsWith(".py")) {
1072
+ fs.chmodSync(file.path, "755");
1073
+ }
1074
+ console.log(chalk.green(` + ${file.relativePath}`));
1075
+ added++;
1076
+ }
1077
+ }
1078
+ // Auto-update files (template updated, user didn't modify)
1079
+ if (changes.autoUpdateFiles.length > 0) {
1080
+ console.log(chalk.blue("\nAuto-updating template files..."));
1081
+ for (const file of changes.autoUpdateFiles) {
1082
+ fs.writeFileSync(file.path, file.newContent);
1083
+ // Make scripts executable
1084
+ if (file.relativePath.endsWith(".sh") || file.relativePath.endsWith(".py")) {
1085
+ fs.chmodSync(file.path, "755");
1086
+ }
1087
+ console.log(chalk.cyan(` ↑ ${file.relativePath}`));
1088
+ autoUpdated++;
1089
+ }
1090
+ }
1091
+ // Handle changed files
1092
+ if (changes.changedFiles.length > 0) {
1093
+ console.log(chalk.blue("\n--- Resolving conflicts ---\n"));
1094
+ const applyToAll = { action: null };
1095
+ for (const file of changes.changedFiles) {
1096
+ const action = await promptConflictResolution(file, options, applyToAll);
1097
+ if (action === "overwrite") {
1098
+ fs.writeFileSync(file.path, file.newContent);
1099
+ if (file.relativePath.endsWith(".sh") || file.relativePath.endsWith(".py")) {
1100
+ fs.chmodSync(file.path, "755");
1101
+ }
1102
+ console.log(chalk.yellow(` āœ“ Overwritten: ${file.relativePath}`));
1103
+ updated++;
1104
+ }
1105
+ else if (action === "create-new") {
1106
+ const newPath = file.path + ".new";
1107
+ fs.writeFileSync(newPath, file.newContent);
1108
+ console.log(chalk.blue(` āœ“ Created: ${file.relativePath}.new`));
1109
+ createdNew++;
1110
+ }
1111
+ else {
1112
+ console.log(chalk.gray(` ā—‹ Skipped: ${file.relativePath}`));
1113
+ skipped++;
1114
+ }
1115
+ }
1116
+ }
1117
+ // Update version file
1118
+ updateVersionFile(cwd);
1119
+ // Update template hashes for new, auto-updated, and overwritten files
1120
+ const filesToHash = new Map();
1121
+ for (const file of changes.newFiles) {
1122
+ filesToHash.set(file.relativePath, file.newContent);
1123
+ }
1124
+ // Auto-updated files always get new hash
1125
+ for (const file of changes.autoUpdateFiles) {
1126
+ filesToHash.set(file.relativePath, file.newContent);
1127
+ }
1128
+ // Only hash overwritten files (not skipped or .new copies)
1129
+ for (const file of changes.changedFiles) {
1130
+ const fullPath = path.join(cwd, file.relativePath);
1131
+ if (fs.existsSync(fullPath)) {
1132
+ const content = fs.readFileSync(fullPath, "utf-8");
1133
+ if (content === file.newContent) {
1134
+ filesToHash.set(file.relativePath, file.newContent);
1135
+ }
1136
+ }
1137
+ }
1138
+ if (filesToHash.size > 0) {
1139
+ updateHashes(cwd, filesToHash);
1140
+ }
1141
+ // Print summary
1142
+ console.log(chalk.cyan("\n--- Summary ---\n"));
1143
+ if (added > 0) {
1144
+ console.log(` Added: ${added} file(s)`);
1145
+ }
1146
+ if (autoUpdated > 0) {
1147
+ console.log(` Auto-updated: ${autoUpdated} file(s)`);
1148
+ }
1149
+ if (updated > 0) {
1150
+ console.log(` Updated: ${updated} file(s)`);
1151
+ }
1152
+ if (skipped > 0) {
1153
+ console.log(` Skipped: ${skipped} file(s)`);
1154
+ }
1155
+ if (createdNew > 0) {
1156
+ console.log(` Created .new copies: ${createdNew} file(s)`);
1157
+ }
1158
+ if (backupDir) {
1159
+ console.log(` Backup: ${path.relative(cwd, backupDir)}/`);
1160
+ }
1161
+ const actionWord = isDowngrade ? "Downgrade" : "Update";
1162
+ console.log(chalk.green(`\nāœ… ${actionWord} complete! (${projectVersion} → ${cliVersion})`));
1163
+ if (createdNew > 0) {
1164
+ console.log(chalk.gray("\nTip: Review .new files and merge changes manually if needed."));
1165
+ }
1166
+ // Create migration task if there are breaking changes with migration guides
1167
+ if (cliVsProject > 0 && projectVersion !== "unknown") {
1168
+ const metadata = getMigrationMetadata(projectVersion, cliVersion);
1169
+ if (metadata.breaking && metadata.migrationGuides.length > 0) {
1170
+ // Create task directory
1171
+ const today = new Date();
1172
+ const monthDay = `${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
1173
+ const taskSlug = `migrate-to-${cliVersion}`;
1174
+ const taskDirName = `${monthDay}-${taskSlug}`;
1175
+ const tasksDir = path.join(cwd, DIR_NAMES.WORKFLOW, DIR_NAMES.TASKS);
1176
+ const taskDir = path.join(tasksDir, taskDirName);
1177
+ // Check if task already exists
1178
+ if (!fs.existsSync(taskDir)) {
1179
+ fs.mkdirSync(taskDir, { recursive: true });
1180
+ // Get current developer for assignee
1181
+ const developerFile = path.join(cwd, DIR_NAMES.WORKFLOW, ".developer");
1182
+ let currentDeveloper = "unknown";
1183
+ if (fs.existsSync(developerFile)) {
1184
+ currentDeveloper = fs.readFileSync(developerFile, "utf-8").trim();
1185
+ }
1186
+ // Build task.json
1187
+ const taskTitle = `Migrate to v${cliVersion}`;
1188
+ const todayStr = today.toISOString().split("T")[0];
1189
+ const taskJson = {
1190
+ title: taskTitle,
1191
+ description: `Breaking change migration from v${projectVersion} to v${cliVersion}`,
1192
+ status: "planning",
1193
+ dev_type: null,
1194
+ scope: "migration",
1195
+ priority: "P1",
1196
+ creator: "trellis-update",
1197
+ assignee: currentDeveloper,
1198
+ createdAt: todayStr,
1199
+ completedAt: null,
1200
+ branch: null,
1201
+ base_branch: null,
1202
+ worktree_path: null,
1203
+ current_phase: 0,
1204
+ next_action: [
1205
+ { phase: 1, action: "review-guide" },
1206
+ { phase: 2, action: "update-files" },
1207
+ { phase: 3, action: "run-migrate" },
1208
+ { phase: 4, action: "test" },
1209
+ ],
1210
+ commit: null,
1211
+ pr_url: null,
1212
+ subtasks: [],
1213
+ };
1214
+ // Write task.json
1215
+ const taskJsonPath = path.join(taskDir, "task.json");
1216
+ fs.writeFileSync(taskJsonPath, JSON.stringify(taskJson, null, 2));
1217
+ // Build PRD content
1218
+ let prdContent = `# Migration Task: Upgrade to v${cliVersion}\n\n`;
1219
+ prdContent += `**Created**: ${todayStr}\n`;
1220
+ prdContent += `**From Version**: ${projectVersion}\n`;
1221
+ prdContent += `**To Version**: ${cliVersion}\n`;
1222
+ prdContent += `**Assignee**: ${currentDeveloper}\n\n`;
1223
+ prdContent += `## Status\n\n- [ ] Review migration guide\n- [ ] Update custom files\n- [ ] Run \`trellis update --migrate\`\n- [ ] Test workflows\n\n`;
1224
+ for (const { version, guide, aiInstructions } of metadata.migrationGuides) {
1225
+ prdContent += `---\n\n## v${version} Migration Guide\n\n`;
1226
+ prdContent += guide;
1227
+ prdContent += "\n\n";
1228
+ if (aiInstructions) {
1229
+ prdContent += `### AI Assistant Instructions\n\n`;
1230
+ prdContent += `When helping with this migration:\n\n`;
1231
+ prdContent += aiInstructions;
1232
+ prdContent += "\n\n";
1233
+ }
1234
+ }
1235
+ // Write PRD
1236
+ const prdPath = path.join(taskDir, "prd.md");
1237
+ fs.writeFileSync(prdPath, prdContent);
1238
+ console.log("");
1239
+ console.log(chalk.bgCyan.black.bold(" šŸ“‹ MIGRATION TASK CREATED "));
1240
+ console.log(chalk.cyan(`A task has been created to help you complete the migration:`));
1241
+ console.log(chalk.white(` ${DIR_NAMES.WORKFLOW}/${DIR_NAMES.TASKS}/${taskDirName}/`));
1242
+ console.log("");
1243
+ console.log(chalk.gray("Use AI to help: Ask Claude/Cursor to read the task and fix your custom files."));
1244
+ }
1245
+ }
1246
+ }
1247
+ // Display breaking change warnings at the very end (so they don't scroll off screen)
1248
+ if (cliVsProject > 0 && projectVersion !== "unknown") {
1249
+ const finalMetadata = getMigrationMetadata(projectVersion, cliVersion);
1250
+ if (finalMetadata.breaking || finalMetadata.changelog.length > 0) {
1251
+ console.log("");
1252
+ console.log(chalk.cyan("═".repeat(60)));
1253
+ if (finalMetadata.breaking) {
1254
+ console.log(chalk.bgRed.white.bold(" āš ļø BREAKING CHANGES ") +
1255
+ chalk.red.bold(" This update contains breaking changes!"));
1256
+ console.log("");
1257
+ }
1258
+ if (finalMetadata.changelog.length > 0) {
1259
+ console.log(chalk.cyan.bold("šŸ“‹ What's Changed:"));
1260
+ for (const entry of finalMetadata.changelog) {
1261
+ console.log(chalk.white(` ${entry}`));
1262
+ }
1263
+ console.log("");
1264
+ }
1265
+ if (finalMetadata.recommendMigrate && !options.migrate) {
1266
+ console.log(chalk.bgGreen.black.bold(" šŸ’” RECOMMENDED ") +
1267
+ chalk.green.bold(" Run with --migrate to complete the migration"));
1268
+ console.log(chalk.gray(" This will remove legacy files and apply all changes."));
1269
+ console.log("");
1270
+ }
1271
+ console.log(chalk.cyan("═".repeat(60)));
1272
+ }
1273
+ }
1274
+ }
1275
+ //# sourceMappingURL=update.js.map