aiwcli 0.10.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/bin/run.js +1 -1
  2. package/dist/commands/clear.d.ts +11 -6
  3. package/dist/commands/clear.js +229 -381
  4. package/dist/commands/init/index.d.ts +1 -17
  5. package/dist/commands/init/index.js +22 -107
  6. package/dist/lib/gitignore-manager.d.ts +32 -0
  7. package/dist/lib/gitignore-manager.js +141 -2
  8. package/dist/lib/template-installer.d.ts +7 -12
  9. package/dist/lib/template-installer.js +69 -193
  10. package/dist/lib/template-settings-reconstructor.d.ts +35 -0
  11. package/dist/lib/template-settings-reconstructor.js +130 -0
  12. package/dist/templates/CLAUDE.md +8 -8
  13. package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
  14. package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
  15. package/dist/templates/_shared/.claude/settings.json +7 -7
  16. package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
  17. package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
  18. package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
  19. package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
  20. package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
  21. package/dist/templates/_shared/hooks-ts/session_end.ts +104 -0
  22. package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
  23. package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
  24. package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
  25. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
  26. package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
  27. package/dist/templates/_shared/lib-ts/base/atomic-write.ts +138 -0
  28. package/dist/templates/_shared/lib-ts/base/constants.ts +306 -0
  29. package/dist/templates/_shared/lib-ts/base/git-state.ts +58 -0
  30. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +439 -0
  31. package/dist/templates/_shared/lib-ts/base/inference.ts +252 -0
  32. package/dist/templates/_shared/lib-ts/base/logger.ts +250 -0
  33. package/dist/templates/_shared/lib-ts/base/state-io.ts +116 -0
  34. package/dist/templates/_shared/lib-ts/base/stop-words.ts +184 -0
  35. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +162 -0
  36. package/dist/templates/_shared/lib-ts/base/utils.ts +184 -0
  37. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +438 -0
  38. package/dist/templates/_shared/lib-ts/context/context-selector.ts +515 -0
  39. package/dist/templates/_shared/lib-ts/context/context-store.ts +707 -0
  40. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +316 -0
  41. package/dist/templates/_shared/lib-ts/context/task-tracker.ts +185 -0
  42. package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +216 -0
  43. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +159 -0
  44. package/dist/templates/_shared/lib-ts/package.json +21 -0
  45. package/dist/templates/_shared/lib-ts/templates/formatters.ts +104 -0
  46. package/dist/templates/_shared/{lib/templates/plan_context.py → lib-ts/templates/plan-context.ts} +14 -22
  47. package/dist/templates/_shared/lib-ts/tsconfig.json +13 -0
  48. package/dist/templates/_shared/lib-ts/types.ts +164 -0
  49. package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
  50. package/dist/templates/_shared/scripts/resume_handoff.ts +321 -0
  51. package/dist/templates/_shared/scripts/save_handoff.ts +359 -0
  52. package/dist/templates/_shared/scripts/status_line.ts +733 -0
  53. package/dist/templates/cc-native/.claude/settings.json +175 -185
  54. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
  55. package/dist/templates/cc-native/_cc-native/agents/ARCH-EVOLUTION.md +63 -0
  56. package/dist/templates/cc-native/_cc-native/agents/ARCH-PATTERNS.md +62 -0
  57. package/dist/templates/cc-native/_cc-native/agents/ARCH-STRUCTURE.md +63 -0
  58. package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-CHAIN-TRACER.md → ASSUMPTION-TRACER.md} +6 -10
  59. package/dist/templates/cc-native/_cc-native/agents/CLARITY-AUDITOR.md +6 -10
  60. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +74 -3
  61. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-FEASIBILITY.md +67 -0
  62. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-GAPS.md +71 -0
  63. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-ORDERING.md +63 -0
  64. package/dist/templates/cc-native/_cc-native/agents/CONSTRAINT-VALIDATOR.md +73 -0
  65. package/dist/templates/cc-native/_cc-native/agents/DESIGN-ADR-VALIDATOR.md +62 -0
  66. package/dist/templates/cc-native/_cc-native/agents/DESIGN-SCALE-MATCHER.md +65 -0
  67. package/dist/templates/cc-native/_cc-native/agents/DEVILS-ADVOCATE.md +6 -9
  68. package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-PHILOSOPHY.md +87 -0
  69. package/dist/templates/cc-native/_cc-native/agents/HANDOFF-READINESS.md +5 -9
  70. package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY-DETECTOR.md → HIDDEN-COMPLEXITY.md} +6 -10
  71. package/dist/templates/cc-native/_cc-native/agents/INCREMENTAL-DELIVERY.md +67 -0
  72. package/dist/templates/cc-native/_cc-native/agents/PLAN-ORCHESTRATOR.md +91 -18
  73. package/dist/templates/cc-native/_cc-native/agents/RISK-DEPENDENCY.md +63 -0
  74. package/dist/templates/cc-native/_cc-native/agents/RISK-FMEA.md +67 -0
  75. package/dist/templates/cc-native/_cc-native/agents/RISK-PREMORTEM.md +72 -0
  76. package/dist/templates/cc-native/_cc-native/agents/RISK-REVERSIBILITY.md +75 -0
  77. package/dist/templates/cc-native/_cc-native/agents/SCOPE-BOUNDARY.md +78 -0
  78. package/dist/templates/cc-native/_cc-native/agents/SIMPLICITY-GUARDIAN.md +5 -9
  79. package/dist/templates/cc-native/_cc-native/agents/SKEPTIC.md +16 -12
  80. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-BEHAVIOR-AUDITOR.md +62 -0
  81. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-CHARACTERIZATION.md +72 -0
  82. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-FIRST-VALIDATOR.md +62 -0
  83. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-PYRAMID-ANALYZER.md +62 -0
  84. package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-COSTS.md +68 -0
  85. package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-STAKEHOLDERS.md +66 -0
  86. package/dist/templates/cc-native/_cc-native/agents/VERIFY-COVERAGE.md +75 -0
  87. package/dist/templates/cc-native/_cc-native/agents/VERIFY-STRENGTH.md +70 -0
  88. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
  89. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
  90. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +921 -0
  91. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
  92. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +157 -0
  93. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +709 -0
  94. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
  95. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +124 -0
  96. package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
  97. package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
  98. package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
  99. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +119 -0
  100. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +162 -0
  101. package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
  102. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +249 -0
  103. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +155 -0
  104. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
  105. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +106 -0
  106. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
  107. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
  108. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +243 -0
  109. package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
  110. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +310 -0
  111. package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
  112. package/dist/templates/cc-native/_cc-native/plan-review.config.json +12 -16
  113. package/oclif.manifest.json +1 -1
  114. package/package.json +1 -1
  115. package/dist/lib/template-merger.d.ts +0 -47
  116. package/dist/lib/template-merger.js +0 -162
  117. package/dist/templates/_shared/hooks/__init__.py +0 -16
  118. package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  119. package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  120. package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
  121. package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
  122. package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
  123. package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
  124. package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
  125. package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
  126. package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
  127. package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
  128. package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
  129. package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  130. package/dist/templates/_shared/hooks/archive_plan.py +0 -169
  131. package/dist/templates/_shared/hooks/context_monitor.py +0 -270
  132. package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
  133. package/dist/templates/_shared/hooks/pre_compact.py +0 -104
  134. package/dist/templates/_shared/hooks/session_end.py +0 -173
  135. package/dist/templates/_shared/hooks/session_start.py +0 -206
  136. package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
  137. package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
  138. package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
  139. package/dist/templates/_shared/lib/__init__.py +0 -1
  140. package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  141. package/dist/templates/_shared/lib/base/__init__.py +0 -65
  142. package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
  143. package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
  144. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  145. package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
  146. package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
  147. package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
  148. package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
  149. package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
  150. package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
  151. package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
  152. package/dist/templates/_shared/lib/base/constants.py +0 -358
  153. package/dist/templates/_shared/lib/base/hook_utils.py +0 -341
  154. package/dist/templates/_shared/lib/base/inference.py +0 -318
  155. package/dist/templates/_shared/lib/base/logger.py +0 -291
  156. package/dist/templates/_shared/lib/base/stop_words.py +0 -213
  157. package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
  158. package/dist/templates/_shared/lib/base/utils.py +0 -242
  159. package/dist/templates/_shared/lib/context/__init__.py +0 -102
  160. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  161. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  162. package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
  163. package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
  164. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  165. package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
  166. package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
  167. package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
  168. package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
  169. package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
  170. package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
  171. package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
  172. package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
  173. package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
  174. package/dist/templates/_shared/lib/context/context_selector.py +0 -508
  175. package/dist/templates/_shared/lib/context/context_store.py +0 -653
  176. package/dist/templates/_shared/lib/context/plan_manager.py +0 -204
  177. package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
  178. package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
  179. package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
  180. package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
  181. package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
  182. package/dist/templates/_shared/lib/templates/README.md +0 -206
  183. package/dist/templates/_shared/lib/templates/__init__.py +0 -36
  184. package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
  185. package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
  186. package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
  187. package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
  188. package/dist/templates/_shared/lib/templates/formatters.py +0 -146
  189. package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
  190. package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
  191. package/dist/templates/_shared/scripts/save_handoff.py +0 -357
  192. package/dist/templates/_shared/scripts/status_line.py +0 -701
  193. package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
  194. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
  195. package/dist/templates/cc-native/MIGRATION.md +0 -86
  196. package/dist/templates/cc-native/_cc-native/agents/ACCESSIBILITY-TESTER.md +0 -79
  197. package/dist/templates/cc-native/_cc-native/agents/ARCHITECT-REVIEWER.md +0 -48
  198. package/dist/templates/cc-native/_cc-native/agents/CODE-REVIEWER.md +0 -70
  199. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-CHECKER.md +0 -59
  200. package/dist/templates/cc-native/_cc-native/agents/CONTEXT-EXTRACTOR.md +0 -92
  201. package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-REVIEWER.md +0 -51
  202. package/dist/templates/cc-native/_cc-native/agents/FEASIBILITY-ANALYST.md +0 -57
  203. package/dist/templates/cc-native/_cc-native/agents/FRESH-PERSPECTIVE.md +0 -54
  204. package/dist/templates/cc-native/_cc-native/agents/INCENTIVE-MAPPER.md +0 -61
  205. package/dist/templates/cc-native/_cc-native/agents/PENETRATION-TESTER.md +0 -79
  206. package/dist/templates/cc-native/_cc-native/agents/PERFORMANCE-ENGINEER.md +0 -75
  207. package/dist/templates/cc-native/_cc-native/agents/PRECEDENT-FINDER.md +0 -70
  208. package/dist/templates/cc-native/_cc-native/agents/REVERSIBILITY-ANALYST.md +0 -61
  209. package/dist/templates/cc-native/_cc-native/agents/RISK-ASSESSOR.md +0 -58
  210. package/dist/templates/cc-native/_cc-native/agents/SECOND-ORDER-ANALYST.md +0 -61
  211. package/dist/templates/cc-native/_cc-native/agents/STAKEHOLDER-ADVOCATE.md +0 -55
  212. package/dist/templates/cc-native/_cc-native/agents/TRADE-OFF-ILLUMINATOR.md +0 -204
  213. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  214. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  215. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
  216. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
  217. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
  218. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
  219. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
  220. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -869
  221. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
  222. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
  223. package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
  224. package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
  225. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  226. package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
  227. package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
  228. package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
  229. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  230. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  231. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  232. package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
  233. package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
  234. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
  235. package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
  236. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  237. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  238. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  239. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  240. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  241. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
  242. package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
  243. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
  244. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
  245. package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
  246. package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1027
  247. package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
  248. package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
  249. package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +0 -134
