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