aiwcli 0.15.5 → 0.15.7

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