aiwcli 0.9.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 (204) hide show
  1. package/README.md +1248 -0
  2. package/bin/dev.cmd +3 -0
  3. package/bin/dev.js +16 -0
  4. package/bin/run.cmd +3 -0
  5. package/bin/run.js +19 -0
  6. package/dist/commands/branch.d.ts +45 -0
  7. package/dist/commands/branch.js +488 -0
  8. package/dist/commands/clean.d.ts +34 -0
  9. package/dist/commands/clean.js +186 -0
  10. package/dist/commands/clear.d.ts +51 -0
  11. package/dist/commands/clear.js +835 -0
  12. package/dist/commands/init/index.d.ts +107 -0
  13. package/dist/commands/init/index.js +565 -0
  14. package/dist/commands/launch.d.ts +21 -0
  15. package/dist/commands/launch.js +108 -0
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +1 -0
  18. package/dist/lib/base-command.d.ts +114 -0
  19. package/dist/lib/base-command.js +153 -0
  20. package/dist/lib/bmad-installer.d.ts +38 -0
  21. package/dist/lib/bmad-installer.js +145 -0
  22. package/dist/lib/claude-settings-types.d.ts +102 -0
  23. package/dist/lib/claude-settings-types.js +5 -0
  24. package/dist/lib/config.d.ts +25 -0
  25. package/dist/lib/config.js +46 -0
  26. package/dist/lib/debug.d.ts +39 -0
  27. package/dist/lib/debug.js +74 -0
  28. package/dist/lib/env-compat.d.ts +26 -0
  29. package/dist/lib/env-compat.js +35 -0
  30. package/dist/lib/errors.d.ts +126 -0
  31. package/dist/lib/errors.js +145 -0
  32. package/dist/lib/generic-merge.d.ts +74 -0
  33. package/dist/lib/generic-merge.js +105 -0
  34. package/dist/lib/git/branch.d.ts +67 -0
  35. package/dist/lib/git/branch.js +155 -0
  36. package/dist/lib/git/index.d.ts +11 -0
  37. package/dist/lib/git/index.js +13 -0
  38. package/dist/lib/git/safety-checks.d.ts +44 -0
  39. package/dist/lib/git/safety-checks.js +102 -0
  40. package/dist/lib/git/types.d.ts +31 -0
  41. package/dist/lib/git/types.js +6 -0
  42. package/dist/lib/git/worktree.d.ts +67 -0
  43. package/dist/lib/git/worktree.js +220 -0
  44. package/dist/lib/gitignore-manager.d.ts +10 -0
  45. package/dist/lib/gitignore-manager.js +60 -0
  46. package/dist/lib/hooks-merger.d.ts +28 -0
  47. package/dist/lib/hooks-merger.js +94 -0
  48. package/dist/lib/ide-path-resolver.d.ts +102 -0
  49. package/dist/lib/ide-path-resolver.js +129 -0
  50. package/dist/lib/index.d.ts +13 -0
  51. package/dist/lib/index.js +22 -0
  52. package/dist/lib/output.d.ts +51 -0
  53. package/dist/lib/output.js +76 -0
  54. package/dist/lib/paths.d.ts +66 -0
  55. package/dist/lib/paths.js +136 -0
  56. package/dist/lib/quiet.d.ts +12 -0
  57. package/dist/lib/quiet.js +17 -0
  58. package/dist/lib/settings-hierarchy.d.ts +42 -0
  59. package/dist/lib/settings-hierarchy.js +105 -0
  60. package/dist/lib/spawn.d.ts +105 -0
  61. package/dist/lib/spawn.js +157 -0
  62. package/dist/lib/spinner.d.ts +19 -0
  63. package/dist/lib/spinner.js +34 -0
  64. package/dist/lib/stdin.d.ts +48 -0
  65. package/dist/lib/stdin.js +60 -0
  66. package/dist/lib/template-installer.d.ts +92 -0
  67. package/dist/lib/template-installer.js +375 -0
  68. package/dist/lib/template-linter.d.ts +49 -0
  69. package/dist/lib/template-linter.js +173 -0
  70. package/dist/lib/template-merger.d.ts +47 -0
  71. package/dist/lib/template-merger.js +173 -0
  72. package/dist/lib/template-resolver.d.ts +20 -0
  73. package/dist/lib/template-resolver.js +60 -0
  74. package/dist/lib/terminal.d.ts +102 -0
  75. package/dist/lib/terminal.js +245 -0
  76. package/dist/lib/tty-detection.d.ts +62 -0
  77. package/dist/lib/tty-detection.js +83 -0
  78. package/dist/lib/user-utils.d.ts +5 -0
  79. package/dist/lib/user-utils.js +23 -0
  80. package/dist/lib/version.d.ts +99 -0
  81. package/dist/lib/version.js +144 -0
  82. package/dist/lib/watch-templates.d.ts +6 -0
  83. package/dist/lib/watch-templates.js +73 -0
  84. package/dist/lib/windsurf-hooks-hierarchy.d.ts +30 -0
  85. package/dist/lib/windsurf-hooks-hierarchy.js +66 -0
  86. package/dist/lib/windsurf-hooks-merger.d.ts +26 -0
  87. package/dist/lib/windsurf-hooks-merger.js +53 -0
  88. package/dist/lib/windsurf-hooks-types.d.ts +33 -0
  89. package/dist/lib/windsurf-hooks-types.js +5 -0
  90. package/dist/templates/CLAUDE.md +174 -0
  91. package/dist/templates/_shared/.claude/commands/handoff.md +14 -0
  92. package/dist/templates/_shared/.claude/settings.json +61 -0
  93. package/dist/templates/_shared/.codex/workflows/handoff.md +14 -0
  94. package/dist/templates/_shared/.windsurf/workflows/handoff.md +14 -0
  95. package/dist/templates/_shared/hooks/__init__.py +16 -0
  96. package/dist/templates/_shared/hooks/archive_plan.py +270 -0
  97. package/dist/templates/_shared/hooks/context_enforcer.py +621 -0
  98. package/dist/templates/_shared/hooks/context_monitor.py +322 -0
  99. package/dist/templates/_shared/hooks/file-suggestion.py +188 -0
  100. package/dist/templates/_shared/hooks/task_create_capture.py +194 -0
  101. package/dist/templates/_shared/hooks/task_update_capture.py +254 -0
  102. package/dist/templates/_shared/hooks/user_prompt_submit.py +157 -0
  103. package/dist/templates/_shared/lib/__init__.py +1 -0
  104. package/dist/templates/_shared/lib/base/__init__.py +49 -0
  105. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  106. package/dist/templates/_shared/lib/base/atomic_write.py +180 -0
  107. package/dist/templates/_shared/lib/base/constants.py +299 -0
  108. package/dist/templates/_shared/lib/base/inference.py +189 -0
  109. package/dist/templates/_shared/lib/base/utils.py +216 -0
  110. package/dist/templates/_shared/lib/context/__init__.py +119 -0
  111. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  112. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  113. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  114. package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
  115. package/dist/templates/_shared/lib/context/cache.py +446 -0
  116. package/dist/templates/_shared/lib/context/context_manager.py +1171 -0
  117. package/dist/templates/_shared/lib/context/discovery.py +486 -0
  118. package/dist/templates/_shared/lib/context/event_log.py +308 -0
  119. package/dist/templates/_shared/lib/context/plan_archive.py +247 -0
  120. package/dist/templates/_shared/lib/context/task_sync.py +367 -0
  121. package/dist/templates/_shared/lib/handoff/__init__.py +22 -0
  122. package/dist/templates/_shared/lib/handoff/document_generator.py +307 -0
  123. package/dist/templates/_shared/lib/templates/README.md +215 -0
  124. package/dist/templates/_shared/lib/templates/__init__.py +40 -0
  125. package/dist/templates/_shared/lib/templates/formatters.py +147 -0
  126. package/dist/templates/_shared/lib/templates/plan_context.py +119 -0
  127. package/dist/templates/_shared/scripts/save_handoff.py +99 -0
  128. package/dist/templates/_shared/workflows/handoff.md +212 -0
  129. package/dist/templates/cc-native/.claude/agents/cc-native/ACCESSIBILITY-TESTER.md +80 -0
  130. package/dist/templates/cc-native/.claude/agents/cc-native/ARCHITECT-REVIEWER.md +75 -0
  131. package/dist/templates/cc-native/.claude/agents/cc-native/ASSUMPTION-CHAIN-TRACER.md +239 -0
  132. package/dist/templates/cc-native/.claude/agents/cc-native/CLARITY-AUDITOR.md +109 -0
  133. package/dist/templates/cc-native/.claude/agents/cc-native/CODE-REVIEWER.md +71 -0
  134. package/dist/templates/cc-native/.claude/agents/cc-native/COMPLETENESS-CHECKER.md +104 -0
  135. package/dist/templates/cc-native/.claude/agents/cc-native/CONTEXT-EXTRACTOR.md +93 -0
  136. package/dist/templates/cc-native/.claude/agents/cc-native/DEVILS-ADVOCATE.md +223 -0
  137. package/dist/templates/cc-native/.claude/agents/cc-native/DOCUMENTATION-REVIEWER.md +73 -0
  138. package/dist/templates/cc-native/.claude/agents/cc-native/FEASIBILITY-ANALYST.md +93 -0
  139. package/dist/templates/cc-native/.claude/agents/cc-native/FRESH-PERSPECTIVE.md +103 -0
  140. package/dist/templates/cc-native/.claude/agents/cc-native/HANDOFF-READINESS.md +145 -0
  141. package/dist/templates/cc-native/.claude/agents/cc-native/HIDDEN-COMPLEXITY-DETECTOR.md +248 -0
  142. package/dist/templates/cc-native/.claude/agents/cc-native/INCENTIVE-MAPPER.md +235 -0
  143. package/dist/templates/cc-native/.claude/agents/cc-native/PENETRATION-TESTER.md +80 -0
  144. package/dist/templates/cc-native/.claude/agents/cc-native/PERFORMANCE-ENGINEER.md +76 -0
  145. package/dist/templates/cc-native/.claude/agents/cc-native/PLAN-ORCHESTRATOR.md +141 -0
  146. package/dist/templates/cc-native/.claude/agents/cc-native/PRECEDENT-FINDER.md +240 -0
  147. package/dist/templates/cc-native/.claude/agents/cc-native/REVERSIBILITY-ANALYST.md +211 -0
  148. package/dist/templates/cc-native/.claude/agents/cc-native/RISK-ASSESSOR.md +101 -0
  149. package/dist/templates/cc-native/.claude/agents/cc-native/SECOND-ORDER-ANALYST.md +197 -0
  150. package/dist/templates/cc-native/.claude/agents/cc-native/SIMPLICITY-GUARDIAN.md +97 -0
  151. package/dist/templates/cc-native/.claude/agents/cc-native/SKEPTIC.md +349 -0
  152. package/dist/templates/cc-native/.claude/agents/cc-native/STAKEHOLDER-ADVOCATE.md +106 -0
  153. package/dist/templates/cc-native/.claude/agents/cc-native/TRADE-OFF-ILLUMINATOR.md +205 -0
  154. package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +8 -0
  155. package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +10 -0
  156. package/dist/templates/cc-native/.claude/settings.json +119 -0
  157. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fix.md +8 -0
  158. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +8 -0
  159. package/dist/templates/cc-native/.windsurf/workflows/cc-native/implement.md +8 -0
  160. package/dist/templates/cc-native/.windsurf/workflows/cc-native/research.md +8 -0
  161. package/dist/templates/cc-native/CC-NATIVE-README.md +192 -0
  162. package/dist/templates/cc-native/MIGRATION.md +86 -0
  163. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +331 -0
  164. package/dist/templates/cc-native/_cc-native/docs/PERMISSION_REQUEST_VERIFICATION.md +147 -0
  165. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  166. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  167. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-agent-review.cpython-313.pyc +0 -0
  168. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  169. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/test_permission_request.cpython-313.pyc +0 -0
  170. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +150 -0
  171. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +746 -0
  172. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +339 -0
  173. package/dist/templates/cc-native/_cc-native/lib/__init__.py +57 -0
  174. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  175. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  176. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  177. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  178. package/dist/templates/cc-native/_cc-native/lib/async_archive.py +68 -0
  179. package/dist/templates/cc-native/_cc-native/lib/atomic_write.py +98 -0
  180. package/dist/templates/cc-native/_cc-native/lib/constants.py +45 -0
  181. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +273 -0
  182. package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +28 -0
  183. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  184. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  185. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  186. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  187. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  188. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +164 -0
  189. package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +89 -0
  190. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +119 -0
  191. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +103 -0
  192. package/dist/templates/cc-native/_cc-native/lib/state.py +251 -0
  193. package/dist/templates/cc-native/_cc-native/lib/utils.py +830 -0
  194. package/dist/templates/cc-native/_cc-native/plan-review.config.json +76 -0
  195. package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
  196. package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +151 -0
  197. package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +134 -0
  198. package/dist/templates/cc-native/_cc-native/workflows/specdev.md +9 -0
  199. package/dist/types/exit-codes.d.ts +11 -0
  200. package/dist/types/exit-codes.js +10 -0
  201. package/dist/types/index.d.ts +5 -0
  202. package/dist/types/index.js +7 -0
  203. package/oclif.manifest.json +405 -0
  204. package/package.json +109 -0