@@ -3,8 +3,9 @@ import { join } from 'node:path';
3
3
  import { confirm } from '@inquirer/prompts';
4
4
  import { Flags } from '@oclif/core';
5
5
  import BaseCommand from '../lib/base-command.js';
6
- import { pruneGitignoreStaleEntries } from '../lib/gitignore-manager.js';
6
+ import { computeGitignoreRemovals, pruneGitignoreStaleEntries, removeGitignoreEntries } from '../lib/gitignore-manager.js';
7
7
  import { pathExists } from '../lib/paths.js';
8
+ import { reconstructIdeSettings } from '../lib/template-settings-reconstructor.js';
8
9
  import { EXIT_CODES } from '../types/exit-codes.js';
9
10
  /**
10
11
  * Container folder for method-specific files
@@ -43,6 +44,7 @@ async function getInstalledMethodNames(targetDir) {
43
44
  for (const ide of Object.values(IDE_FOLDERS)) {
44
45
  const settingsPath = join(targetDir, ide.root, ide.settingsFile);
45
46
  try {
47
+ // eslint-disable-next-line no-await-in-loop
46
48
  const content = await fs.readFile(settingsPath, 'utf8');
47
49
  const settings = JSON.parse(content);
48
50
  if (settings.methods && typeof settings.methods === 'object') {
@@ -70,10 +72,6 @@ async function getInstalledMethodNames(targetDir) {
70
72
  }
71
73
  return methods;
72
74
  }
73
- /**
74
- * AIW gitignore section header marker
75
- */
76
- const AIW_GITIGNORE_HEADER = '# AIW Installation';
77
75
  /**
78
76
  * Check if a directory is empty.
79
77
  *
@@ -189,217 +187,6 @@ async function shouldDeleteIdeFolder(targetDir, ideFolder) {
189
187
  async function removeDirectory(dir) {
190
188
  await fs.rm(dir, { force: true, recursive: true });
191
189
  }
192
- /**
193
- * Update .gitignore to remove patterns for cleared folders.
194
- * Removes patterns from the AIW Installation section.
195
- *
196
- * @param targetDir - Directory containing .gitignore
197
- * @param foldersToRemove - Folder patterns to remove (without trailing slash)
198
- */
199
- async function updateGitignoreAfterClear(targetDir, foldersToRemove) {
200
- const gitignorePath = join(targetDir, '.gitignore');
201
- try {
202
- const content = await fs.readFile(gitignorePath, 'utf8');
203
- // Check if AIW Installation section exists
204
- if (!content.includes(AIW_GITIGNORE_HEADER)) {
205
- return;
206
- }
207
- // Split content into lines
208
- const lines = content.split('\n');
209
- const newLines = [];
210
- let inAiwSection = false;
211
- const aiwSectionLines = [];
212
- // Parse lines and identify AIW section
213
- for (const line of lines) {
214
- if (line === AIW_GITIGNORE_HEADER) {
215
- inAiwSection = true;
216
- aiwSectionLines.push(line);
217
- continue;
218
- }
219
- if (inAiwSection) {
220
- // AIW section ends at empty line or another comment header
221
- if (line === '' || (line.startsWith('#') && line !== AIW_GITIGNORE_HEADER)) {
222
- inAiwSection = false;
223
- // Process AIW section lines now
224
- const filteredAiwLines = filterAiwSection(aiwSectionLines, foldersToRemove);
225
- newLines.push(...filteredAiwLines, line);
226
- }
227
- else {
228
- aiwSectionLines.push(line);
229
- }
230
- }
231
- else {
232
- newLines.push(line);
233
- }
234
- }
235
- // Handle case where AIW section is at end of file
236
- if (inAiwSection) {
237
- const filteredAiwLines = filterAiwSection(aiwSectionLines, foldersToRemove);
238
- newLines.push(...filteredAiwLines);
239
- }
240
- // Clean up: remove AIW section entirely if only header remains
241
- const finalContent = cleanupGitignoreContent(newLines.join('\n'));
242
- await fs.writeFile(gitignorePath, finalContent, 'utf8');
243
- }
244
- catch {
245
- // .gitignore doesn't exist or can't be read
246
- }
247
- }
248
- /**
249
- * Filter AIW section lines to remove specified folders.
250
- *
251
- * @param aiwLines - Lines from AIW section (including header)
252
- * @param foldersToRemove - Folder names to remove
253
- * @returns Filtered lines
254
- */
255
- function filterAiwSection(aiwLines, foldersToRemove) {
256
- const patternsToRemove = new Set(foldersToRemove.map((f) => `${f}/`));
257
- return aiwLines.filter((line) => {
258
- // Always keep the header
259
- if (line === AIW_GITIGNORE_HEADER) {
260
- return true;
261
- }
262
- // Remove matching patterns
263
- return !patternsToRemove.has(line);
264
- });
265
- }
266
- /**
267
- * Clean up gitignore content after filtering.
268
- * Removes AIW section if empty, cleans up extra newlines.
269
- *
270
- * @param content - Gitignore content
271
- * @returns Cleaned content
272
- */
273
- function cleanupGitignoreContent(content) {
274
- const lines = content.split('\n');
275
- const newLines = [];
276
- let i = 0;
277
- while (i < lines.length) {
278
- const line = lines[i];
279
- // Check if this is an AIW header with nothing following
280
- if (line === AIW_GITIGNORE_HEADER) {
281
- // Look ahead to see if there are any patterns
282
- let hasPatterns = false;
283
- const nextLine = lines[i + 1];
284
- if (nextLine !== undefined && nextLine !== '' && !nextLine.startsWith('#')) {
285
- hasPatterns = true;
286
- }
287
- if (!hasPatterns) {
288
- // Skip the AIW header since it has no patterns
289
- i++;
290
- // Also skip any trailing empty lines that were before AIW section
291
- while (newLines.length > 0 && newLines.at(-1) === '') {
292
- newLines.pop();
293
- }
294
- continue;
295
- }
296
- }
297
- newLines.push(line);
298
- i++;
299
- }
300
- // Ensure file ends with newline but not multiple
301
- let result = newLines.join('\n');
302
- result = result.replace(/\n+$/, '\n');
303
- // Handle empty file case
304
- if (result.trim() === '') {
305
- return '';
306
- }
307
- return result;
308
- }
309
- /**
310
- * Update IDE settings file to remove method-specific entries.
311
- * Creates a backup before modifying.
312
- *
313
- * @param targetDir - Directory containing the IDE folder
314
- * @param ideFolder - IDE folder configuration (e.g., IDE_FOLDERS.claude)
315
- * @param ideFolder.root - Root folder name (e.g., '.claude')
316
- * @param ideFolder.settingsFile - Settings file name (e.g., 'settings.json')
317
- * @param methodsToRemove - Method names to remove from settings
318
- */
319
- async function updateIdeSettings(targetDir, ideFolder, methodsToRemove) {
320
- const settingsPath = join(targetDir, ideFolder.root, ideFolder.settingsFile);
321
- const result = { backedUp: false, updated: false };
322
- try {
323
- const content = await fs.readFile(settingsPath, 'utf8');
324
- const settings = JSON.parse(content);
325
- // Create backup before modifying
326
- const backupPath = `${settingsPath}.backup`;
327
- await fs.writeFile(backupPath, content, 'utf8');
328
- result.backedUp = true;
329
- // Remove method-specific entries from methods tracking object
330
- let modified = false;
331
- if (settings.methods && typeof settings.methods === 'object') {
332
- for (const method of methodsToRemove) {
333
- if (method in settings.methods) {
334
- delete settings.methods[method];
335
- modified = true;
336
- }
337
- }
338
- // Remove methods object if empty
339
- if (Object.keys(settings.methods).length === 0) {
340
- delete settings.methods;
341
- modified = true;
342
- }
343
- }
344
- // Remove method-specific hooks from hooks array
345
- if (settings.hooks && typeof settings.hooks === 'object') {
346
- for (const hookType of Object.keys(settings.hooks)) {
347
- const hookArray = settings.hooks[hookType];
348
- if (Array.isArray(hookArray)) {
349
- const filtered = hookArray.filter((hook) => {
350
- // Check if hook command references any of the methods to remove
351
- if (hook.hooks && Array.isArray(hook.hooks)) {
352
- const filteredInner = hook.hooks.filter((innerHook) => {
353
- if (typeof innerHook.command === 'string') {
354
- return !methodsToRemove.some((method) => innerHook.command?.toString().includes(`_${method}/`));
355
- }
356
- return true;
357
- });
358
- if (filteredInner.length !== hook.hooks.length) {
359
- hook.hooks = filteredInner;
360
- modified = true;
361
- }
362
- // Remove hook entry if all inner hooks were removed
363
- if (filteredInner.length === 0) {
364
- return false;
365
- }
366
- }
367
- return true;
368
- });
369
- if (filtered.length !== hookArray.length) {
370
- settings.hooks[hookType] = filtered;
371
- modified = true;
372
- }
373
- // Remove hook type if empty
374
- if (filtered.length === 0) {
375
- delete settings.hooks[hookType];
376
- modified = true;
377
- }
378
- }
379
- }
380
- // Remove hooks object if empty
381
- if (Object.keys(settings.hooks).length === 0) {
382
- delete settings.hooks;
383
- modified = true;
384
- }
385
- }
386
- if (modified) {
387
- // Write updated settings
388
- const newContent = JSON.stringify(settings, null, 2) + '\n';
389
- await fs.writeFile(settingsPath, newContent, 'utf8');
390
- result.updated = true;
391
- }
392
- else {
393
- // No changes needed, remove backup
394
- await fs.unlink(backupPath);
395
- result.backedUp = false;
396
- }
397
- }
398
- catch {
399
- // Settings file doesn't exist or can't be read
400
- }
401
- return result;
402
- }
403
190
  /**
404
191
  * Clear workflow folders, output folders, IDE method folders, and update configurations.
405
192
  */
