aiwcli 0.15.5 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (435) hide show
  1. package/README.md +108 -1124
  2. package/bin/run.js +0 -4
  3. package/dist/capabilities/branch/adapters.d.ts +2 -0
  4. package/dist/capabilities/branch/adapters.js +21 -0
  5. package/dist/capabilities/branch/contracts.d.ts +57 -0
  6. package/dist/capabilities/branch/contracts.js +1 -0
  7. package/dist/capabilities/branch/control-plane.d.ts +2 -0
  8. package/dist/capabilities/branch/control-plane.js +343 -0
  9. package/dist/capabilities/branch/runtime-core.d.ts +5 -0
  10. package/dist/capabilities/branch/runtime-core.js +36 -0
  11. package/dist/capabilities/installation/control-plane/clean-command.d.ts +41 -0
  12. package/dist/capabilities/installation/control-plane/clean-command.js +196 -0
  13. package/dist/capabilities/installation/control-plane/clear-command.d.ts +162 -0
  14. package/dist/capabilities/installation/control-plane/clear-command.js +1249 -0
  15. package/dist/capabilities/installation/control-plane/init-command.d.ts +81 -0
  16. package/dist/capabilities/installation/control-plane/init-command.js +449 -0
  17. package/dist/capabilities/launch/contracts.d.ts +86 -0
  18. package/dist/capabilities/launch/contracts.js +1 -0
  19. package/dist/capabilities/launch/control-plane/execute-launch.d.ts +2 -0
  20. package/dist/capabilities/launch/control-plane/execute-launch.js +261 -0
  21. package/dist/capabilities/launch/runtime-core/launch-decisions.d.ts +82 -0
  22. package/dist/capabilities/launch/runtime-core/launch-decisions.js +202 -0
  23. package/dist/capabilities/launch/runtime-core/launch-options.d.ts +14 -0
  24. package/dist/capabilities/launch/runtime-core/launch-options.js +69 -0
  25. package/dist/cli/base-command.d.ts +18 -0
  26. package/dist/cli/base-command.js +55 -0
  27. package/dist/commands/branch.d.ts +1 -21
  28. package/dist/commands/branch.js +25 -417
  29. package/dist/commands/clean.d.ts +1 -41
  30. package/dist/commands/clean.js +1 -196
  31. package/dist/commands/clear.d.ts +1 -161
  32. package/dist/commands/clear.js +1 -1121
  33. package/dist/commands/init/index.d.ts +1 -98
  34. package/dist/commands/init/index.js +4 -478
  35. package/dist/commands/launch.d.ts +32 -12
  36. package/dist/commands/launch.js +107 -166
  37. package/dist/lib/claude-settings-types.d.ts +31 -19
  38. package/dist/lib/config.js +1 -2
  39. package/dist/lib/context/context-formatter.d.ts +74 -0
  40. package/dist/lib/context/context-formatter.js +493 -0
  41. package/dist/lib/context/context-selector.d.ts +42 -0
  42. package/dist/lib/context/context-selector.js +451 -0
  43. package/dist/lib/context/context-store.d.ts +100 -0
  44. package/dist/lib/context/context-store.js +644 -0
  45. package/dist/lib/context/plan-manager.d.ts +54 -0
  46. package/dist/lib/context/plan-manager.js +282 -0
  47. package/dist/lib/context/task-tracker.d.ts +44 -0
  48. package/dist/lib/context/task-tracker.js +146 -0
  49. package/dist/lib/core-ide-base.d.ts +4 -0
  50. package/dist/lib/core-ide-base.js +77 -0
  51. package/dist/lib/core-installer.d.ts +5 -0
  52. package/dist/lib/core-installer.js +33 -0
  53. package/dist/lib/debug.d.ts +0 -10
  54. package/dist/lib/debug.js +0 -10
  55. package/dist/lib/env-sanitizer.d.ts +25 -0
  56. package/dist/lib/env-sanitizer.js +46 -0
  57. package/dist/lib/errors.d.ts +0 -13
  58. package/dist/lib/errors.js +0 -15
  59. package/dist/lib/git-exclude-manager.d.ts +2 -2
  60. package/dist/lib/git-exclude-manager.js +3 -3
  61. package/dist/lib/hooks/context-monitor-logic.d.ts +6 -0
  62. package/dist/lib/hooks/context-monitor-logic.js +25 -0
  63. package/dist/lib/hooks/hook-utils.d.ts +143 -0
  64. package/dist/lib/hooks/hook-utils.js +620 -0
  65. package/dist/lib/hooks/prompt-binding-logic.d.ts +7 -0
  66. package/dist/lib/hooks/prompt-binding-logic.js +50 -0
  67. package/dist/lib/hooks/session-end-logic.d.ts +5 -0
  68. package/dist/lib/hooks/session-end-logic.js +51 -0
  69. package/dist/lib/hooks-merger.js +25 -19
  70. package/dist/lib/ide-path-resolver.d.ts +19 -7
  71. package/dist/lib/ide-path-resolver.js +25 -9
  72. package/dist/lib/install-state.d.ts +34 -0
  73. package/dist/lib/install-state.js +154 -0
  74. package/dist/lib/json-io.d.ts +12 -0
  75. package/dist/lib/json-io.js +30 -0
  76. package/dist/lib/lsp-patch.d.ts +12 -0
  77. package/dist/lib/lsp-patch.js +156 -0
  78. package/dist/lib/multiplexer.d.ts +65 -0
  79. package/dist/lib/multiplexer.js +38 -0
  80. package/dist/lib/multiplexers/psmux.d.ts +55 -0
  81. package/dist/lib/multiplexers/psmux.js +324 -0
  82. package/dist/lib/multiplexers/tmux.d.ts +36 -0
  83. package/dist/lib/multiplexers/tmux.js +221 -0
  84. package/dist/lib/multiplexers/wezterm.d.ts +38 -0
  85. package/dist/lib/multiplexers/wezterm.js +225 -0
  86. package/dist/lib/mux-utils.d.ts +6 -0
  87. package/dist/lib/mux-utils.js +36 -0
  88. package/dist/lib/paths.d.ts +2 -2
  89. package/dist/lib/paths.js +2 -2
  90. package/dist/lib/platform-commands.d.ts +27 -0
  91. package/dist/lib/platform-commands.js +49 -0
  92. package/dist/lib/prompt-file-manager.d.ts +23 -0
  93. package/dist/lib/prompt-file-manager.js +41 -0
  94. package/dist/lib/runtime/agent-launcher.d.ts +67 -0
  95. package/dist/lib/runtime/agent-launcher.js +262 -0
  96. package/dist/lib/runtime/aiw-cli.d.ts +39 -0
  97. package/dist/lib/runtime/aiw-cli.js +76 -0
  98. package/dist/lib/runtime/atomic-write.d.ts +19 -0
  99. package/dist/lib/runtime/atomic-write.js +121 -0
  100. package/dist/lib/runtime/cli-args.d.ts +58 -0
  101. package/dist/lib/runtime/cli-args.js +200 -0
  102. package/dist/lib/runtime/constants.d.ts +56 -0
  103. package/dist/lib/runtime/constants.js +230 -0
  104. package/dist/lib/runtime/executable-policy.d.ts +16 -0
  105. package/dist/lib/runtime/executable-policy.js +57 -0
  106. package/dist/lib/runtime/git-state.d.ts +9 -0
  107. package/dist/lib/runtime/git-state.js +59 -0
  108. package/dist/lib/runtime/inference.d.ts +37 -0
  109. package/dist/lib/runtime/inference.js +251 -0
  110. package/dist/lib/runtime/lint-dispatch.d.ts +40 -0
  111. package/dist/lib/runtime/lint-dispatch.js +285 -0
  112. package/dist/lib/runtime/logger.d.ts +66 -0
  113. package/dist/lib/runtime/logger.js +201 -0
  114. package/dist/lib/runtime/models.d.ts +20 -0
  115. package/dist/lib/runtime/models.js +20 -0
  116. package/dist/lib/runtime/platform-adapter.d.ts +7 -0
  117. package/dist/lib/runtime/platform-adapter.js +21 -0
  118. package/dist/lib/runtime/preflight.d.ts +24 -0
  119. package/dist/lib/runtime/preflight.js +65 -0
  120. package/dist/lib/runtime/sentinel-ipc.d.ts +14 -0
  121. package/dist/lib/runtime/sentinel-ipc.js +67 -0
  122. package/dist/lib/runtime/state-io.d.ts +31 -0
  123. package/dist/lib/runtime/state-io.js +179 -0
  124. package/dist/lib/runtime/stop-words.d.ts +20 -0
  125. package/dist/lib/runtime/stop-words.js +150 -0
  126. package/dist/lib/runtime/subprocess-utils.d.ts +29 -0
  127. package/dist/lib/runtime/subprocess-utils.js +96 -0
  128. package/dist/lib/runtime/tmux-preflight.d.ts +13 -0
  129. package/dist/lib/runtime/tmux-preflight.js +78 -0
  130. package/dist/lib/runtime/utils.d.ts +62 -0
  131. package/dist/lib/runtime/utils.js +192 -0
  132. package/dist/lib/schemas.d.ts +250 -0
  133. package/dist/lib/schemas.js +216 -0
  134. package/dist/lib/sentinel-manager.d.ts +32 -0
  135. package/dist/lib/sentinel-manager.js +62 -0
  136. package/dist/lib/sentinel-wrapper.d.ts +10 -0
  137. package/dist/lib/sentinel-wrapper.js +29 -0
  138. package/dist/lib/settings-hierarchy.js +3 -20
  139. package/dist/lib/shell-adapters/bash-adapter.d.ts +18 -0
  140. package/dist/lib/shell-adapters/bash-adapter.js +69 -0
  141. package/dist/lib/shell-adapters/index.d.ts +5 -0
  142. package/dist/lib/shell-adapters/index.js +7 -0
  143. package/dist/lib/shell-adapters/powershell-adapter.d.ts +18 -0
  144. package/dist/lib/shell-adapters/powershell-adapter.js +62 -0
  145. package/dist/lib/shell-adapters/shell-adapter.d.ts +45 -0
  146. package/dist/lib/shell-adapters/shell-adapter.js +5 -0
  147. package/dist/lib/shell-quoting.d.ts +5 -0
  148. package/dist/lib/shell-quoting.js +17 -0
  149. package/dist/lib/spawn-errors.d.ts +9 -0
  150. package/dist/lib/spawn-errors.js +29 -0
  151. package/dist/lib/spawn.js +5 -11
  152. package/dist/lib/spinner.d.ts +0 -5
  153. package/dist/lib/spinner.js +0 -16
  154. package/dist/lib/template-installer.d.ts +14 -5
  155. package/dist/lib/template-installer.js +40 -38
  156. package/dist/lib/template-resolver.d.ts +6 -7
  157. package/dist/lib/template-resolver.js +26 -21
  158. package/dist/lib/template-settings-reconstructor.d.ts +7 -2
  159. package/dist/lib/template-settings-reconstructor.js +76 -45
  160. package/dist/lib/terminal-strategy.d.ts +12 -0
  161. package/dist/lib/terminal-strategy.js +55 -0
  162. package/dist/lib/terminal.d.ts +34 -4
  163. package/dist/lib/terminal.js +192 -119
  164. package/dist/lib/tmux-pane-placement.d.ts +17 -0
  165. package/dist/lib/tmux-pane-placement.js +58 -0
  166. package/dist/lib/tmux-primitives.d.ts +3 -0
  167. package/dist/lib/tmux-primitives.js +11 -0
  168. package/dist/lib/tmux-session.d.ts +32 -0
  169. package/dist/lib/tmux-session.js +87 -0
  170. package/dist/lib/tty-detection.js +1 -1
  171. package/dist/lib/types.d.ts +168 -0
  172. package/dist/lib/types.js +6 -0
  173. package/dist/lib/version.d.ts +1 -1
  174. package/dist/lib/version.js +1 -1
  175. package/dist/lib/windsurf-hooks-hierarchy.js +6 -23
  176. package/dist/platform/launch.d.ts +11 -0
  177. package/dist/platform/launch.js +11 -0
  178. package/dist/templates/CLAUDE.md +30 -40
  179. package/dist/templates/cc-native/.claude/settings.json +26 -36
  180. package/dist/templates/cc-native/CC-NATIVE-README.md +1 -1
  181. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +20 -12
  182. package/dist/templates/cc-native/_cc-native/cc-native.config.json +2 -6
  183. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +39 -59
  184. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +9 -11
  185. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +2 -2
  186. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +4 -5
  187. package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +4 -4
  188. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +2 -27
  189. package/dist/templates/cc-native/_cc-native/hooks/validate_task_prompt.ts +7 -7
  190. package/dist/templates/cc-native/_cc-native/lib-ts/.mocharc.json +9 -0
  191. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/aggregate-agents.test.ts +118 -0
  192. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/artifacts.test.ts +234 -0
  193. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/cc-native-state.test.ts +170 -0
  194. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/cli-output-parser.test.ts +73 -0
  195. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/config.test.ts +64 -0
  196. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/constants.test.ts +40 -0
  197. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/debug.test.ts +42 -0
  198. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/exports.test.ts +58 -0
  199. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/helpers.ts +107 -0
  200. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/hooks/add-plan-context.hook.test.ts +97 -0
  201. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/hooks/plan-questions.hook.test.ts +81 -0
  202. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/hooks/plan-review.hook.test.ts +71 -0
  203. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/json-parser.test.ts +99 -0
  204. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/orchestrator-agent.test.ts +288 -0
  205. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/orchestrator.test.ts +48 -0
  206. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/reviewers.test.ts +32 -0
  207. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/state.test.ts +124 -0
  208. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/verdict.test.ts +93 -0
  209. package/dist/templates/cc-native/_cc-native/lib-ts/agent-selection.ts +163 -0
  210. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +6 -14
  211. package/dist/templates/cc-native/_cc-native/{artifacts/lib → lib-ts/artifacts}/format.ts +597 -599
  212. package/dist/templates/cc-native/_cc-native/{artifacts/lib → lib-ts/artifacts}/index.ts +26 -26
  213. package/dist/templates/cc-native/_cc-native/{artifacts/lib → lib-ts/artifacts}/tracker.ts +106 -107
  214. package/dist/templates/cc-native/_cc-native/{artifacts/lib → lib-ts/artifacts}/write.ts +118 -119
  215. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +21 -0
  216. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +17 -16
  217. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +132 -10
  218. package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +1 -1
  219. package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +6 -6
  220. package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +119 -0
  221. package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +2 -3
  222. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/graduation.ts +132 -132
  223. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +88 -86
  224. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +5 -6
  225. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/orchestrator.ts +70 -70
  226. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/output-builder.ts +130 -121
  227. package/dist/templates/cc-native/_cc-native/lib-ts/package-lock.json +1679 -0
  228. package/dist/templates/cc-native/_cc-native/lib-ts/package.json +24 -0
  229. package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +5 -5
  230. package/dist/templates/cc-native/_cc-native/lib-ts/plan-enhancement.ts +1 -6
  231. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/plan-questions.ts +101 -101
  232. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/review-pipeline.ts +511 -543
  233. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/__tests__/agent-providers.test.ts +262 -0
  234. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/agent.ts +71 -85
  235. package/dist/templates/{_shared/lib-ts/agent-exec → cc-native/_cc-native/lib-ts/reviewers/base}/base-agent.ts +138 -150
  236. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/index.ts +12 -12
  237. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/providers/claude-agent.ts +66 -57
  238. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/providers/codex-agent.ts +185 -200
  239. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/providers/gemini-agent.ts +39 -40
  240. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/providers/orchestrator-claude-agent.ts +196 -225
  241. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/schemas.ts +201 -201
  242. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/types.ts +21 -23
  243. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/__tests__/hyde.test.ts +365 -0
  244. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/__tests__/ollama-client.test.ts +223 -0
  245. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/embedding-indexer.ts +12 -16
  246. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/hyde.ts +3 -2
  247. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/index.ts +31 -31
  248. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/logger.ts +7 -8
  249. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/ollama-client.ts +7 -9
  250. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/retrieval-pipeline.ts +16 -19
  251. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-indexer.ts +37 -41
  252. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-loader.ts +33 -43
  253. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-searcher.ts +20 -20
  254. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/types.ts +9 -10
  255. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/vector-store.ts +3 -4
  256. package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +50 -126
  257. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +20 -22
  258. package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +2 -2
  259. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +14 -89
  260. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/verdict.ts +72 -72
  261. package/dist/templates/cc-native/_cc-native/plan-review/CLAUDE.md +38 -1
  262. package/dist/templates/cc-native/_cc-native/plan-review/lib/__tests__/agent-selection.test.ts +345 -0
  263. package/dist/templates/cc-native/_cc-native/plan-review/lib/__tests__/preflight.test.ts +344 -0
  264. package/dist/templates/cc-native/_cc-native/plan-review/lib/agent-selection.ts +38 -16
  265. package/dist/templates/cc-native/_cc-native/plan-review/lib/preflight.ts +56 -26
  266. package/dist/templates/cc-native/_cc-native/scripts/council_debate.ts +242 -0
  267. package/dist/templates/cc-native/_cc-native/scripts/council_debate_simple.ts +294 -0
  268. package/dist/templates/cc-native/_cc-native/{plan-review/workflows → workflows}/specdev.md +9 -9
  269. package/dist/templates/core/.claude/skills/codex/SKILL.md +25 -0
  270. package/dist/templates/core/.claude/skills/devin/SKILL.md +25 -0
  271. package/dist/templates/core/.claude/skills/handoff/SKILL.md +11 -0
  272. package/dist/templates/core/.claude/skills/handoff-resume/SKILL.md +11 -0
  273. package/dist/templates/core/.claude/skills/meta-plan/SKILL.md +13 -0
  274. package/dist/templates/core/.codex/skills/codex/SKILL.md +13 -0
  275. package/dist/templates/core/.codex/skills/devin/SKILL.md +19 -0
  276. package/dist/templates/core/.codex/skills/handoff/SKILL.md +11 -0
  277. package/dist/templates/core/.codex/skills/handoff-resume/SKILL.md +11 -0
  278. package/dist/templates/core/.codex/skills/meta-plan/SKILL.md +13 -0
  279. package/dist/templates/core/.devin/AGENTS.md +5 -0
  280. package/dist/templates/core/.devin/config.json +12 -0
  281. package/dist/templates/core/.devin/skills/codex/SKILL.md +19 -0
  282. package/dist/templates/core/.devin/skills/devin/SKILL.md +13 -0
  283. package/dist/templates/core/.devin/skills/handoff/SKILL.md +11 -0
  284. package/dist/templates/core/.devin/skills/handoff-resume/SKILL.md +11 -0
  285. package/dist/templates/core/.devin/skills/meta-plan/SKILL.md +13 -0
  286. package/dist/templates/core/.windsurf/workflows/handoff-resume.md +9 -0
  287. package/dist/templates/{_shared → core}/.windsurf/workflows/handoff.md +1 -1
  288. package/dist/templates/{_shared → core}/.windsurf/workflows/meta-plan.md +1 -1
  289. package/dist/templates/core/hooks-ts/_utils/git-state.ts +2 -0
  290. package/dist/templates/{_shared → core}/hooks-ts/archive_plan.ts +15 -44
  291. package/dist/templates/core/hooks-ts/codex_explorer.ts +160 -0
  292. package/dist/templates/{_shared → core}/hooks-ts/context_monitor.ts +23 -55
  293. package/dist/templates/{_shared → core}/hooks-ts/file-suggestion.ts +5 -22
  294. package/dist/templates/{_shared → core}/hooks-ts/lint_after_edit.ts +7 -9
  295. package/dist/templates/core/hooks-ts/pre_compact.ts +36 -0
  296. package/dist/templates/{_shared → core}/hooks-ts/session_end.ts +38 -78
  297. package/dist/templates/{_shared → core}/hooks-ts/session_start.ts +5 -5
  298. package/dist/templates/core/hooks-ts/task_create_capture.ts +32 -0
  299. package/dist/templates/{_shared → core}/hooks-ts/task_update_capture.ts +9 -24
  300. package/dist/templates/core/hooks-ts/user_prompt_submit.ts +46 -0
  301. package/dist/templates/{_shared → core}/lib-ts/CLAUDE.md +27 -16
  302. package/dist/templates/{_shared → core}/lib-ts/context/CLAUDE.md +9 -6
  303. package/dist/templates/{_shared → core}/lib-ts/context/context-formatter.ts +16 -21
  304. package/dist/templates/{_shared → core}/lib-ts/context/context-selector.ts +8 -6
  305. package/dist/templates/{_shared → core}/lib-ts/context/context-store.ts +59 -20
  306. package/dist/templates/{_shared → core}/lib-ts/context/plan-manager.ts +19 -15
  307. package/dist/templates/{_shared → core}/lib-ts/context/task-tracker.ts +3 -3
  308. package/dist/templates/core/lib-ts/hooks/context-monitor-logic.ts +32 -0
  309. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/hooks}/hook-utils.ts +179 -41
  310. package/dist/templates/core/lib-ts/hooks/prompt-binding-logic.ts +80 -0
  311. package/dist/templates/core/lib-ts/hooks/session-end-logic.ts +82 -0
  312. package/dist/templates/core/lib-ts/package.json +19 -0
  313. package/dist/templates/core/lib-ts/runtime/agent-launcher.ts +369 -0
  314. package/dist/templates/core/lib-ts/runtime/aiw-cli.ts +108 -0
  315. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/atomic-write.ts +12 -7
  316. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/cli-args.ts +24 -8
  317. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/constants.ts +326 -324
  318. package/dist/templates/core/lib-ts/runtime/executable-policy.ts +89 -0
  319. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/git-state.ts +6 -4
  320. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/inference.ts +60 -23
  321. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/lint-dispatch.ts +25 -23
  322. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/logger.ts +32 -29
  323. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/models.ts +9 -2
  324. package/dist/templates/core/lib-ts/runtime/platform-adapter.ts +33 -0
  325. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/preflight.ts +4 -3
  326. package/dist/templates/core/lib-ts/runtime/sentinel-ipc.ts +91 -0
  327. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/state-io.ts +20 -11
  328. package/dist/templates/core/lib-ts/runtime/stop-words.ts +185 -0
  329. package/dist/templates/core/lib-ts/runtime/subprocess-utils.ts +147 -0
  330. package/dist/templates/core/lib-ts/runtime/tmux-preflight.ts +93 -0
  331. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/utils.ts +34 -4
  332. package/dist/templates/core/lib-ts/schemas.ts +233 -0
  333. package/dist/templates/{_shared → core}/lib-ts/templates/formatters.ts +7 -5
  334. package/dist/templates/{_shared → core}/lib-ts/templates/plan-context.ts +2 -1
  335. package/dist/templates/{_shared → core}/lib-ts/tsconfig.json +3 -1
  336. package/dist/templates/{_shared → core}/lib-ts/types.ts +78 -77
  337. package/dist/templates/core/scripts/resolve-run.ts +93 -0
  338. package/dist/templates/{_shared → core}/scripts/resolve_context.ts +3 -3
  339. package/dist/templates/{_shared → core}/scripts/status_line.ts +26 -21
  340. package/dist/templates/core/skills/codex/CLAUDE.md +83 -0
  341. package/dist/templates/{_shared → core}/skills/codex/SKILL.md +27 -18
  342. package/dist/templates/{_shared → core}/skills/codex/lib/codex-watcher.ts +79 -113
  343. package/dist/templates/{_shared → core}/skills/codex/scripts/launch-codex.ts +134 -148
  344. package/dist/templates/{_shared → core}/skills/codex/scripts/watch-codex.ts +6 -4
  345. package/dist/templates/core/skills/devin/CLAUDE.md +122 -0
  346. package/dist/templates/core/skills/devin/SKILL.md +73 -0
  347. package/dist/templates/core/skills/devin/lib/devin-watcher.ts +300 -0
  348. package/dist/templates/core/skills/devin/scripts/launch-devin.ts +258 -0
  349. package/dist/templates/{_shared → core}/skills/handoff-system/CLAUDE.md +436 -433
  350. package/dist/templates/{_shared → core}/skills/handoff-system/lib/document-generator.ts +9 -7
  351. package/dist/templates/{_shared → core}/skills/handoff-system/lib/handoff-reader.ts +6 -4
  352. package/dist/templates/{_shared → core}/skills/handoff-system/scripts/resume_handoff.ts +10 -8
  353. package/dist/templates/{_shared → core}/skills/handoff-system/scripts/save_handoff.ts +12 -10
  354. package/dist/templates/{_shared → core}/skills/handoff-system/workflows/handoff-resume.md +2 -2
  355. package/dist/templates/{_shared → core}/skills/handoff-system/workflows/handoff.md +6 -5
  356. package/dist/templates/{_shared → core}/skills/meta-plan/CLAUDE.md +2 -1
  357. package/dist/templates/{_shared → core}/skills/meta-plan/workflows/meta-plan.md +8 -7
  358. package/oclif.manifest.json +89 -13
  359. package/package.json +13 -12
  360. package/dist/lib/base-command.d.ts +0 -114
  361. package/dist/lib/base-command.js +0 -153
  362. package/dist/lib/env-compat.d.ts +0 -18
  363. package/dist/lib/env-compat.js +0 -23
  364. package/dist/lib/stdin.d.ts +0 -48
  365. package/dist/lib/stdin.js +0 -60
  366. package/dist/templates/_shared/.claude/settings.json +0 -120
  367. package/dist/templates/_shared/.claude/skills/codex/SKILL.md +0 -35
  368. package/dist/templates/_shared/.claude/skills/handoff/SKILL.md +0 -13
  369. package/dist/templates/_shared/.claude/skills/handoff-resume/SKILL.md +0 -13
  370. package/dist/templates/_shared/.claude/skills/meta-plan/SKILL.md +0 -43
  371. package/dist/templates/_shared/.codex/workflows/codex.md +0 -11
  372. package/dist/templates/_shared/.codex/workflows/handoff.md +0 -226
  373. package/dist/templates/_shared/.codex/workflows/meta-plan.md +0 -347
  374. package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +0 -2
  375. package/dist/templates/_shared/hooks-ts/pre_compact.ts +0 -49
  376. package/dist/templates/_shared/hooks-ts/task_create_capture.ts +0 -48
  377. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +0 -93
  378. package/dist/templates/_shared/lib-ts/agent-exec/backends/headless.ts +0 -33
  379. package/dist/templates/_shared/lib-ts/agent-exec/backends/index.ts +0 -6
  380. package/dist/templates/_shared/lib-ts/agent-exec/backends/tmux.ts +0 -119
  381. package/dist/templates/_shared/lib-ts/agent-exec/execution-backend.ts +0 -50
  382. package/dist/templates/_shared/lib-ts/agent-exec/index.ts +0 -6
  383. package/dist/templates/_shared/lib-ts/agent-exec/structured-output.ts +0 -166
  384. package/dist/templates/_shared/lib-ts/base/launchers/tmux-launcher.ts +0 -173
  385. package/dist/templates/_shared/lib-ts/base/launchers/window-launcher.ts +0 -93
  386. package/dist/templates/_shared/lib-ts/base/launchers/wt-launcher.ts +0 -64
  387. package/dist/templates/_shared/lib-ts/base/pane-launcher.ts +0 -55
  388. package/dist/templates/_shared/lib-ts/base/sentinel-ipc.ts +0 -87
  389. package/dist/templates/_shared/lib-ts/base/stop-words.ts +0 -184
  390. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +0 -249
  391. package/dist/templates/_shared/lib-ts/base/tmux-driver.ts +0 -341
  392. package/dist/templates/_shared/lib-ts/base/tmux-pane-placement.ts +0 -78
  393. package/dist/templates/_shared/lib-ts/package.json +0 -20
  394. package/dist/templates/_shared/scripts/resolve-run.ts +0 -62
  395. package/dist/templates/_shared/skills/codex/CLAUDE.md +0 -70
  396. package/dist/templates/cc-native/_cc-native/CLAUDE.md +0 -73
  397. package/dist/templates/cc-native/_cc-native/artifacts/CLAUDE.md +0 -64
  398. package/dist/templates/cc-native/_cc-native/lib-ts/CLAUDE.md +0 -70
  399. package/dist/templates/cc-native/_cc-native/plan-review/CODING-STANDARDS-CHECKLIST.md +0 -75
  400. package/dist/templates/cc-native/_cc-native/plan-review/agents/CLAUDE.md +0 -143
  401. package/dist/templates/cc-native/_cc-native/plan-review/agents/PLAN-ORCHESTRATOR.md +0 -213
  402. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-questions/PLAN-QUESTIONER.md +0 -70
  403. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-EVOLUTION.md +0 -62
  404. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-PATTERNS.md +0 -61
  405. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-STRUCTURE.md +0 -62
  406. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ASSUMPTION-TRACER.md +0 -56
  407. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CLARITY-AUDITOR.md +0 -53
  408. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-FEASIBILITY.md +0 -66
  409. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-GAPS.md +0 -70
  410. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-ORDERING.md +0 -62
  411. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CONSTRAINT-VALIDATOR.md +0 -72
  412. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-ADR-VALIDATOR.md +0 -61
  413. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-SCALE-MATCHER.md +0 -64
  414. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DEVILS-ADVOCATE.md +0 -56
  415. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DOCUMENTATION-PHILOSOPHY.md +0 -86
  416. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HANDOFF-READINESS.md +0 -59
  417. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HIDDEN-COMPLEXITY.md +0 -58
  418. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/INCREMENTAL-DELIVERY.md +0 -66
  419. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-DEPENDENCY.md +0 -62
  420. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-FMEA.md +0 -66
  421. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-PREMORTEM.md +0 -71
  422. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-REVERSIBILITY.md +0 -74
  423. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SCOPE-BOUNDARY.md +0 -77
  424. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SIMPLICITY-GUARDIAN.md +0 -62
  425. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SKEPTIC.md +0 -68
  426. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md +0 -61
  427. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-CHARACTERIZATION.md +0 -71
  428. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-FIRST-VALIDATOR.md +0 -61
  429. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md +0 -61
  430. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-COSTS.md +0 -67
  431. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-STAKEHOLDERS.md +0 -65
  432. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-COVERAGE.md +0 -74
  433. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-STRENGTH.md +0 -69
  434. package/dist/templates/cc-native/_cc-native/plan-review/lib/corroboration.ts +0 -172
  435. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/base/base-agent.ts +0 -7