@@ -0,0 +1,107 @@
1
+ import BaseCommand from '../../lib/base-command.js';
2
+ /**
3
+ * Initialize AIW tools and integrations with specified template method.
4
+ */
5
+ export default class Init extends BaseCommand {
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ interactive: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ method: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ ide: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
12
+ debug: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ help: import("@oclif/core/interfaces").BooleanFlag<void>;
14
+ quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
+ };
16
+ run(): Promise<void>;
17
+ /**
18
+ * Copy directory recursively, excluding test files and cache
19
+ *
20
+ * @param src - Source directory path
21
+ * @param dest - Destination directory path
22
+ * @param excludeIdeFolders - If true, exclude IDE config folders (.claude, .windsurf, etc.)
23
+ */
24
+ private copyDirectory;
25
+ /**
26
+ * Get description for a template
27
+ *
28
+ * @param template - Template name
29
+ * @returns Template description
30
+ */
31
+ private getTemplateDescription;
32
+ /**
33
+ * Merge settings from multiple method templates into project settings.
34
+ * Processes methods in order, allowing later methods to override earlier ones.
35
+ *
36
+ * @param targetDir - Project directory
37
+ * @param methods - Array of method names to merge (e.g., ['_shared', 'cc-native'])
38
+ * @param ides - IDEs being configured (for IDE-specific merging)
39
+ */
40
+ private mergeMethodsSettings;
41
+ /**
42
+ * Merge Windsurf template hooks into project hooks
43
+ *
44
+ * @param targetDir - Project directory
45
+ * @param templatePath - Template source path
46
+ */
47
+ private mergeWindsurfTemplateHooks;
48
+ /**
49
+ * Check if a path exists
50
+ */
51
+ private pathExists;
52
+ /**
53
+ * Perform post-installation actions.
54
+ *
55
+ * Handles:
56
+ * - Method tracking in settings.json
57
+ * - Settings/hooks merging for all methods
58
+ * - .gitignore updates
59
+ *
60
+ * @param config - Post-install configuration
61
+ * @param config.targetDir - Project directory
62
+ * @param config.method - Method name that was installed
63
+ * @param config.ides - IDEs that were configured
64
+ * @param config.hasGit - Whether git repository exists
65
+ * @param config.foldersForGitignore - Folders to add to .gitignore
66
+ */
67
+ private performPostInstallActions;
68
+ /**
69
+ * Resolve installation configuration from flags or interactive wizard.
70
+ *
71
+ * Determines what to install based on:
72
+ * - Interactive wizard input
73
+ * - Command-line flags
74
+ * - Minimal install mode (no method specified)
75
+ *
76
+ * @param flags - Parsed command flags
77
+ * @param flags.interactive - Run interactive wizard
78
+ * @param flags.method - Template method to install
79
+ * @param flags.ide - IDEs to configure
80
+ * @param targetDir - Target directory for installation
81
+ * @param availableTemplates - List of available template names
82
+ * @returns Installation configuration or null for minimal install
83
+ */
84
+ private resolveInstallationConfig;
85
+ /**
86
+ * Run interactive setup wizard
87
+ *
88
+ * @param targetDir - Target directory for installation
89
+ * @param availableTemplates - List of available template names
90
+ * @returns Wizard configuration result
91
+ */
92
+ private runInteractiveWizard;
93
+ /**
94
+ * Check if a file should be excluded from copying
95
+ */
96
+ private shouldExcludeFile;
97
+ /**
98
+ * Track method installation in settings.json
99
+ *
100
+ * Adds method entry to the methods object with installation metadata.
101
+ *
102
+ * @param targetDir - Project directory
103
+ * @param method - Method name being installed
104
+ * @param ides - IDEs configured for this method
105
+ */
106
+ private trackMethodInstallation;
107
+ }
@@ -0,0 +1,565 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import { basename, dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { checkbox, confirm, input, select } from '@inquirer/prompts';
5
+ import { Flags } from '@oclif/core';
6
+ import BaseCommand from '../../lib/base-command.js';
7
+ import { detectUsername } from '../../lib/user-utils.js';
8
+ import { updateGitignore } from '../../lib/gitignore-manager.js';
9
+ import { mergeClaudeSettings } from '../../lib/hooks-merger.js';
10
+ import { IdePathResolver } from '../../lib/ide-path-resolver.js';
11
+ import { getTargetSettingsFile, readClaudeSettings, writeClaudeSettings } from '../../lib/settings-hierarchy.js';
12
+ import { checkTemplateStatus, installTemplate } from '../../lib/template-installer.js';
13
+ import { getAvailableTemplates, getTemplatePath } from '../../lib/template-resolver.js';
14
+ import { getTargetHooksFile, readWindsurfHooks, writeWindsurfHooks } from '../../lib/windsurf-hooks-hierarchy.js';
15
+ import { mergeWindsurfHooks } from '../../lib/windsurf-hooks-merger.js';
16
+ import { EXIT_CODES } from '../../types/exit-codes.js';
17
+ /**
18
+ * Available IDEs for configuration
19
+ */
20
+ const AVAILABLE_IDES = [
21
+ { value: 'claude', name: 'Claude Code', description: 'Anthropic Claude Code CLI' },
22
+ { value: 'windsurf', name: 'Windsurf', description: 'Codeium Windsurf IDE' },
23
+ ];
24
+ /**
25
+ * Detect if current directory is a git repository.
26
+ * Checks for .git directory existence.
27
+ */
28
+ async function detectGitRepository(targetDir) {
29
+ try {
30
+ const gitPath = join(targetDir, '.git');
31
+ await fs.access(gitPath);
32
+ return true;
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
38
+ /**
39
+ * Extract project name from directory path.
40
+ * Returns the basename of the given directory.
41
+ */
42
+ function detectProjectName(targetDir) {
43
+ return basename(targetDir);
44
+ }
45
+ /**
46
+ * Initialize AIW tools and integrations with specified template method.
47
+ */
48
+ export default class Init extends BaseCommand {
49
+ static description = 'Initialize AIW tools and integrations with specified template method';
50
+ static examples = [
51
+ '<%= config.bin %> <%= command.id %> --interactive',
52
+ '<%= config.bin %> <%= command.id %> --method cc-native',
53
+ '<%= config.bin %> <%= command.id %> --method cc-native --ide windsurf',
54
+ '<%= config.bin %> <%= command.id %> --method cc-native --ide claude --ide windsurf',
55
+ ];
56
+ static flags = {
57
+ ...BaseCommand.baseFlags,
58
+ interactive: Flags.boolean({
59
+ char: 'I',
60
+ description: 'Run interactive setup wizard',
61
+ default: false,
62
+ }),
63
+ method: Flags.string({
64
+ char: 'm',
65
+ description: 'Template method to initialize',
66
+ required: false,
67
+ }),
68
+ ide: Flags.string({
69
+ char: 'i',
70
+ default: ['claude'],
71
+ description: 'IDEs to configure (specify multiple: --ide claude --ide windsurf)',
72
+ multiple: true,
73
+ }),
74
+ };
75
+ async run() {
76
+ const { flags } = await this.parse(Init);
77
+ const targetDir = process.cwd();
78
+ try {
79
+ // Get available templates for validation
80
+ const availableTemplates = await getAvailableTemplates();
81
+ // Check git repository early (needed by both install paths)
82
+ const hasGit = await detectGitRepository(targetDir);
83
+ // Resolve installation configuration from flags or interactive wizard
84
+ const config = await this.resolveInstallationConfig(flags, targetDir, availableTemplates);
85
+ // If config is null, perform minimal install (shared folder only)
86
+ if (!config) {
87
+ this.logInfo('Performing minimal installation (_shared folder only)...');
88
+ this.log('');
89
+ // Create .aiwcli container and install _shared
90
+ const resolver = new IdePathResolver(targetDir);
91
+ const containerDir = resolver.getAiwcliContainer();
92
+ await fs.mkdir(containerDir, { recursive: true });
93
+ const sharedDestPath = resolver.getSharedFolder();
94
+ const sharedExists = await this.pathExists(sharedDestPath);
95
+ if (!sharedExists) {
96
+ const currentFilePath = fileURLToPath(import.meta.url);
97
+ const currentDir = dirname(currentFilePath);
98
+ const templatesRoot = join(dirname(dirname(currentDir)), 'templates');
99
+ const sharedSrcPath = join(templatesRoot, '_shared');
100
+ if (!(await this.pathExists(sharedSrcPath))) {
101
+ this.error(`Shared folder not found at ${sharedSrcPath}. This indicates a corrupted installation.`, {
102
+ exit: EXIT_CODES.ENVIRONMENT_ERROR,
103
+ });
104
+ }
105
+ await this.copyDirectory(sharedSrcPath, sharedDestPath, true);
106
+ this.logSuccess('✓ Installed _shared folder');
107
+ }
108
+ else {
109
+ this.logInfo('✓ _shared folder already exists - skipping');
110
+ }
111
+ // Merge settings from _shared template
112
+ await this.mergeMethodsSettings(targetDir, ['_shared'], ['claude']);
113
+ // Update .gitignore if git repository exists
114
+ if (hasGit) {
115
+ await updateGitignore(targetDir, ['.aiwcli']);
116
+ this.logSuccess('✓ .gitignore updated');
117
+ }
118
+ this.log('');
119
+ this.logSuccess('✓ Minimal installation completed successfully');
120
+ this.log('');
121
+ this.logInfo('Next steps:');
122
+ this.logInfo(' aiw init --method <template> Install a full template method (cc-native)');
123
+ this.logInfo(' aiw init --interactive Run interactive setup wizard');
124
+ return;
125
+ }
126
+ const { method, ides, username, projectName } = config;
127
+ // Validate write permissions
128
+ try {
129
+ const testFile = join(targetDir, '.aiwcli-write-test');
130
+ await fs.writeFile(testFile, '', 'utf8');
131
+ await fs.unlink(testFile);
132
+ }
133
+ catch {
134
+ this.error('Permission denied. Cannot write to current directory.', {
135
+ exit: EXIT_CODES.ENVIRONMENT_ERROR,
136
+ });
137
+ }
138
+ // Get template path
139
+ const templatePath = await getTemplatePath(method);
140
+ // Check what already exists vs what's missing
141
+ const status = await checkTemplateStatus(templatePath, targetDir, ides, method);
142
+ this.logInfo(`Installing ${method} template for project: ${projectName}`);
143
+ this.logInfo(`Detected user: ${username}`);
144
+ this.logInfo(`Target IDEs: ${ides.join(', ')}`);
145
+ // Report existing items
146
+ if (status.existing.length > 0) {
147
+ this.log('');
148
+ this.logInfo('Already present (will be skipped):');
149
+ for (const item of status.existing) {
150
+ const suffix = item.isDirectory ? '/' : '';
151
+ this.log(` - ${item.name}${suffix}`);
152
+ }
153
+ }
154
+ // Report missing items that will be installed
155
+ if (status.missing.length > 0) {
156
+ this.log('');
157
+ this.logInfo('Will be installed:');
158
+ for (const item of status.missing) {
159
+ const suffix = item.isDirectory ? '/' : '';
160
+ this.log(` - ${item.name}${suffix}`);
161
+ }
162
+ }
163
+ // If everything already exists, report and continue (don't block)
164
+ if (status.missing.length === 0) {
165
+ this.log('');
166
+ this.logInfo('All template items already exist. Nothing new to install.');
167
+ this.log('');
168
+ // Still update gitignore and merge hooks if needed
169
+ }
170
+ this.log('');
171
+ // Install template with selective installation (skip existing items)
172
+ const result = await installTemplate({
173
+ templateName: method,
174
+ targetDir,
175
+ ides,
176
+ username,
177
+ projectName,
178
+ templatePath,
179
+ }, true);
180
+ // Collect all folders that need gitignore entries
181
+ // The .aiwcli/ container holds all template infrastructure and runtime data
182
+ const foldersForGitignore = ['.aiwcli'];
183
+ // Report installation results
184
+ if (result.installedFolders.length > 0) {
185
+ this.logSuccess(`✓ Installed: ${result.installedFolders.join(', ')}`);
186
+ }
187
+ if (result.mergedFolders.length > 0) {
188
+ this.logSuccess(`✓ Merged content into: ${result.mergedFolders.join(', ')} (${result.mergedFileCount} files)`);
189
+ }
190
+ if (result.skippedFolders.length > 0) {
191
+ this.logInfo(`✓ Skipped (already exist): ${result.skippedFolders.join(', ')}`);
192
+ }
193
+ // Perform post-installation actions (settings tracking, hook merging, gitignore updates)
194
+ await this.performPostInstallActions({
195
+ targetDir,
196
+ method,
197
+ ides,
198
+ hasGit,
199
+ foldersForGitignore,
200
+ });
201
+ this.log('');
202
+ this.logSuccess(`✓ ${method} initialized successfully`);
203
+ this.log('');
204
+ this.logInfo('Next steps:');
205
+ this.logInfo(' aiw launch Start Claude Code with agents');
206
+ }
207
+ catch (error) {
208
+ const err = error;
209
+ // Categorize errors for better user feedback
210
+ // Check error codes first, then fall back to message matching
211
+ if (err.code === 'EACCES' || err.code === 'EPERM') {
212
+ this.error(`Permission denied. Cannot write to current directory. ${err.message}`, {
213
+ exit: EXIT_CODES.ENVIRONMENT_ERROR,
214
+ });
215
+ }
216
+ if (err.code === 'ENOENT' || err.message?.includes('not found') || err.message?.includes('not available')) {
217
+ this.error(err.message || 'Resource not found', { exit: EXIT_CODES.INVALID_USAGE });
218
+ }
219
+ // Generic error fallback
220
+ this.error(`Installation failed: ${err.message}`, {
221
+ exit: EXIT_CODES.GENERAL_ERROR,
222
+ });
223
+ }
224
+ }
225
+ /**
226
+ * Copy directory recursively, excluding test files and cache
227
+ *
228
+ * @param src - Source directory path
229
+ * @param dest - Destination directory path
230
+ * @param excludeIdeFolders - If true, exclude IDE config folders (.claude, .windsurf, etc.)
231
+ */
232
+ async copyDirectory(src, dest, excludeIdeFolders = false) {
233
+ await fs.mkdir(dest, { recursive: true });
234
+ const entries = await fs.readdir(src, { withFileTypes: true });
235
+ const operations = entries
236
+ .filter((entry) => {
237
+ // Standard exclusions (test files, cache, etc.)
238
+ if (this.shouldExcludeFile(entry.name)) {
239
+ return false;
240
+ }
241
+ // Exclude IDE config folders if requested (used for _shared folder)
242
+ // These folders are used for settings merging, not direct installation
243
+ if (excludeIdeFolders && entry.isDirectory() && entry.name.startsWith('.')) {
244
+ return false;
245
+ }
246
+ return true;
247
+ })
248
+ .map(async (entry) => {
249
+ const srcPath = join(src, entry.name);
250
+ const destPath = join(dest, entry.name);
251
+ try {
252
+ return entry.isDirectory() ? await this.copyDirectory(srcPath, destPath, excludeIdeFolders) : await fs.copyFile(srcPath, destPath);
253
+ }
254
+ catch (error) {
255
+ const err = error;
256
+ throw new Error(`Failed to copy ${srcPath} to ${destPath}: ${err.message}`);
257
+ }
258
+ });
259
+ await Promise.all(operations);
260
+ }
261
+ /**
262
+ * Get description for a template
263
+ *
264
+ * @param template - Template name
265
+ * @returns Template description
266
+ */
267
+ getTemplateDescription(template) {
268
+ const descriptions = {
269
+ 'cc-native': 'CC-Native - Event-sourced context management with plan review',
270
+ };
271
+ return descriptions[template] || 'Custom template';
272
+ }
273
+ /**
274
+ * Merge settings from multiple method templates into project settings.
275
+ * Processes methods in order, allowing later methods to override earlier ones.
276
+ *
277
+ * @param targetDir - Project directory
278
+ * @param methods - Array of method names to merge (e.g., ['_shared', 'cc-native'])
279
+ * @param ides - IDEs being configured (for IDE-specific merging)
280
+ */
281
+ async mergeMethodsSettings(targetDir, methods, ides) {
282
+ const targetSettingsPath = getTargetSettingsFile(targetDir);
283
+ let projectSettings = (await readClaudeSettings(targetSettingsPath)) || {};
284
+ for (const method of methods) {
285
+ try {
286
+ // Get template path for this method
287
+ let templatePath;
288
+ if (method === '_shared') {
289
+ // Special case: _shared is at templates/_shared
290
+ const currentFilePath = fileURLToPath(import.meta.url);
291
+ const currentDir = dirname(currentFilePath);
292
+ const templatesRoot = join(dirname(dirname(currentDir)), 'templates');
293
+ templatePath = join(templatesRoot, '_shared');
294
+ }
295
+ else {
296
+ // Named method templates
297
+ templatePath = await getTemplatePath(method);
298
+ }
299
+ // Merge Claude settings if claude IDE is selected
300
+ if (ides.includes('claude')) {
301
+ const templateSettingsPath = join(templatePath, '.claude', 'settings.json');
302
+ const templateSettings = await readClaudeSettings(templateSettingsPath);
303
+ if (templateSettings) {
304
+ projectSettings = mergeClaudeSettings(projectSettings, templateSettings);
305
+ this.logSuccess(`✓ Merged ${method} settings into .claude/settings.json`);
306
+ }
307
+ }
308
+ // Merge Windsurf hooks if windsurf IDE is selected
309
+ if (ides.includes('windsurf')) {
310
+ await this.mergeWindsurfTemplateHooks(targetDir, templatePath);
311
+ }
312
+ }
313
+ catch (error) {
314
+ const err = error;
315
+ this.warn(`Failed to merge ${method} settings: ${err.message}`);
316
+ }
317
+ }
318
+ // Write merged Claude settings
319
+ if (ides.includes('claude')) {
320
+ await writeClaudeSettings(targetSettingsPath, projectSettings);
321
+ }
322
+ }
323
+ /**
324
+ * Merge Windsurf template hooks into project hooks
325
+ *
326
+ * @param targetDir - Project directory
327
+ * @param templatePath - Template source path
328
+ */
329
+ async mergeWindsurfTemplateHooks(targetDir, templatePath) {
330
+ try {
331
+ // Read template hooks
332
+ const templateHooksPath = join(templatePath, '.windsurf', 'hooks.json');
333
+ const templateHooks = await readWindsurfHooks(templateHooksPath);
334
+ // If template has no hooks, nothing to merge
335
+ if (!templateHooks || !templateHooks.hooks || Object.keys(templateHooks.hooks).length === 0) {
336
+ this.logInfo('No Windsurf hooks in template to merge');
337
+ return;
338
+ }
339
+ // Get target hooks file path
340
+ const targetHooksPath = getTargetHooksFile(targetDir);
341
+ // Read existing project hooks
342
+ const existingHooks = await readWindsurfHooks(targetHooksPath);
343
+ // Merge hooks
344
+ const mergedHooks = mergeWindsurfHooks(existingHooks, templateHooks);
345
+ // Write merged hooks
346
+ await writeWindsurfHooks(targetHooksPath, mergedHooks);
347
+ this.logSuccess('✓ Windsurf template hooks merged into project hooks');
348
+ }
349
+ catch (error) {
350
+ const err = error;
351
+ this.warn(`Failed to merge Windsurf template hooks: ${err.message}`);
352
+ // Don't fail the entire installation if hook merging fails
353
+ }
354
+ }
355
+ /**
356
+ * Check if a path exists
357
+ */
358
+ async pathExists(path) {
359
+ try {
360
+ await fs.access(path);
361
+ return true;
362
+ }
363
+ catch {
364
+ return false;
365
+ }
366
+ }
367
+ /**
368
+ * Perform post-installation actions.
369
+ *
370
+ * Handles:
371
+ * - Method tracking in settings.json
372
+ * - Settings/hooks merging for all methods
373
+ * - .gitignore updates
374
+ *
375
+ * @param config - Post-install configuration
376
+ * @param config.targetDir - Project directory
377
+ * @param config.method - Method name that was installed
378
+ * @param config.ides - IDEs that were configured
379
+ * @param config.hasGit - Whether git repository exists
380
+ * @param config.foldersForGitignore - Folders to add to .gitignore
381
+ */
382
+ async performPostInstallActions(config) {
383
+ const { targetDir, method, ides, hasGit, foldersForGitignore } = config;
384
+ // Track method installation in settings.json
385
+ await this.trackMethodInstallation(targetDir, method, ides);
386
+ // Merge settings from _shared and method templates
387
+ await this.mergeMethodsSettings(targetDir, ['_shared', method], ides);
388
+ // Update .gitignore if git repository exists
389
+ if (hasGit) {
390
+ await updateGitignore(targetDir, foldersForGitignore);
391
+ this.logSuccess('✓ .gitignore updated');
392
+ }
393
+ }
394
+ /**
395
+ * Resolve installation configuration from flags or interactive wizard.
396
+ *
397
+ * Determines what to install based on:
398
+ * - Interactive wizard input
399
+ * - Command-line flags
400
+ * - Minimal install mode (no method specified)
401
+ *
402
+ * @param flags - Parsed command flags
403
+ * @param flags.interactive - Run interactive wizard
404
+ * @param flags.method - Template method to install
405
+ * @param flags.ide - IDEs to configure
406
+ * @param targetDir - Target directory for installation
407
+ * @param availableTemplates - List of available template names
408
+ * @returns Installation configuration or null for minimal install
409
+ */
410
+ async resolveInstallationConfig(flags, targetDir, availableTemplates) {
411
+ if (flags.interactive) {
412
+ // Run interactive wizard
413
+ const wizardResult = await this.runInteractiveWizard(targetDir, availableTemplates);
414
+ if (!wizardResult.confirmed) {
415
+ this.log('Installation cancelled.');
416
+ return null;
417
+ }
418
+ return {
419
+ method: wizardResult.method,
420
+ ides: wizardResult.ides,
421
+ username: wizardResult.username,
422
+ projectName: wizardResult.projectName,
423
+ };
424
+ }
425
+ if (flags.method) {
426
+ // Use flags (method specified)
427
+ // Validate template exists
428
+ if (!availableTemplates.includes(flags.method)) {
429
+ this.error(`Template '${flags.method}' not found. Available templates: ${availableTemplates.join(', ')}`, {
430
+ exit: EXIT_CODES.INVALID_USAGE,
431
+ });
432
+ }
433
+ return {
434
+ method: flags.method,
435
+ ides: flags.ide,
436
+ username: await detectUsername(),
437
+ projectName: detectProjectName(targetDir),
438
+ };
439
+ }
440
+ // Minimal install mode - install only _shared folder
441
+ return null;
442
+ }
443
+ /**
444
+ * Run interactive setup wizard
445
+ *
446
+ * @param targetDir - Target directory for installation
447
+ * @param availableTemplates - List of available template names
448
+ * @returns Wizard configuration result
449
+ */
450
+ async runInteractiveWizard(targetDir, availableTemplates) {
451
+ this.log('');
452
+ this.log('┌─────────────────────────────────────────┐');
453
+ this.log('│ AIW Interactive Setup Wizard │');
454
+ this.log('└─────────────────────────────────────────┘');
455
+ this.log('');
456
+ // Detect defaults
457
+ const detectedUsername = await detectUsername();
458
+ const detectedProjectName = detectProjectName(targetDir);
459
+ // Step 1: Select template method
460
+ const method = await select({
461
+ message: 'Select a template method:',
462
+ choices: availableTemplates.map((template) => ({
463
+ value: template,
464
+ name: template.toUpperCase(),
465
+ description: this.getTemplateDescription(template),
466
+ })),
467
+ });
468
+ this.log('');
469
+ // Step 2: Select IDEs
470
+ const ides = await checkbox({
471
+ message: 'Select IDEs to configure:',
472
+ choices: AVAILABLE_IDES.map((ide) => ({
473
+ value: ide.value,
474
+ name: ide.name,
475
+ checked: ide.value === 'claude', // Default to Claude selected
476
+ })),
477
+ required: true,
478
+ });
479
+ this.log('');
480
+ // Step 3: Confirm/edit username
481
+ const username = await input({
482
+ message: 'Username:',
483
+ default: detectedUsername,
484
+ });
485
+ // Step 4: Confirm/edit project name
486
+ const projectName = await input({
487
+ message: 'Project name:',
488
+ default: detectedProjectName,
489
+ });
490
+ this.log('');
491
+ // Step 5: Summary and confirmation
492
+ this.log('┌─────────────────────────────────────────┐');
493
+ this.log('│ Installation Summary │');
494
+ this.log('├─────────────────────────────────────────┤');
495
+ this.log(`│ Template: ${method.padEnd(24)}│`);
496
+ this.log(`│ IDEs: ${ides.join(', ').padEnd(24)}│`);
497
+ this.log(`│ Username: ${username.padEnd(24)}│`);
498
+ this.log(`│ Project: ${projectName.padEnd(24)}│`);
499
+ this.log(`│ Directory: ${basename(targetDir).padEnd(24)}│`);
500
+ this.log('└─────────────────────────────────────────┘');
501
+ this.log('');
502
+ const confirmed = await confirm({
503
+ message: 'Proceed with installation?',
504
+ default: true,
505
+ });
506
+ return {
507
+ method,
508
+ ides,
509
+ username,
510
+ projectName,
511
+ confirmed,
512
+ };
513
+ }
514
+ /**
515
+ * Check if a file should be excluded from copying
516
+ */
517
+ shouldExcludeFile(name) {
518
+ const excludedPatterns = [
519
+ '_output',
520
+ '__pycache__',
521
+ '.pytest_cache',
522
+ 'conftest.py',
523
+ /^test_.*\.py$/,
524
+ /.*\.pyc$/,
525
+ ];
526
+ return excludedPatterns.some((pattern) => {
527
+ if (typeof pattern === 'string') {
528
+ return name === pattern;
529
+ }
530
+ return pattern.test(name);
531
+ });
532
+ }
533
+ /**
534
+ * Track method installation in settings.json
535
+ *
536
+ * Adds method entry to the methods object with installation metadata.
537
+ *
538
+ * @param targetDir - Project directory
539
+ * @param method - Method name being installed
540
+ * @param ides - IDEs configured for this method
541
+ */
542
+ async trackMethodInstallation(targetDir, method, ides) {
543
+ try {
544
+ const settingsPath = getTargetSettingsFile(targetDir);
545
+ const existingSettings = (await readClaudeSettings(settingsPath)) || {};
546
+ // Add method tracking
547
+ const updatedSettings = {
548
+ ...existingSettings,
549
+ methods: {
550
+ ...existingSettings.methods,
551
+ [method]: {
552
+ ides,
553
+ installedAt: new Date().toISOString(),
554
+ },
555
+ },
556
+ };
557
+ await writeClaudeSettings(settingsPath, updatedSettings);
558
+ this.logSuccess(`✓ Method '${method}' tracked in settings.json`);
559
+ }
560
+ catch (error) {
561
+ const err = error;
562
+ this.warn(`Failed to track method installation: ${err.message}`);
563
+ }
564
+ }
565
+ }