@@ -591,6 +378,18 @@ export default class ClearCommand extends BaseCommand {
591
378
  this.logInfo(`${IDE_FOLDERS.windsurf.root}/ folder will be removed (will be empty)`);
592
379
  this.log('');
593
380
  }
381
+ // Compute gitignore changes for dry-run display
382
+ const gitignoreSimulation = await computeGitignoreRemovals(targetDir);
383
+ if (gitignoreSimulation.toRemove.length > 0 || gitignoreSimulation.toKeep.length > 0) {
384
+ this.logInfo('Gitignore changes:');
385
+ for (const { entry, reason } of gitignoreSimulation.toKeep) {
386
+ this.log(` keep ${entry}/ (${reason})`);
387
+ }
388
+ for (const entry of gitignoreSimulation.toRemove) {
389
+ this.log(` remove ${entry}/`);
390
+ }
391
+ this.log('');
392
+ }
594
393
  // Dry run - just show what would happen
595
394
  if (flags['dry-run']) {
596
395
  this.logInfo('Dry run complete. No files or folders were deleted.');
@@ -654,29 +453,48 @@ export default class ClearCommand extends BaseCommand {
654
453
  catch {
655
454
  // .aiwcli doesn't exist or can't be accessed
656
455
  }
657
- // Update .gitignore to remove .aiwcli if the container was deleted
658
- if (removedAiwcliContainer) {
659
- await updateGitignoreAfterClear(targetDir, [AIWCLI_CONTAINER]);
660
- this.logDebug('Updated .gitignore');
456
+ // Smart gitignore removal: compute what should be removed based on disk state
457
+ const { toRemove, toKeep } = await computeGitignoreRemovals(targetDir);
458
+ for (const { entry, reason } of toKeep) {
459
+ this.logDebug(`Keeping ${entry}/ in .gitignore (${reason})`);
460
+ }
461
+ if (toRemove.length > 0) {
462
+ await removeGitignoreEntries(targetDir, toRemove);
463
+ this.logDebug(`Removed from .gitignore: ${toRemove.join(', ')}`);
661
464
  }
662
- // Prune stale gitignore entries (paths that no longer exist on disk)
465
+ // Prune stale gitignore entries as safety net
663
466
  const pruned = await pruneGitignoreStaleEntries(targetDir);
664
467
  if (pruned) {
665
468
  this.logDebug('Pruned stale .gitignore entries');
666
469
  }
667
- // Update IDE settings files to remove method-specific entries
470
+ // Reconstruct IDE settings from remaining templates
668
471
  let updatedClaudeSettings = false;
669
472
  let updatedWindsurfSettings = false;
670
473
  if (methodsToRemove.length > 0) {
671
- const claudeResult = await updateIdeSettings(targetDir, IDE_FOLDERS.claude, methodsToRemove);
672
- if (claudeResult.updated) {
673
- this.logDebug('Updated .claude/settings.json (backup created)');
674
- updatedClaudeSettings = true;
675
- }
676
- const windsurfResult = await updateIdeSettings(targetDir, IDE_FOLDERS.windsurf, methodsToRemove);
677
- if (windsurfResult.updated) {
678
- this.logDebug('Updated .windsurf/hooks.json (backup created)');
679
- updatedWindsurfSettings = true;
474
+ // Remove method entries from settings files first
475
+ await this.removeMethodEntries(targetDir, methodsToRemove);
476
+ // Get remaining installed methods
477
+ const allMethods = await getInstalledMethodNames(targetDir);
478
+ // Filter out methods being removed (in case disk scan still finds them)
479
+ const remainingTemplates = [...allMethods].filter(m => !methodsToRemove.includes(m));
480
+ // Determine which IDEs need reconstruction
481
+ const ides = [];
482
+ if (await pathExists(join(targetDir, IDE_FOLDERS.claude.root))) {
483
+ ides.push('claude');
484
+ }
485
+ if (await pathExists(join(targetDir, IDE_FOLDERS.windsurf.root))) {
486
+ ides.push('windsurf');
487
+ }
488
+ if (ides.length > 0) {
489
+ await reconstructIdeSettings(targetDir, remainingTemplates, ides);
490
+ if (ides.includes('claude')) {
491
+ this.logDebug('Reconstructed .claude/settings.json (backup created)');
492
+ updatedClaudeSettings = true;
493
+ }
494
+ if (ides.includes('windsurf')) {
495
+ this.logDebug('Reconstructed .windsurf/hooks.json (backup created)');
496
+ updatedWindsurfSettings = true;
497
+ }
680
498
  }
681
499
  }
682
500
  // Check if IDE folders should be fully deleted (empty settings + empty subfolders)
@@ -733,7 +551,7 @@ export default class ClearCommand extends BaseCommand {
733
551
  parts.push(`${IDE_FOLDERS.windsurf.root}/ folder`);
734
552
  }
735
553
  this.logSuccess(`Cleared: ${parts.join(', ')}.`);
736
- if (removedAiwcliContainer || pruned) {
554
+ if (toRemove.length > 0 || pruned) {
737
555
  this.logSuccess('Updated .gitignore.');
738
556
  }
739
557
  if (updatedClaudeSettings) {
@@ -755,151 +573,6 @@ export default class ClearCommand extends BaseCommand {
755
573
  });
756
574
  }
757
575
  }
758
- /**
759
- * Extract method names from workflow folder names (e.g., _gsd -> gsd).
760
- *
761
- * @param workflowFolders - Array of workflow folder paths
762
- * @returns Array of method names
763
- */
764
- extractMethodNames(workflowFolders) {
765
- const methods = [];
766
- for (const folder of workflowFolders) {
767
- const folderName = folder.split(/[/\\]/).pop() || '';
768
- if (folderName.startsWith('_')) {
769
- methods.push(folderName.slice(1));
770
- }
771
- }
772
- return methods;
773
- }
774
- /**
775
- * Find all IDE method folders by scanning subdirectories of each IDE root
776
- * for children matching installed method names.
777
- *
778
- * For example, finds .claude/commands/cc-native/, .claude/skills/cc-native/,
779
- * .windsurf/workflows/cc-native/ — without hardcoding which subdirectories exist.
780
- *
781
- * @param targetDir - Directory to search in
782
- * @param template - Optional template/method name to filter by
783
- * @returns Array of IDE method folder paths
784
- */
785
- async findIdeMethodFolders(targetDir, template) {
786
- // Build method set: from --template flag, or from installed methods
787
- const methodNames = template ? new Set([template]) : await getInstalledMethodNames(targetDir);
788
- if (methodNames.size === 0) {
789
- return [];
790
- }
791
- // For each IDE root, scan all subdirectories for children matching method names
792
- const ideRoots = Object.values(IDE_FOLDERS).map((ide) => join(targetDir, ide.root));
793
- const ideResults = await Promise.all(ideRoots.map(async (ideRoot) => {
794
- // Get all subdirectories of IDE root (e.g., .claude/commands/, .claude/skills/)
795
- try {
796
- const topEntries = await fs.readdir(ideRoot, { withFileTypes: true });
797
- const subdirs = topEntries.filter((e) => e.isDirectory());
798
- // For each subdirectory, check for method-named children
799
- const subResults = await Promise.all(subdirs.map(async (subdir) => {
800
- const subdirPath = join(ideRoot, subdir.name);
801
- try {
802
- const entries = await fs.readdir(subdirPath, { withFileTypes: true });
803
- return entries
804
- .filter((entry) => entry.isDirectory() && methodNames.has(entry.name))
805
- .map((entry) => join(subdirPath, entry.name));
806
- }
807
- catch {
808
- return [];
809
- }
810
- }));
811
- return subResults.flat();
812
- }
813
- catch {
814
- return [];
815
- }
816
- }));
817
- return ideResults.flat();
818
- }
819
- /**
820
- * Find all output folders in the target directory.
821
- * Looks for .aiwcli/_output/{method}/ structure.
822
- *
823
- * @param targetDir - Directory to search in
824
- * @param template - Optional template/method name to filter by (e.g., 'bmad', 'gsd')
825
- * @returns Array of output folder paths
826
- */
827
- async findOutputFolders(targetDir, template) {
828
- const containerDir = join(targetDir, AIWCLI_CONTAINER);
829
- const outputDir = join(containerDir, OUTPUT_FOLDER_NAME);
830
- // Check if _output folder exists
831
- try {
832
- const stat = await fs.stat(outputDir);
833
- if (!stat.isDirectory()) {
834
- return [];
835
- }
836
- }
837
- catch {
838
- // _output folder doesn't exist
839
- return [];
840
- }
841
- // If template specified, only look for that specific method folder
842
- if (template) {
843
- const methodPath = join(outputDir, template);
844
- try {
845
- const stat = await fs.stat(methodPath);
846
- if (stat.isDirectory()) {
847
- return [methodPath];
848
- }
849
- }
850
- catch {
851
- // Method folder doesn't exist
852
- }
853
- return [];
854
- }
855
- // No template filter - find all method folders within _output
856
- const foundFolders = [];
857
- try {
858
- const entries = await fs.readdir(outputDir, { withFileTypes: true });
859
- for (const entry of entries) {
860
- if (entry.isDirectory()) {
861
- foundFolders.push(join(outputDir, entry.name));
862
- }
863
- }
864
- }
865
- catch {
866
- // Directory can't be read - return empty
867
- }
868
- return foundFolders;
869
- }
870
- /**
871
- * Find all workflow folders in the target directory.
872
- * Looks for .aiwcli/_{method}/ structure (e.g., .aiwcli/_gsd/, .aiwcli/_bmad/).
873
- *
874
- * @param targetDir - Directory to search in
875
- * @param template - Optional template/method name to filter by (e.g., 'bmad', 'gsd')
876
- * @returns Array of workflow folder paths
877
- */
878
- async findWorkflowFolders(targetDir, template) {
879
- const foundFolders = [];
880
- const containerDir = join(targetDir, AIWCLI_CONTAINER);
881
- try {
882
- const entries = await fs.readdir(containerDir, { withFileTypes: true });
883
- for (const entry of entries) {
884
- // Look for directories starting with underscore (workflow folders)
885
- if (entry.isDirectory() && entry.name.startsWith('_') && entry.name !== OUTPUT_FOLDER_NAME) {
886
- // If template specified, only include matching folder
887
- if (template) {
888
- if (entry.name === `_${template}`) {
889
- foundFolders.push(join(containerDir, entry.name));
890
- }
891
- }
892
- else {
893
- foundFolders.push(join(containerDir, entry.name));
894
- }
895
- }
896
- }
897
- }
898
- catch {
899
- // Directory can't be read - return empty
900
- }
901
- return foundFolders;
902
- }
903
576
  /**
904
577
  * Clean runtime output artifacts from _output/ at project root.
905
578
  * Handles temp files, cache files, log rotation, and archive cleanup.
@@ -907,6 +580,7 @@ export default class ClearCommand extends BaseCommand {
907
580
  * @param targetDir - Project root directory
908
581
  * @param flags - Command flags (dry-run, force)
909
582
  */
583
+ // eslint-disable-next-line complexity
910
584
  async cleanRuntimeOutput(targetDir, flags) {
911
585
  const outputDir = join(targetDir, '_output');
912
586
  if (!(await pathExists(outputDir))) {
@@ -934,7 +608,7 @@ export default class ClearCommand extends BaseCommand {
934
608
  // Log rotation: hook-log.jsonl > 1MB
935
609
  if (entry.isFile() && entry.name === 'hook-log.jsonl') {
936
610
  try {
937
- const stat = await fs.stat(entryPath);
611
+ const stat = await fs.stat(entryPath); // eslint-disable-line no-await-in-loop
938
612
  if (stat.size > 1_048_576) {
939
613
  logAction = { path: entryPath, sizeBytes: stat.size };
940
614
  }
@@ -948,7 +622,7 @@ export default class ClearCommand extends BaseCommand {
948
622
  if (entry.isDirectory() && entry.name === 'contexts') {
949
623
  const archivePath = join(entryPath, '_archive');
950
624
  try {
951
- const archiveEntries = await fs.readdir(archivePath);
625
+ const archiveEntries = await fs.readdir(archivePath); // eslint-disable-line no-await-in-loop
952
626
  if (archiveEntries.length > 0) {
953
627
  archiveDir = archivePath;
954
628
  archiveCount = archiveEntries.length;
@@ -1008,7 +682,7 @@ export default class ClearCommand extends BaseCommand {
1008
682
  let deletedCount = 0;
1009
683
  for (const item of toDelete) {
1010
684
  try {
1011
- await fs.unlink(item.path);
685
+ await fs.unlink(item.path); // eslint-disable-line no-await-in-loop
1012
686
  deletedCount++;
1013
687
  }
1014
688
  catch (error) {
@@ -1024,7 +698,7 @@ export default class ClearCommand extends BaseCommand {
1024
698
  const truncated = content.slice(-524_288);
1025
699
  // Find the first complete line
1026
700
  const firstNewline = truncated.indexOf('\n');
1027
- const cleaned = firstNewline >= 0 ? truncated.slice(firstNewline + 1) : truncated;
701
+ const cleaned = firstNewline === -1 ? truncated : truncated.slice(firstNewline + 1);
1028
702
  await fs.writeFile(logAction.path, cleaned, 'utf8');
1029
703
  this.logDebug('Rotated hook-log.jsonl');
1030
704
  }
@@ -1072,4 +746,178 @@ export default class ClearCommand extends BaseCommand {
1072
746
  this.logInfo('No changes made.');
1073
747
  }
1074
748
  }
749
+ /**
750
+ * Extract method names from workflow folder names (e.g., _gsd -> gsd).
751
+ *
752
+ * @param workflowFolders - Array of workflow folder paths
753
+ * @returns Array of method names
754
+ */
755
+ extractMethodNames(workflowFolders) {
756
+ const methods = [];
757
+ for (const folder of workflowFolders) {
758
+ const folderName = folder.split(/[/\\]/).pop() || '';
759
+ if (folderName.startsWith('_')) {
760
+ methods.push(folderName.slice(1));
761
+ }
762
+ }
763
+ return methods;
764
+ }
765
+ /**
766
+ * Find all IDE method folders by scanning subdirectories of each IDE root
767
+ * for children matching installed method names.
768
+ *
769
+ * For example, finds .claude/commands/cc-native/, .claude/skills/cc-native/,
770
+ * .windsurf/workflows/cc-native/ — without hardcoding which subdirectories exist.
771
+ *
772
+ * @param targetDir - Directory to search in
773
+ * @param template - Optional template/method name to filter by
774
+ * @returns Array of IDE method folder paths
775
+ */
776
+ async findIdeMethodFolders(targetDir, template) {
777
+ // Build method set: from --template flag, or from installed methods
778
+ const methodNames = template ? new Set([template]) : await getInstalledMethodNames(targetDir);
779
+ if (methodNames.size === 0) {
780
+ return [];
781
+ }
782
+ // For each IDE root, scan all subdirectories for children matching method names
783
+ const ideRoots = Object.values(IDE_FOLDERS).map((ide) => join(targetDir, ide.root));
784
+ const ideResults = await Promise.all(ideRoots.map(async (ideRoot) => {
785
+ // Get all subdirectories of IDE root (e.g., .claude/commands/, .claude/skills/)
786
+ try {
787
+ const topEntries = await fs.readdir(ideRoot, { withFileTypes: true });
788
+ const subdirs = topEntries.filter((e) => e.isDirectory());
789
+ // For each subdirectory, check for method-named children
790
+ const subResults = await Promise.all(subdirs.map(async (subdir) => {
791
+ const subdirPath = join(ideRoot, subdir.name);
792
+ try {
793
+ const entries = await fs.readdir(subdirPath, { withFileTypes: true });
794
+ return entries
795
+ .filter((entry) => entry.isDirectory() && methodNames.has(entry.name))
796
+ .map((entry) => join(subdirPath, entry.name));
797
+ }
798
+ catch {
799
+ return [];
800
+ }
801
+ }));
802
+ return subResults.flat();
803
+ }
804
+ catch {
805
+ return [];
806
+ }
807
+ }));
808
+ return ideResults.flat();
809
+ }
810
+ /**
811
+ * Find all output folders in the target directory.
812
+ * Looks for .aiwcli/_output/{method}/ structure.
813
+ *
814
+ * @param targetDir - Directory to search in
815
+ * @param template - Optional template/method name to filter by (e.g., 'bmad', 'gsd')
816
+ * @returns Array of output folder paths
817
+ */
818
+ async findOutputFolders(targetDir, template) {
819
+ const containerDir = join(targetDir, AIWCLI_CONTAINER);
820
+ const outputDir = join(containerDir, OUTPUT_FOLDER_NAME);
821
+ // Check if _output folder exists
822
+ try {
823
+ const stat = await fs.stat(outputDir);
824
+ if (!stat.isDirectory()) {
825
+ return [];
826
+ }
827
+ }
828
+ catch {
829
+ // _output folder doesn't exist
830
+ return [];
831
+ }
832
+ // If template specified, only look for that specific method folder
833
+ if (template) {
834
+ const methodPath = join(outputDir, template);
835
+ try {
836
+ const stat = await fs.stat(methodPath);
837
+ if (stat.isDirectory()) {
838
+ return [methodPath];
839
+ }
840
+ }
841
+ catch {
842
+ // Method folder doesn't exist
843
+ }
844
+ return [];
845
+ }
846
+ // No template filter - find all method folders within _output
847
+ const foundFolders = [];
848
+ try {
849
+ const entries = await fs.readdir(outputDir, { withFileTypes: true });
850
+ for (const entry of entries) {
851
+ if (entry.isDirectory()) {
852
+ foundFolders.push(join(outputDir, entry.name));
853
+ }
854
+ }
855
+ }
856
+ catch {
857
+ // Directory can't be read - return empty
858
+ }
859
+ return foundFolders;
860
+ }
861
+ /**
862
+ * Find all workflow folders in the target directory.
863
+ * Looks for .aiwcli/_{method}/ structure (e.g., .aiwcli/_gsd/, .aiwcli/_bmad/).
864
+ *
865
+ * @param targetDir - Directory to search in
866
+ * @param template - Optional template/method name to filter by (e.g., 'bmad', 'gsd')
867
+ * @returns Array of workflow folder paths
868
+ */
869
+ async findWorkflowFolders(targetDir, template) {
870
+ const foundFolders = [];
871
+ const containerDir = join(targetDir, AIWCLI_CONTAINER);
872
+ try {
873
+ const entries = await fs.readdir(containerDir, { withFileTypes: true });
874
+ for (const entry of entries) {
875
+ // Look for directories starting with underscore (workflow folders)
876
+ if (entry.isDirectory() && entry.name.startsWith('_') && entry.name !== OUTPUT_FOLDER_NAME) {
877
+ // If template specified, only include matching folder
878
+ if (template) {
879
+ if (entry.name === `_${template}`) {
880
+ foundFolders.push(join(containerDir, entry.name));
881
+ }
882
+ }
883
+ else {
884
+ foundFolders.push(join(containerDir, entry.name));
885
+ }
886
+ }
887
+ }
888
+ }
889
+ catch {
890
+ // Directory can't be read - return empty
891
+ }
892
+ return foundFolders;
893
+ }
894
+ /**
895
+ * Remove method entries from IDE settings files (methods tracking only).
896
+ * Settings reconstruction handles hooks/permissions; this only strips the methods object.
897
+ */
898
+ async removeMethodEntries(targetDir, methodsToRemove) {
899
+ const ops = Object.values(IDE_FOLDERS).map(async (ide) => {
900
+ const settingsPath = join(targetDir, ide.root, ide.settingsFile);
901
+ try {
902
+ const content = await fs.readFile(settingsPath, 'utf8');
903
+ const settings = JSON.parse(content);
904
+ if (settings.methods && typeof settings.methods === 'object') {
905
+ for (const method of methodsToRemove) {
906
+ if (method in settings.methods) {
907
+ delete settings.methods[method];
908
+ }
909
+ }
910
+ if (Object.keys(settings.methods).length === 0) {
911
+ delete settings.methods;
912
+ }
913
+ // Write back with methods removed (backup created by reconstructor)
914
+ await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
915
+ }
916
+ }
917
+ catch {
918
+ // Settings file doesn't exist or can't be read
919
+ }
920
+ });
921
+ await Promise.all(ops);
922
+ }
1075
923
  }