@@ -0,0 +1,1249 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import confirm from '@inquirer/confirm';
4
+ import { Flags } from '@oclif/core';
5
+ import BaseCommand from '../../../cli/base-command.js';
6
+ import { computeExcludeRemovals, pruneExcludeStaleEntries, removeExcludeEntries, resolveGitDir } from '../../../lib/git-exclude-manager.js';
7
+ import { deleteInstallStateIfPresent, getInstalledMethods, markMethodRemoved, readInstallState } from '../../../lib/install-state.js';
8
+ import { pathExists } from '../../../lib/paths.js';
9
+ import { getTemplatePath } from '../../../lib/template-resolver.js';
10
+ import { reconstructIdeSettings } from '../../../lib/template-settings-reconstructor.js';
11
+ import { EXIT_CODES } from '../../../types/exit-codes.js';
12
+ /**
13
+ * Container folder for method-specific files
14
+ * This keeps template infrastructure separate from IDE config
15
+ */
16
+ const AIWCLI_CONTAINER = '.aiwcli';
17
+ /**
18
+ * The output folder name that contains method subdirectories.
19
+ * Structure: _output/{method}/ (e.g., _output/bmad/, _output/gsd/)
20
+ */
21
+ const OUTPUT_FOLDER_NAME = '_output';
22
+ const CORE_TEMPLATE_NAME = 'core';
23
+ const SETTINGS_FILES_TO_SKIP = new Set(['hooks.json', 'settings.json']);
24
+ const CORE_RUNTIME_FOLDERS = ['_core'];
25
+ export const PROTECTED_OUTPUT_DIRS = new Set(['contexts', 'cache', '_archive']);
26
+ /**
27
+ * IDE configuration folder names and settings file locations.
28
+ * Method subfolders are discovered dynamically via disk scanning.
29
+ */
30
+ const IDE_FOLDERS = {
31
+ claude: {
32
+ root: '.claude',
33
+ settingsFile: 'settings.json',
34
+ },
35
+ codex: {
36
+ root: '.codex',
37
+ },
38
+ devin: {
39
+ root: '.devin',
40
+ },
41
+ windsurf: {
42
+ root: '.windsurf',
43
+ settingsFile: 'hooks.json',
44
+ },
45
+ };
46
+ /**
47
+ * Check if a directory is empty.
48
+ *
49
+ * @param dir - Directory to check
50
+ * @returns True if directory is empty or doesn't exist
51
+ */
52
+ async function isDirectoryEmpty(dir) {
53
+ try {
54
+ const entries = await fs.readdir(dir);
55
+ return entries.length === 0;
56
+ }
57
+ catch {
58
+ return true;
59
+ }
60
+ }
61
+ /**
62
+ * Check if a JSON settings file is empty or effectively empty.
63
+ * Returns true if the file doesn't exist, can't be parsed, or contains an empty object.
64
+ *
65
+ * @param filePath - Path to the JSON settings file
66
+ * @returns True if file is empty or doesn't exist
67
+ */
68
+ async function isSettingsFileEmpty(filePath) {
69
+ try {
70
+ const content = await fs.readFile(filePath, 'utf8');
71
+ const trimmed = content.trim();
72
+ if (trimmed === '' || trimmed === '{}') {
73
+ return true;
74
+ }
75
+ const parsed = JSON.parse(content);
76
+ // Check if it's an empty object
77
+ return typeof parsed === 'object' && parsed !== null && Object.keys(parsed).length === 0;
78
+ }
79
+ catch {
80
+ // File doesn't exist or can't be parsed - consider it empty
81
+ return true;
82
+ }
83
+ }
84
+ /**
85
+ * Check if an IDE folder should be fully deleted.
86
+ * Returns true if:
87
+ * 1. The settings file is empty (or doesn't exist)
88
+ * 2. All subdirectories are empty (or don't exist)
89
+ * Backup files (e.g., settings.json.backup) are ignored.
90
+ *
91
+ * @param targetDir - Directory containing the IDE folder
92
+ * @param ideFolder - IDE folder configuration
93
+ * @param ideFolder.root - Root folder name (e.g., '.claude')
94
+ * @param ideFolder.settingsFile - Settings file name (e.g., 'settings.json')
95
+ * @returns True if the IDE folder should be fully deleted
96
+ */
97
+ async function shouldDeleteIdeFolder(targetDir, ideFolder) {
98
+ const ideFolderPath = join(targetDir, ideFolder.root);
99
+ // Check if IDE folder exists at all
100
+ try {
101
+ const stat = await fs.stat(ideFolderPath);
102
+ if (!stat.isDirectory()) {
103
+ return false;
104
+ }
105
+ }
106
+ catch {
107
+ // Folder doesn't exist - nothing to delete
108
+ return false;
109
+ }
110
+ // Check if settings file is empty (for IDEs that use one)
111
+ if (ideFolder.settingsFile) {
112
+ const settingsPath = join(ideFolderPath, ideFolder.settingsFile);
113
+ const settingsEmpty = await isSettingsFileEmpty(settingsPath);
114
+ if (!settingsEmpty) {
115
+ return false;
116
+ }
117
+ }
118
+ // Check the IDE folder itself - ignore backup files and check for other meaningful content
119
+ try {
120
+ const entries = await fs.readdir(ideFolderPath);
121
+ // Filter entries to check (skip backup files and settings file)
122
+ const entriesToCheck = entries.filter((entry) => {
123
+ if (entry.endsWith('.backup'))
124
+ return false;
125
+ if (ideFolder.settingsFile && entry === ideFolder.settingsFile)
126
+ return false;
127
+ return true;
128
+ });
129
+ // Check all entries in parallel
130
+ const entryResults = await Promise.all(entriesToCheck.map(async (entry) => {
131
+ const entryPath = join(ideFolderPath, entry);
132
+ try {
133
+ const stat = await fs.stat(entryPath);
134
+ if (stat.isDirectory()) {
135
+ return isDirectoryEmpty(entryPath);
136
+ }
137
+ // Non-backup file exists - don't delete the folder
138
+ return false;
139
+ }
140
+ catch {
141
+ // Can't stat entry - be safe and don't delete
142
+ return false;
143
+ }
144
+ }));
145
+ // If unknown entry is not empty (or is a non-backup file), don't delete
146
+ if (entryResults.some((result) => !result)) {
147
+ return false;
148
+ }
149
+ }
150
+ catch {
151
+ return false;
152
+ }
153
+ return true;
154
+ }
155
+ /**
156
+ * Remove a directory recursively.
157
+ *
158
+ * @param dir - Directory to remove
159
+ */
160
+ async function removeDirectory(dir) {
161
+ await fs.rm(dir, { force: true, recursive: true });
162
+ }
163
+ /**
164
+ * Try to remove a directory if it is empty.
165
+ *
166
+ * @param dir - Directory to check and potentially remove
167
+ * @returns True if the directory was removed
168
+ */
169
+ async function tryRemoveEmptyDir(dir) {
170
+ try {
171
+ if (await isDirectoryEmpty(dir)) {
172
+ await removeDirectory(dir);
173
+ return true;
174
+ }
175
+ }
176
+ catch {
177
+ // Directory doesn't exist or can't be accessed
178
+ }
179
+ return false;
180
+ }
181
+ /**
182
+ * Check if an IDE folder will be empty after removing specified method folders.
183
+ * Counts method folders vs folders being deleted, then simulates settings cleanup.
184
+ *
185
+ * @param targetDir - Project root directory
186
+ * @param ideFolder - IDE folder configuration
187
+ * @param ideFolder.root - Root folder name (e.g., '.claude')
188
+ * @param ideFolder.settingsFile - Settings file name (e.g., 'settings.json')
189
+ * @param ideMethodFolders - IDE method folders being deleted
190
+ * @returns True if the IDE folder will be empty after removal
191
+ */
192
+ async function checkIdeRemovalEligibility(targetDir, ideFolder, ideMethodFolders) {
193
+ const idePath = join(targetDir, ideFolder.root);
194
+ try {
195
+ const stat = await fs.stat(idePath);
196
+ if (!stat.isDirectory())
197
+ return false;
198
+ }
199
+ catch {
200
+ return false;
201
+ }
202
+ // Count method folders vs folders being deleted
203
+ const counts = await countMethodFolderDeletions(idePath, ideMethodFolders);
204
+ if (counts.total === 0 || counts.total !== counts.deleted)
205
+ return false;
206
+ // IDEs without a settings file are eligible based on folder counts alone.
207
+ if (!ideFolder.settingsFile)
208
+ return true;
209
+ // Check if settings file would become empty after removing AIW-managed hooks.
210
+ return wouldSettingsBeEmpty(idePath, ideFolder.settingsFile);
211
+ }
212
+ /**
213
+ * Count total method folders and how many are being deleted in an IDE root.
214
+ *
215
+ * @param idePath - Path to IDE root folder
216
+ * @param ideMethodFolders - IDE method folders being deleted
217
+ * @returns Counts of total and deleted method folders
218
+ */
219
+ async function countMethodFolderDeletions(idePath, ideMethodFolders) {
220
+ let total = 0;
221
+ let deleted = 0;
222
+ try {
223
+ const topEntries = await fs.readdir(idePath, { withFileTypes: true });
224
+ const subdirs = topEntries.filter((e) => e.isDirectory());
225
+ const subResults = await Promise.all(subdirs.map(async (subdir) => {
226
+ const subdirPath = join(idePath, subdir.name);
227
+ try {
228
+ const entries = await fs.readdir(subdirPath, { withFileTypes: true });
229
+ const methodDirs = entries.filter((e) => e.isDirectory());
230
+ const deletedCount = methodDirs.filter((entry) => ideMethodFolders.includes(join(subdirPath, entry.name))).length;
231
+ return { deleted: deletedCount, total: methodDirs.length };
232
+ }
233
+ catch {
234
+ return { deleted: 0, total: 0 };
235
+ }
236
+ }));
237
+ for (const r of subResults) {
238
+ total += r.total;
239
+ deleted += r.deleted;
240
+ }
241
+ }
242
+ catch {
243
+ return { deleted: 0, total: 0 };
244
+ }
245
+ return { deleted, total };
246
+ }
247
+ /**
248
+ * Check if a settings file would be empty after removing AIW-managed hooks.
249
+ *
250
+ * @param idePath - Path to IDE root folder
251
+ * @param settingsFile - Settings file name
252
+ * @returns True if settings would be empty
253
+ */
254
+ async function wouldSettingsBeEmpty(idePath, settingsFile) {
255
+ const settingsPath = join(idePath, settingsFile);
256
+ try {
257
+ const content = await fs.readFile(settingsPath, 'utf8');
258
+ const settings = JSON.parse(content);
259
+ if (settings.hooks && typeof settings.hooks === 'object') {
260
+ delete settings.hooks;
261
+ }
262
+ return Object.keys(settings).length === 0;
263
+ }
264
+ catch {
265
+ return true;
266
+ }
267
+ }
268
+ /**
269
+ * Check if a log file exceeds 1MB and needs rotation.
270
+ *
271
+ * @param logPath - Path to the log file
272
+ * @returns Log action info if rotation needed, null otherwise
273
+ */
274
+ async function checkLogRotation(logPath) {
275
+ try {
276
+ const stat = await fs.stat(logPath);
277
+ if (stat.size > 1_048_576) {
278
+ return { path: logPath, sizeBytes: stat.size };
279
+ }
280
+ }
281
+ catch {
282
+ // Can't stat log file
283
+ }
284
+ return null;
285
+ }
286
+ /**
287
+ * Check if a contexts directory has a non-empty _archive/ subdirectory.
288
+ *
289
+ * @param contextsPath - Path to the contexts directory
290
+ * @returns Archive info if found, null otherwise
291
+ */
292
+ async function checkArchiveDir(contextsPath) {
293
+ const archivePath = join(contextsPath, '_archive');
294
+ try {
295
+ const entries = await fs.readdir(archivePath);
296
+ if (entries.length > 0) {
297
+ return { path: archivePath, count: entries.length };
298
+ }
299
+ }
300
+ catch {
301
+ // No archive directory
302
+ }
303
+ return null;
304
+ }
305
+ /**
306
+ * Recursively remove files from targetDir that have a matching file in sourceDir.
307
+ * This removes only AIW-managed template files and leaves unknown user files intact.
308
+ *
309
+ * @param sourceDir - Template source subtree
310
+ * @param targetDir - Target subtree in project
311
+ * @returns Number of files removed
312
+ */
313
+ async function removeMatchingFiles(sourceDir, targetDir) {
314
+ let entries;
315
+ try {
316
+ entries = await fs.readdir(sourceDir, { withFileTypes: true });
317
+ }
318
+ catch {
319
+ return 0;
320
+ }
321
+ let removed = 0;
322
+ for (const entry of entries) {
323
+ const sourcePath = join(sourceDir, entry.name);
324
+ const targetPath = join(targetDir, entry.name);
325
+ if (entry.isDirectory()) {
326
+ // eslint-disable-next-line no-await-in-loop
327
+ removed += await removeMatchingFiles(sourcePath, targetPath);
328
+ continue;
329
+ }
330
+ if (!entry.isFile())
331
+ continue;
332
+ if (SETTINGS_FILES_TO_SKIP.has(entry.name))
333
+ continue;
334
+ try {
335
+ // eslint-disable-next-line no-await-in-loop
336
+ if (!(await pathExists(targetPath)))
337
+ continue;
338
+ // eslint-disable-next-line no-await-in-loop
339
+ await fs.rm(targetPath, { force: true });
340
+ removed++;
341
+ }
342
+ catch {
343
+ // Best-effort deletion
344
+ }
345
+ }
346
+ try {
347
+ const leftovers = await fs.readdir(targetDir);
348
+ if (leftovers.length === 0) {
349
+ await fs.rmdir(targetDir);
350
+ }
351
+ }
352
+ catch {
353
+ // Target dir doesn't exist or isn't empty
354
+ }
355
+ return removed;
356
+ }
357
+ /**
358
+ * Clear method runtime folders, output folders, IDE method folders, and update configurations.
359
+ */
360
+ export default class ClearCommand extends BaseCommand {
361
+ static description = 'Clear method runtime folders, output folders, IDE method folders (.claude/.codex/.windsurf), and update configurations';
362
+ static examples = [
363
+ '<%= config.bin %> <%= command.id %>',
364
+ '<%= config.bin %> <%= command.id %> --template cc-native',
365
+ '<%= config.bin %> <%= command.id %> -t cc-native',
366
+ '<%= config.bin %> <%= command.id %> --dry-run',
367
+ '<%= config.bin %> <%= command.id %> --force',
368
+ '<%= config.bin %> <%= command.id %> --output',
369
+ '<%= config.bin %> <%= command.id %> --output --dry-run',
370
+ ];
371
+ static flags = {
372
+ ...BaseCommand.baseFlags,
373
+ 'dry-run': Flags.boolean({
374
+ char: 'n',
375
+ description: 'Show what would be deleted without actually deleting',
376
+ default: false,
377
+ }),
378
+ force: Flags.boolean({
379
+ char: 'f',
380
+ description: 'Skip confirmation prompt',
381
+ default: false,
382
+ }),
383
+ output: Flags.boolean({
384
+ char: 'o',
385
+ description: 'Clean runtime output artifacts (temp files, caches, log rotation, archives)',
386
+ default: false,
387
+ exclusive: ['template'],
388
+ }),
389
+ template: Flags.string({
390
+ char: 't',
391
+ description: 'Clear only a specific template (e.g., cc-native)',
392
+ exclusive: ['output'],
393
+ }),
394
+ };
395
+ async run() {
396
+ const { flags } = await this.parse(ClearCommand);
397
+ const targetDir = process.cwd();
398
+ const isTemplateScoped = Boolean(flags.template);
399
+ // Handle --output flag separately (mutually exclusive with --template)
400
+ if (flags.output) {
401
+ await this.cleanRuntimeOutput(targetDir, flags);
402
+ return;
403
+ }
404
+ try {
405
+ // Find all folders to clear
406
+ const methodRuntimeFolders = await this.findMethodRuntimeFolders(targetDir, flags.template);
407
+ const outputMethodFolders = await this.findOutputFolders(targetDir, flags.template);
408
+ const ideMethodFolders = await this.findIdeMethodFolders(targetDir, flags.template);
409
+ const coreRuntimeFolders = isTemplateScoped ? [] : await this.findCoreRuntimeFolders(targetDir);
410
+ const coreIdeFilesToRemove = isTemplateScoped ? 0 : await this.countCoreIdeManagedFiles(targetDir);
411
+ const methodsToRemove = await this.resolveMethodsToRemove(targetDir, flags.template, methodRuntimeFolders);
412
+ // Nothing to clear
413
+ if (methodRuntimeFolders.length === 0 &&
414
+ outputMethodFolders.length === 0 &&
415
+ ideMethodFolders.length === 0 &&
416
+ coreRuntimeFolders.length === 0 &&
417
+ coreIdeFilesToRemove === 0) {
418
+ const msg = flags.template
419
+ ? `No folders found for template '${flags.template}'.`
420
+ : 'No AIW-managed folders or core template files found.';
421
+ this.logInfo(msg);
422
+ return;
423
+ }
424
+ // Display pending changes
425
+ await this.displayPendingChanges(targetDir, {
426
+ methodRuntimeFolders, outputMethodFolders, ideMethodFolders, coreRuntimeFolders, methodsToRemove, coreIdeFilesToRemove,
427
+ });
428
+ // Dry run - just show what would happen
429
+ if (flags['dry-run']) {
430
+ this.logInfo('Dry run complete. No files or folders were deleted.');
431
+ return;
432
+ }
433
+ // Confirm deletion
434
+ const totalFolders = methodRuntimeFolders.length + outputMethodFolders.length + ideMethodFolders.length + coreRuntimeFolders.length;
435
+ const coreFilesSuffix = coreIdeFilesToRemove > 0 ? ` and clean ${coreIdeFilesToRemove} core IDE file(s)` : '';
436
+ if (!flags.force) {
437
+ const shouldDelete = await confirm({
438
+ message: `Delete ${totalFolders} folder(s)${coreFilesSuffix}?`,
439
+ default: false,
440
+ });
441
+ if (!shouldDelete) {
442
+ this.log('Operation cancelled.');
443
+ return;
444
+ }
445
+ }
446
+ // Execute deletion and cleanup
447
+ const deleteCounts = await this.executeFolderDeletion(methodRuntimeFolders, outputMethodFolders, ideMethodFolders, coreRuntimeFolders);
448
+ // Audit log for aiw clear operations (only if _output/ will survive — don't create orphan files)
449
+ try {
450
+ const outputDir = join(targetDir, OUTPUT_FOLDER_NAME);
451
+ if (await pathExists(outputDir) && !await isDirectoryEmpty(outputDir)) {
452
+ const hookLogPath = join(outputDir, 'hook-log.jsonl');
453
+ const logEntry = JSON.stringify({
454
+ ts: new Date().toISOString(),
455
+ level: 'warn',
456
+ hook: 'aiw_clear',
457
+ msg: `aiw clear: deleted ${deleteCounts.deletedMethodRuntime + deleteCounts.deletedOutput + deleteCounts.deletedIde + deleteCounts.deletedCoreRuntime} folder(s)`,
458
+ data: { methodRuntime: deleteCounts.deletedMethodRuntime, output: deleteCounts.deletedOutput, ide: deleteCounts.deletedIde, coreRuntime: deleteCounts.deletedCoreRuntime }
459
+ });
460
+ await fs.appendFile(hookLogPath, logEntry + '\n');
461
+ }
462
+ }
463
+ catch { /* non-critical */ }
464
+ const cleanupResult = await this.performPostDeleteCleanup(targetDir, methodsToRemove, !isTemplateScoped);
465
+ this.reportClearResults(deleteCounts, cleanupResult);
466
+ }
467
+ catch (error) {
468
+ const err = error;
469
+ if (err.code === 'EACCES' || err.code === 'EPERM') {
470
+ this.error(`Permission denied. ${err.message}`, {
471
+ exit: EXIT_CODES.ENVIRONMENT_ERROR,
472
+ });
473
+ }
474
+ this.error(`Clear failed: ${err.message}`, {
475
+ exit: EXIT_CODES.GENERAL_ERROR,
476
+ });
477
+ }
478
+ }
479
+ /**
480
+ * Clean runtime output artifacts from _output/ at project root.
481
+ * Handles temp files, cache files, log rotation, and archive cleanup.
482
+ *
483
+ * @param targetDir - Project root directory
484
+ * @param flags - Command flags (dry-run, force)
485
+ * @param flags.force - Skip confirmation prompt
486
+ */
487
+ // eslint-disable-next-line complexity
488
+ async cleanRuntimeOutput(targetDir, flags) {
489
+ const outputDir = join(targetDir, '_output');
490
+ if (!(await pathExists(outputDir))) {
491
+ this.logInfo('No _output/ directory found.');
492
+ return;
493
+ }
494
+ const toDelete = [];
495
+ let logAction = null;
496
+ let archiveDir = null;
497
+ let archiveCount = 0;
498
+ try {
499
+ const entries = await fs.readdir(outputDir, { withFileTypes: true });
500
+ for (const entry of entries) {
501
+ const entryPath = join(outputDir, entry.name);
502
+ // Temp files: .index_*.tmp (orphaned atomic write files)
503
+ if (entry.isFile() && entry.name.startsWith('.index_') && entry.name.endsWith('.tmp')) {
504
+ toDelete.push({ path: entryPath, reason: 'temp file' });
505
+ continue;
506
+ }
507
+ // Cache files: .*-cache.json
508
+ if (entry.isFile() && entry.name.startsWith('.') && entry.name.endsWith('-cache.json')) {
509
+ toDelete.push({ path: entryPath, reason: 'cache file' });
510
+ continue;
511
+ }
512
+ // Log rotation: hook-log.jsonl > 1MB
513
+ if (entry.isFile() && entry.name === 'hook-log.jsonl') {
514
+ logAction = await checkLogRotation(entryPath); // eslint-disable-line no-await-in-loop
515
+ continue;
516
+ }
517
+ // Archive cleanup: contexts/_archive/
518
+ if (entry.isDirectory() && entry.name === 'contexts') {
519
+ const result = await checkArchiveDir(entryPath); // eslint-disable-line no-await-in-loop
520
+ if (result) {
521
+ archiveDir = result.path;
522
+ archiveCount = result.count;
523
+ }
524
+ }
525
+ }
526
+ }
527
+ catch (error) {
528
+ const err = error;
529
+ this.error(`Cannot read _output/: ${err.message}`, {
530
+ exit: EXIT_CODES.GENERAL_ERROR,
531
+ });
532
+ }
533
+ // Nothing to clean
534
+ if (toDelete.length === 0 && !logAction && !archiveDir) {
535
+ this.logInfo('No runtime output artifacts to clean.');
536
+ return;
537
+ }
538
+ // Show what will be cleaned
539
+ this.log('');
540
+ this.logInfo('Runtime output cleanup:');
541
+ if (toDelete.length > 0) {
542
+ for (const item of toDelete) {
543
+ const relativePath = item.path.replace(targetDir + '\\', '').replace(targetDir + '/', '');
544
+ this.log(` ${relativePath} (${item.reason})`);
545
+ }
546
+ }
547
+ if (logAction) {
548
+ const sizeMB = (logAction.sizeBytes / 1_048_576).toFixed(1);
549
+ this.log(` _output/hook-log.jsonl (${sizeMB}MB → truncate to ~512KB)`);
550
+ }
551
+ if (archiveDir) {
552
+ this.log(` _output/contexts/_archive/ (${archiveCount} archived context(s))`);
553
+ }
554
+ this.log('');
555
+ // Dry run
556
+ if (flags['dry-run']) {
557
+ this.logInfo('Dry run complete. No files were modified.');
558
+ return;
559
+ }
560
+ // Confirm archive deletion (unless --force)
561
+ if (archiveDir && !flags.force) {
562
+ const shouldDelete = await confirm({
563
+ message: `Delete ${archiveCount} archived context(s)?`,
564
+ default: false,
565
+ });
566
+ if (!shouldDelete) {
567
+ archiveDir = null;
568
+ archiveCount = 0;
569
+ }
570
+ }
571
+ // Execute deletions
572
+ let deletedCount = 0;
573
+ for (const item of toDelete) {
574
+ try {
575
+ await fs.unlink(item.path); // eslint-disable-line no-await-in-loop
576
+ deletedCount++;
577
+ }
578
+ catch (error) {
579
+ const err = error;
580
+ this.logWarning(`Failed to delete ${item.path}: ${err.message}`);
581
+ }
582
+ }
583
+ // Log rotation
584
+ if (logAction) {
585
+ try {
586
+ const content = await fs.readFile(logAction.path, 'utf8');
587
+ // Keep the most recent 512KB
588
+ const truncated = content.slice(-524_288);
589
+ // Find the first complete line
590
+ const firstNewline = truncated.indexOf('\n');
591
+ const cleaned = firstNewline === -1 ? truncated : truncated.slice(firstNewline + 1);
592
+ await fs.writeFile(logAction.path, cleaned, 'utf8');
593
+ this.logDebug('Rotated hook-log.jsonl');
594
+ }
595
+ catch (error) {
596
+ const err = error;
597
+ this.logWarning(`Failed to rotate log: ${err.message}`);
598
+ }
599
+ }
600
+ // Archive cleanup
601
+ let archivedCleaned = 0;
602
+ if (archiveDir) {
603
+ try {
604
+ const archiveEntries = await fs.readdir(archiveDir);
605
+ await Promise.all(archiveEntries.map(async (entry) => {
606
+ try {
607
+ await fs.rm(join(archiveDir, entry), { force: true, recursive: true });
608
+ archivedCleaned++;
609
+ }
610
+ catch {
611
+ // Individual entry failed
612
+ }
613
+ }));
614
+ }
615
+ catch (error) {
616
+ const err = error;
617
+ this.logWarning(`Failed to clean archive: ${err.message}`);
618
+ }
619
+ }
620
+ // Summary
621
+ this.log('');
622
+ const parts = [];
623
+ if (deletedCount > 0) {
624
+ parts.push(`${deletedCount} file(s) removed`);
625
+ }
626
+ if (logAction) {
627
+ parts.push('log rotated');
628
+ }
629
+ if (archivedCleaned > 0) {
630
+ parts.push(`${archivedCleaned} archived context(s) removed`);
631
+ }
632
+ if (parts.length > 0) {
633
+ this.logSuccess(`Output cleanup: ${parts.join(', ')}.`);
634
+ }
635
+ else {
636
+ this.logInfo('No changes made.');
637
+ }
638
+ }
639
+ /**
640
+ * Clean up backup files created during settings reconstruction.
641
+ *
642
+ * @param targetDir - Project root directory
643
+ */
644
+ async cleanupBackupFiles(targetDir) {
645
+ const cleanups = Object.values(IDE_FOLDERS).map(async (ide) => {
646
+ if (!ide.settingsFile)
647
+ return;
648
+ const backupPath = join(targetDir, ide.root, `${ide.settingsFile}.backup`);
649
+ try {
650
+ await fs.rm(backupPath, { force: true });
651
+ }
652
+ catch {
653
+ // Backup doesn't exist or can't be removed
654
+ }
655
+ });
656
+ await Promise.all(cleanups);
657
+ }
658
+ /**
659
+ * Clean up git exclude entries and prune stale entries.
660
+ *
661
+ * @param targetDir - Project root directory
662
+ * @returns True if git exclude was updated
663
+ */
664
+ async cleanupGitExclude(targetDir, isFullClear = false) {
665
+ const gitDir = await resolveGitDir(targetDir);
666
+ if (!gitDir)
667
+ return false;
668
+ const { toRemove, toKeep } = await computeExcludeRemovals(gitDir, targetDir, isFullClear ? [] : undefined);
669
+ for (const { entry, reason } of toKeep) {
670
+ this.logDebug(`Keeping ${entry}/ in git exclude (${reason})`);
671
+ }
672
+ if (toRemove.length > 0) {
673
+ await removeExcludeEntries(gitDir, toRemove);
674
+ this.logDebug(`Removed from git exclude: ${toRemove.join(', ')}`);
675
+ }
676
+ const pruned = await pruneExcludeStaleEntries(gitDir, targetDir);
677
+ if (pruned) {
678
+ this.logDebug('Pruned stale git exclude entries');
679
+ }
680
+ return toRemove.length > 0 || pruned;
681
+ }
682
+ async countCoreIdeManagedFiles(targetDir) {
683
+ const coreTemplatePath = await this.getCoreTemplatePathSafe();
684
+ if (!coreTemplatePath)
685
+ return 0;
686
+ let total = 0;
687
+ for (const ide of Object.values(IDE_FOLDERS)) {
688
+ const sourceIdeRoot = join(coreTemplatePath, ide.root);
689
+ const targetIdeRoot = join(targetDir, ide.root);
690
+ // eslint-disable-next-line no-await-in-loop
691
+ if (!(await pathExists(sourceIdeRoot)) || !(await pathExists(targetIdeRoot)))
692
+ continue;
693
+ // eslint-disable-next-line no-await-in-loop
694
+ total += await this.countMatchingManagedFiles(sourceIdeRoot, targetIdeRoot);
695
+ }
696
+ return total;
697
+ }
698
+ async countMatchingManagedFiles(sourceDir, targetDir) {
699
+ let entries;
700
+ try {
701
+ entries = await fs.readdir(sourceDir, { withFileTypes: true });
702
+ }
703
+ catch {
704
+ return 0;
705
+ }
706
+ let count = 0;
707
+ for (const entry of entries) {
708
+ const sourcePath = join(sourceDir, entry.name);
709
+ const targetPath = join(targetDir, entry.name);
710
+ if (entry.isDirectory()) {
711
+ // eslint-disable-next-line no-await-in-loop
712
+ count += await this.countMatchingManagedFiles(sourcePath, targetPath);
713
+ continue;
714
+ }
715
+ if (!entry.isFile())
716
+ continue;
717
+ if (SETTINGS_FILES_TO_SKIP.has(entry.name))
718
+ continue;
719
+ // eslint-disable-next-line no-await-in-loop
720
+ if (await pathExists(targetPath))
721
+ count++;
722
+ }
723
+ return count;
724
+ }
725
+ /**
726
+ * Display a list of folders to remove.
727
+ *
728
+ * @param targetDir - Base directory for relative path display
729
+ * @param folders - Array of folder paths
730
+ * @param label - Label for the folder type
731
+ */
732
+ displayFolderList(targetDir, folders, label) {
733
+ if (folders.length === 0)
734
+ return;
735
+ this.logInfo(`${label} to remove (${folders.length}):`);
736
+ for (const folder of folders) {
737
+ const folderName = folder.replace(targetDir + '\\', '').replace(targetDir + '/', '');
738
+ this.log(` ${folderName}/`);
739
+ }
740
+ this.log('');
741
+ }
742
+ /**
743
+ * Display all pending changes before confirmation.
744
+ *
745
+ * @param targetDir - Project root directory
746
+ * @param folders - Discovered folders and methods to remove
747
+ * @param folders.methodRuntimeFolders - Method runtime folders to remove
748
+ * @param folders.outputMethodFolders - Output method folders to remove
749
+ * @param folders.ideMethodFolders - IDE method folders to remove
750
+ * @param folders.coreRuntimeFolders - Core runtime folders to remove
751
+ * @param folders.coreIdeFilesToRemove - Number of core IDE files to remove
752
+ * @param folders.methodsToRemove - Method names being removed
753
+ */
754
+ async displayPendingChanges(targetDir, folders) {
755
+ const { methodRuntimeFolders, outputMethodFolders, ideMethodFolders, coreRuntimeFolders, methodsToRemove, coreIdeFilesToRemove, } = folders;
756
+ this.log('');
757
+ this.displayFolderList(targetDir, methodRuntimeFolders, 'Method runtime folders');
758
+ this.displayFolderList(targetDir, outputMethodFolders, 'Output folders');
759
+ this.displayFolderList(targetDir, ideMethodFolders, 'IDE method folders');
760
+ this.displayFolderList(targetDir, coreRuntimeFolders, 'Core runtime folders');
761
+ if (coreIdeFilesToRemove > 0) {
762
+ this.logInfo(`Core IDE template files to remove (${coreIdeFilesToRemove})`);
763
+ this.log('');
764
+ }
765
+ if (methodsToRemove.length > 0) {
766
+ this.logInfo(`Will reconstruct core IDE settings after removing: ${methodsToRemove.join(', ')}`);
767
+ this.log('');
768
+ }
769
+ // Check if _output will be empty after clearing
770
+ const allMethodFolders = await this.findOutputFolders(targetDir);
771
+ if (allMethodFolders.length > 0 && allMethodFolders.length === outputMethodFolders.length) {
772
+ this.logInfo(`${OUTPUT_FOLDER_NAME}/ folder will be removed (will be empty)`);
773
+ this.log('');
774
+ }
775
+ // Check if IDE folders might be removed after clearing
776
+ const [willClaudeFolderBeEmpty, willCodexFolderBeEmpty, willDevinFolderBeEmpty, willWindsurfFolderBeEmpty] = await Promise.all([
777
+ checkIdeRemovalEligibility(targetDir, IDE_FOLDERS.claude, ideMethodFolders),
778
+ checkIdeRemovalEligibility(targetDir, IDE_FOLDERS.codex, ideMethodFolders),
779
+ checkIdeRemovalEligibility(targetDir, IDE_FOLDERS.devin, ideMethodFolders),
780
+ checkIdeRemovalEligibility(targetDir, IDE_FOLDERS.windsurf, ideMethodFolders),
781
+ ]);
782
+ if (willClaudeFolderBeEmpty) {
783
+ this.logInfo(`${IDE_FOLDERS.claude.root}/ folder will be removed (will be empty)`);
784
+ this.log('');
785
+ }
786
+ if (willCodexFolderBeEmpty) {
787
+ this.logInfo(`${IDE_FOLDERS.codex.root}/ folder will be removed (will be empty)`);
788
+ this.log('');
789
+ }
790
+ if (willDevinFolderBeEmpty) {
791
+ this.logInfo(`${IDE_FOLDERS.devin.root}/ folder will be removed (will be empty)`);
792
+ this.log('');
793
+ }
794
+ if (willWindsurfFolderBeEmpty) {
795
+ this.logInfo(`${IDE_FOLDERS.windsurf.root}/ folder will be removed (will be empty)`);
796
+ this.log('');
797
+ }
798
+ // Compute git exclude changes for dry-run display
799
+ const gitDir = await resolveGitDir(targetDir);
800
+ const excludeSimulation = gitDir ? await computeExcludeRemovals(gitDir, targetDir) : { toRemove: [], toKeep: [] };
801
+ if (excludeSimulation.toRemove.length > 0 || excludeSimulation.toKeep.length > 0) {
802
+ this.logInfo('Git exclude changes:');
803
+ for (const { entry, reason } of excludeSimulation.toKeep) {
804
+ this.log(` keep ${entry}/ (${reason})`);
805
+ }
806
+ for (const entry of excludeSimulation.toRemove) {
807
+ this.log(` remove ${entry}/`);
808
+ }
809
+ this.log('');
810
+ }
811
+ }
812
+ /**
813
+ * Delete all discovered folders in parallel.
814
+ *
815
+ * @param methodRuntimeFolders - Method runtime folders to delete
816
+ * @param outputMethodFolders - Output method folders to delete
817
+ * @param ideMethodFolders - IDE method folders to delete
818
+ * @returns Count of successfully deleted folders by type
819
+ */
820
+ async executeFolderDeletion(methodRuntimeFolders, outputMethodFolders, ideMethodFolders, coreRuntimeFolders) {
821
+ const deleteFolder = async (folder, type) => {
822
+ try {
823
+ await removeDirectory(folder);
824
+ this.logDebug(`Removed ${type} folder: ${folder}`);
825
+ return { success: true, type };
826
+ }
827
+ catch (error) {
828
+ const err = error;
829
+ this.logWarning(`Failed to delete ${folder}: ${err.message}`);
830
+ return { success: false, type };
831
+ }
832
+ };
833
+ const deleteResults = await Promise.all([
834
+ ...methodRuntimeFolders.map((f) => deleteFolder(f, 'method runtime')),
835
+ ...outputMethodFolders.map((f) => deleteFolder(f, 'output')),
836
+ ...ideMethodFolders.map((f) => deleteFolder(f, 'IDE method')),
837
+ ...coreRuntimeFolders.map((f) => deleteFolder(f, 'core runtime')),
838
+ ]);
839
+ return {
840
+ deletedMethodRuntime: deleteResults.filter((r) => r.success && r.type === 'method runtime').length,
841
+ deletedOutput: deleteResults.filter((r) => r.success && r.type === 'output').length,
842
+ deletedIde: deleteResults.filter((r) => r.success && r.type === 'IDE method').length,
843
+ deletedCoreRuntime: deleteResults.filter((r) => r.success && r.type === 'core runtime').length,
844
+ };
845
+ }
846
+ /**
847
+ * Extract method names from runtime folder names (e.g., _gsd -> gsd).
848
+ *
849
+ * @param methodRuntimeFolders - Array of runtime folder paths
850
+ * @returns Array of method names
851
+ */
852
+ extractMethodNames(methodRuntimeFolders) {
853
+ const methods = [];
854
+ for (const folder of methodRuntimeFolders) {
855
+ const folderName = folder.split(/[/\\]/).pop() || '';
856
+ if (folderName.startsWith('_')) {
857
+ methods.push(folderName.slice(1));
858
+ }
859
+ }
860
+ return methods;
861
+ }
862
+ async findCoreRuntimeFolders(targetDir) {
863
+ const containerDir = join(targetDir, AIWCLI_CONTAINER);
864
+ const paths = CORE_RUNTIME_FOLDERS.map((name) => join(containerDir, name));
865
+ const checks = await Promise.all(paths.map((p) => pathExists(p)));
866
+ return paths.filter((_, index) => checks[index]);
867
+ }
868
+ /**
869
+ * Find all IDE method folders by scanning subdirectories of each IDE root
870
+ * for children matching installed method names.
871
+ *
872
+ * For example, finds .claude/commands/cc-native/, .claude/skills/cc-native/,
873
+ * .windsurf/workflows/cc-native/ — without hardcoding which subdirectories exist.
874
+ *
875
+ * @param targetDir - Directory to search in
876
+ * @param template - Optional template/method name to filter by
877
+ * @returns Array of IDE method folder paths
878
+ */
879
+ async findIdeMethodFolders(targetDir, template) {
880
+ // Build method set: from --template flag, or from installed methods
881
+ const methodNames = new Set(template ? [template] : await getInstalledMethods(targetDir));
882
+ if (methodNames.size === 0) {
883
+ return [];
884
+ }
885
+ // For each IDE root, scan all subdirectories for children matching method names
886
+ const ideRoots = Object.values(IDE_FOLDERS).map((ide) => join(targetDir, ide.root));
887
+ const ideResults = await Promise.all(ideRoots.map(async (ideRoot) => {
888
+ // Get all subdirectories of IDE root (e.g., .claude/commands/, .claude/skills/)
889
+ try {
890
+ const topEntries = await fs.readdir(ideRoot, { withFileTypes: true });
891
+ const subdirs = topEntries.filter((e) => e.isDirectory());
892
+ // For each subdirectory, check for method-named children
893
+ const subResults = await Promise.all(subdirs.map(async (subdir) => {
894
+ const subdirPath = join(ideRoot, subdir.name);
895
+ try {
896
+ const entries = await fs.readdir(subdirPath, { withFileTypes: true });
897
+ return entries
898
+ .filter((entry) => entry.isDirectory() && methodNames.has(entry.name))
899
+ .map((entry) => join(subdirPath, entry.name));
900
+ }
901
+ catch {
902
+ return [];
903
+ }
904
+ }));
905
+ return subResults.flat();
906
+ }
907
+ catch {
908
+ return [];
909
+ }
910
+ }));
911
+ return ideResults.flat();
912
+ }
913
+ /**
914
+ * Find all method runtime folders in the target directory.
915
+ * Looks for .aiwcli/_{method}/ structure (e.g., .aiwcli/_gsd/, .aiwcli/_bmad/).
916
+ *
917
+ * @param targetDir - Directory to search in
918
+ * @param template - Optional template/method name to filter by (e.g., 'bmad', 'gsd')
919
+ * @returns Array of method runtime folder paths
920
+ */
921
+ async findMethodRuntimeFolders(targetDir, template) {
922
+ const foundFolders = [];
923
+ const containerDir = join(targetDir, AIWCLI_CONTAINER);
924
+ try {
925
+ const entries = await fs.readdir(containerDir, { withFileTypes: true });
926
+ for (const entry of entries) {
927
+ if (!entry.isDirectory() ||
928
+ !entry.name.startsWith('_') ||
929
+ entry.name === OUTPUT_FOLDER_NAME ||
930
+ entry.name === '_core' ||
931
+ false) {
932
+ continue;
933
+ }
934
+ // If template specified, only include matching folder
935
+ if (template && entry.name !== `_${template}`) {
936
+ continue;
937
+ }
938
+ foundFolders.push(join(containerDir, entry.name));
939
+ }
940
+ }
941
+ catch {
942
+ // Directory can't be read - return empty
943
+ }
944
+ return foundFolders;
945
+ }
946
+ /**
947
+ * Find all method output folders in the target directory.
948
+ * Looks for _output/{method}/ structure at project root.
949
+ *
950
+ * @param targetDir - Directory to search in
951
+ * @param template - Optional template/method name to filter by (e.g., 'bmad', 'gsd')
952
+ * @returns Array of output folder paths
953
+ */
954
+ async findOutputFolders(targetDir, template) {
955
+ const outputDir = join(targetDir, OUTPUT_FOLDER_NAME);
956
+ // Check if _output folder exists
957
+ try {
958
+ const stat = await fs.stat(outputDir);
959
+ if (!stat.isDirectory()) {
960
+ return [];
961
+ }
962
+ }
963
+ catch {
964
+ // _output folder doesn't exist
965
+ return [];
966
+ }
967
+ // If template specified, only look for that specific method folder
968
+ if (template) {
969
+ const methodPath = join(outputDir, template);
970
+ try {
971
+ const stat = await fs.stat(methodPath);
972
+ if (stat.isDirectory()) {
973
+ return [methodPath];
974
+ }
975
+ }
976
+ catch {
977
+ // Method folder doesn't exist
978
+ }
979
+ return [];
980
+ }
981
+ // No template filter - find all method folders within _output
982
+ const foundFolders = [];
983
+ try {
984
+ const entries = await fs.readdir(outputDir, { withFileTypes: true });
985
+ for (const entry of entries) {
986
+ if (entry.isDirectory() && !PROTECTED_OUTPUT_DIRS.has(entry.name)) {
987
+ foundFolders.push(join(outputDir, entry.name));
988
+ }
989
+ }
990
+ }
991
+ catch {
992
+ // Directory can't be read - return empty
993
+ }
994
+ return foundFolders;
995
+ }
996
+ async getCoreTemplatePathSafe() {
997
+ try {
998
+ return await getTemplatePath(CORE_TEMPLATE_NAME);
999
+ }
1000
+ catch {
1001
+ return null;
1002
+ }
1003
+ }
1004
+ /**
1005
+ * Perform all post-deletion cleanup: empty dir removal, git exclude, settings, IDE folders.
1006
+ *
1007
+ * @param targetDir - Project root directory
1008
+ * @param methodsToRemove - Method names being removed
1009
+ * @returns Cleanup result state
1010
+ */
1011
+ async performPostDeleteCleanup(targetDir, methodsToRemove, isFullClear) {
1012
+ const containerDir = join(targetDir, AIWCLI_CONTAINER);
1013
+ const outputDir = join(targetDir, OUTPUT_FOLDER_NAME);
1014
+ let removedCoreIdeFiles = 0;
1015
+ let removedOutputDir = false;
1016
+ let removedAiwcliContainer = false;
1017
+ if (isFullClear) {
1018
+ // Force-delete .aiwcli/ entirely on full clear.
1019
+ try {
1020
+ await fs.rm(containerDir, { recursive: true, force: true });
1021
+ removedAiwcliContainer = true;
1022
+ this.logDebug(`Force-deleted ${AIWCLI_CONTAINER}/ folder`);
1023
+ }
1024
+ catch {
1025
+ // Directory may not exist
1026
+ }
1027
+ }
1028
+ else {
1029
+ // Check if .aiwcli container is now empty and remove it.
1030
+ removedAiwcliContainer = await tryRemoveEmptyDir(containerDir);
1031
+ if (removedAiwcliContainer) {
1032
+ this.logDebug(`Removed empty ${AIWCLI_CONTAINER}/ folder`);
1033
+ }
1034
+ }
1035
+ // Check if the root _output folder is now empty and remove it.
1036
+ removedOutputDir = await tryRemoveEmptyDir(outputDir);
1037
+ if (removedOutputDir) {
1038
+ this.logDebug(`Removed empty ${OUTPUT_FOLDER_NAME}/ folder`);
1039
+ }
1040
+ if (isFullClear) {
1041
+ removedCoreIdeFiles = await this.removeCoreIdeContent(targetDir);
1042
+ }
1043
+ // Reconstruct IDE settings
1044
+ let { updatedClaudeSettings, updatedWindsurfSettings } = await this.reconstructSettingsAfterRemoval(targetDir, methodsToRemove, isFullClear);
1045
+ // Clean up backup files
1046
+ await this.cleanupBackupFiles(targetDir);
1047
+ // Check if IDE folders should be fully deleted
1048
+ const removedClaudeDir = await this.tryRemoveIdeFolder(targetDir, IDE_FOLDERS.claude);
1049
+ if (removedClaudeDir)
1050
+ updatedClaudeSettings = false;
1051
+ const removedCodexDir = await this.tryRemoveIdeFolder(targetDir, IDE_FOLDERS.codex);
1052
+ const removedDevinDir = await this.tryRemoveIdeFolder(targetDir, IDE_FOLDERS.devin);
1053
+ const removedWindsurfDir = await this.tryRemoveIdeFolder(targetDir, IDE_FOLDERS.windsurf);
1054
+ if (removedWindsurfDir)
1055
+ updatedWindsurfSettings = false;
1056
+ // Smart git exclude removal must happen after any now-empty IDE folders are deleted.
1057
+ const gitExcludeUpdated = await this.cleanupGitExclude(targetDir, isFullClear);
1058
+ return {
1059
+ removedCoreIdeFiles,
1060
+ removedOutputDir, removedAiwcliContainer, removedClaudeDir, removedCodexDir, removedDevinDir, removedWindsurfDir,
1061
+ updatedClaudeSettings, updatedWindsurfSettings, gitExcludeUpdated,
1062
+ };
1063
+ }
1064
+ /**
1065
+ * Reconstruct IDE settings after method removal.
1066
+ *
1067
+ * @param targetDir - Project root directory
1068
+ * @param methodsToRemove - Methods being removed
1069
+ * @returns Which IDE settings were updated
1070
+ */
1071
+ async reconstructSettingsAfterRemoval(targetDir, methodsToRemove, isFullClear) {
1072
+ let updatedClaudeSettings = false;
1073
+ let updatedWindsurfSettings = false;
1074
+ if (methodsToRemove.length > 0) {
1075
+ for (const method of methodsToRemove) {
1076
+ await markMethodRemoved(targetDir, method); // eslint-disable-line no-await-in-loop
1077
+ }
1078
+ }
1079
+ if (isFullClear) {
1080
+ await deleteInstallStateIfPresent(targetDir);
1081
+ await this.stripAiwSettingsForFullClear(targetDir);
1082
+ }
1083
+ else if (methodsToRemove.length > 0) {
1084
+ const remainingTemplates = (await getInstalledMethods(targetDir)).filter((method) => !methodsToRemove.includes(method));
1085
+ const ides = [];
1086
+ if (await pathExists(join(targetDir, IDE_FOLDERS.claude.root)))
1087
+ ides.push('claude');
1088
+ if (await pathExists(join(targetDir, IDE_FOLDERS.windsurf.root)))
1089
+ ides.push('windsurf');
1090
+ if (ides.length > 0) {
1091
+ await reconstructIdeSettings(targetDir, remainingTemplates, ides);
1092
+ if (ides.includes('claude')) {
1093
+ this.logDebug('Reconstructed .claude/settings.json (backup created)');
1094
+ updatedClaudeSettings = true;
1095
+ }
1096
+ if (ides.includes('windsurf')) {
1097
+ this.logDebug('Reconstructed .windsurf/hooks.json (backup created)');
1098
+ updatedWindsurfSettings = true;
1099
+ }
1100
+ }
1101
+ }
1102
+ const currentState = await readInstallState(targetDir);
1103
+ if (currentState && Object.keys(currentState.methods).length === 0 && !currentState.core.installed) {
1104
+ await deleteInstallStateIfPresent(targetDir);
1105
+ }
1106
+ return { updatedClaudeSettings, updatedWindsurfSettings };
1107
+ }
1108
+ async removeCoreIdeContent(targetDir) {
1109
+ const coreTemplatePath = await this.getCoreTemplatePathSafe();
1110
+ if (!coreTemplatePath)
1111
+ return 0;
1112
+ let removedFiles = 0;
1113
+ for (const ide of Object.values(IDE_FOLDERS)) {
1114
+ const sourceIdeRoot = join(coreTemplatePath, ide.root);
1115
+ const targetIdeRoot = join(targetDir, ide.root);
1116
+ // eslint-disable-next-line no-await-in-loop
1117
+ if (!(await pathExists(sourceIdeRoot)) || !(await pathExists(targetIdeRoot)))
1118
+ continue;
1119
+ // eslint-disable-next-line no-await-in-loop
1120
+ removedFiles += await removeMatchingFiles(sourceIdeRoot, targetIdeRoot);
1121
+ }
1122
+ return removedFiles;
1123
+ }
1124
+ /**
1125
+ * Report the results of a clear operation.
1126
+ *
1127
+ * @param deleteCounts - Counts of deleted folders by type
1128
+ * @param deleteCounts.deletedMethodRuntime - Number of method runtime folders deleted
1129
+ * @param deleteCounts.deletedOutput - Number of output folders deleted
1130
+ * @param deleteCounts.deletedIde - Number of IDE method folders deleted
1131
+ * @param deleteCounts.deletedCoreRuntime - Number of core runtime folders deleted
1132
+ * @param cleanup - Cleanup operation results
1133
+ * @param cleanup.gitExcludeUpdated - Whether git exclude was updated
1134
+ * @param cleanup.removedOutputDir - Whether _output dir was removed
1135
+ * @param cleanup.removedAiwcliContainer - Whether .aiwcli dir was removed
1136
+ * @param cleanup.removedClaudeDir - Whether .claude dir was removed
1137
+ * @param cleanup.removedCodexDir - Whether .codex dir was removed
1138
+ * @param cleanup.removedDevinDir - Whether .devin dir was removed
1139
+ * @param cleanup.removedWindsurfDir - Whether .windsurf dir was removed
1140
+ * @param cleanup.removedCoreIdeFiles - Number of core IDE files removed
1141
+ * @param cleanup.updatedClaudeSettings - Whether Claude settings were updated
1142
+ * @param cleanup.updatedWindsurfSettings - Whether Windsurf settings were updated
1143
+ */
1144
+ reportClearResults(deleteCounts, cleanup) {
1145
+ this.log('');
1146
+ const parts = [];
1147
+ if (deleteCounts.deletedMethodRuntime > 0)
1148
+ parts.push(`${deleteCounts.deletedMethodRuntime} method runtime folder(s)`);
1149
+ if (deleteCounts.deletedOutput > 0)
1150
+ parts.push(`${deleteCounts.deletedOutput} output folder(s)`);
1151
+ if (deleteCounts.deletedIde > 0)
1152
+ parts.push(`${deleteCounts.deletedIde} IDE method folder(s)`);
1153
+ if (deleteCounts.deletedCoreRuntime > 0)
1154
+ parts.push(`${deleteCounts.deletedCoreRuntime} core runtime folder(s)`);
1155
+ if (cleanup.removedCoreIdeFiles > 0)
1156
+ parts.push(`${cleanup.removedCoreIdeFiles} core IDE file(s)`);
1157
+ if (cleanup.removedOutputDir)
1158
+ parts.push(`${OUTPUT_FOLDER_NAME}/ folder`);
1159
+ if (cleanup.removedAiwcliContainer)
1160
+ parts.push(`${AIWCLI_CONTAINER}/ folder`);
1161
+ if (cleanup.removedClaudeDir)
1162
+ parts.push(`${IDE_FOLDERS.claude.root}/ folder`);
1163
+ if (cleanup.removedCodexDir)
1164
+ parts.push(`${IDE_FOLDERS.codex.root}/ folder`);
1165
+ if (cleanup.removedDevinDir)
1166
+ parts.push(`${IDE_FOLDERS.devin.root}/ folder`);
1167
+ if (cleanup.removedWindsurfDir)
1168
+ parts.push(`${IDE_FOLDERS.windsurf.root}/ folder`);
1169
+ this.logSuccess(`Cleared: ${parts.join(', ')}.`);
1170
+ if (cleanup.gitExcludeUpdated) {
1171
+ this.logSuccess('Updated git exclude.');
1172
+ }
1173
+ if (cleanup.updatedClaudeSettings) {
1174
+ this.logSuccess('Updated .claude/settings.json (backup: settings.json.backup).');
1175
+ }
1176
+ if (cleanup.updatedWindsurfSettings) {
1177
+ this.logSuccess('Updated .windsurf/hooks.json (backup: hooks.json.backup).');
1178
+ }
1179
+ }
1180
+ async resolveMethodsToRemove(targetDir, template, methodRuntimeFolders) {
1181
+ if (template)
1182
+ return [template];
1183
+ const installedMethods = new Set(await getInstalledMethods(targetDir));
1184
+ const discoveredFromFolders = this.extractMethodNames(methodRuntimeFolders);
1185
+ for (const method of discoveredFromFolders) {
1186
+ installedMethods.add(method);
1187
+ }
1188
+ return [...installedMethods];
1189
+ }
1190
+ async stripAiwSettingsForFullClear(targetDir) {
1191
+ const ops = Object.values(IDE_FOLDERS).map(async (ide) => {
1192
+ if (!ide.settingsFile)
1193
+ return;
1194
+ const settingsPath = join(targetDir, ide.root, ide.settingsFile);
1195
+ try {
1196
+ const raw = await fs.readFile(settingsPath, 'utf8');
1197
+ const parsed = JSON.parse(raw);
1198
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
1199
+ return;
1200
+ delete parsed.hooks;
1201
+ delete parsed.statusLine;
1202
+ delete parsed.fileSuggestion;
1203
+ delete parsed.methods;
1204
+ if (ide.root === IDE_FOLDERS.claude.root) {
1205
+ if (parsed.env && typeof parsed.env === 'object' && !Array.isArray(parsed.env)) {
1206
+ delete parsed.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS;
1207
+ if (Object.keys(parsed.env).length === 0)
1208
+ delete parsed.env;
1209
+ }
1210
+ if (parsed.permissions && typeof parsed.permissions === 'object' && !Array.isArray(parsed.permissions)) {
1211
+ const permissionKeys = Object.keys(parsed.permissions);
1212
+ const hasOnlyAllowDeny = permissionKeys.every((key) => key === 'allow' || key === 'deny');
1213
+ const allow = Array.isArray(parsed.permissions.allow) ? parsed.permissions.allow : [];
1214
+ const deny = Array.isArray(parsed.permissions.deny) ? parsed.permissions.deny : [];
1215
+ if (hasOnlyAllowDeny && allow.length === 0 && deny.length === 0)
1216
+ delete parsed.permissions;
1217
+ }
1218
+ }
1219
+ await fs.writeFile(settingsPath, JSON.stringify(parsed, null, 2) + '\n', 'utf8');
1220
+ }
1221
+ catch {
1222
+ // Ignore invalid/missing settings files
1223
+ }
1224
+ });
1225
+ await Promise.all(ops);
1226
+ }
1227
+ /**
1228
+ * Try to remove an IDE folder if it should be deleted (empty settings + empty subfolders).
1229
+ *
1230
+ * @param targetDir - Project root directory
1231
+ * @param ideFolder - IDE folder configuration
1232
+ * @param ideFolder.root - Root folder name (e.g., '.claude')
1233
+ * @param ideFolder.settingsFile - Settings file name (e.g., 'settings.json')
1234
+ * @returns True if the folder was removed
1235
+ */
1236
+ async tryRemoveIdeFolder(targetDir, ideFolder) {
1237
+ if (!(await shouldDeleteIdeFolder(targetDir, ideFolder)))
1238
+ return false;
1239
+ const dirPath = join(targetDir, ideFolder.root);
1240
+ try {
1241
+ await removeDirectory(dirPath);
1242
+ this.logDebug(`Removed empty ${ideFolder.root}/ folder`);
1243
+ return true;
1244
+ }
1245
+ catch {
1246
+ return false;
1247
+ }
1248
+ }
1249
+ }