@zik000/archai 0.1.4 → 0.2.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 (97) hide show
  1. package/README.md +636 -486
  2. package/dist/bin/cli.js +52 -1
  3. package/dist/bin/cli.js.map +1 -1
  4. package/dist/commands/cleanup.d.ts +16 -0
  5. package/dist/commands/cleanup.d.ts.map +1 -0
  6. package/dist/commands/cleanup.js +245 -0
  7. package/dist/commands/cleanup.js.map +1 -0
  8. package/dist/commands/doctor.d.ts.map +1 -1
  9. package/dist/commands/doctor.js +14 -6
  10. package/dist/commands/doctor.js.map +1 -1
  11. package/dist/commands/generate.d.ts +1 -5
  12. package/dist/commands/generate.d.ts.map +1 -1
  13. package/dist/commands/generate.js +15 -13
  14. package/dist/commands/generate.js.map +1 -1
  15. package/dist/commands/init.d.ts +7 -4
  16. package/dist/commands/init.d.ts.map +1 -1
  17. package/dist/commands/init.js +570 -90
  18. package/dist/commands/init.js.map +1 -1
  19. package/dist/commands/rollback.d.ts +13 -0
  20. package/dist/commands/rollback.d.ts.map +1 -0
  21. package/dist/commands/rollback.js +186 -0
  22. package/dist/commands/rollback.js.map +1 -0
  23. package/dist/commands/setup-config.js +109 -107
  24. package/dist/commands/setup-config.js.map +1 -1
  25. package/dist/commands/setup-project.d.ts.map +1 -1
  26. package/dist/commands/setup-project.js +78 -76
  27. package/dist/commands/setup-project.js.map +1 -1
  28. package/dist/commands/uninstall.d.ts +3 -0
  29. package/dist/commands/uninstall.d.ts.map +1 -0
  30. package/dist/commands/uninstall.js +172 -0
  31. package/dist/commands/uninstall.js.map +1 -0
  32. package/dist/commands/update.d.ts +10 -0
  33. package/dist/commands/update.d.ts.map +1 -0
  34. package/dist/commands/update.js +374 -0
  35. package/dist/commands/update.js.map +1 -0
  36. package/dist/generator/claude-cli.d.ts +5 -0
  37. package/dist/generator/claude-cli.d.ts.map +1 -1
  38. package/dist/generator/claude-cli.js +74 -3
  39. package/dist/generator/claude-cli.js.map +1 -1
  40. package/dist/generator/prompt-builder.d.ts +6 -17
  41. package/dist/generator/prompt-builder.d.ts.map +1 -1
  42. package/dist/generator/prompt-builder.js +131 -109
  43. package/dist/generator/prompt-builder.js.map +1 -1
  44. package/dist/index.d.ts +14 -2
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +18 -2
  47. package/dist/index.js.map +1 -1
  48. package/dist/scaffold/create-claude-settings.d.ts.map +1 -1
  49. package/dist/scaffold/create-claude-settings.js +32 -20
  50. package/dist/scaffold/create-claude-settings.js.map +1 -1
  51. package/dist/scaffold/create-config.d.ts +4 -4
  52. package/dist/scaffold/create-config.d.ts.map +1 -1
  53. package/dist/scaffold/create-config.js +86 -86
  54. package/dist/scaffold/create-project-description.d.ts +4 -4
  55. package/dist/scaffold/create-project-description.d.ts.map +1 -1
  56. package/dist/scaffold/create-project-description.js +97 -97
  57. package/dist/types/index.d.ts +116 -0
  58. package/dist/types/index.d.ts.map +1 -0
  59. package/dist/types/index.js +3 -0
  60. package/dist/types/index.js.map +1 -0
  61. package/dist/utils/analyze-files.d.ts +7 -0
  62. package/dist/utils/analyze-files.d.ts.map +1 -0
  63. package/dist/utils/analyze-files.js +27 -0
  64. package/dist/utils/analyze-files.js.map +1 -0
  65. package/dist/utils/backup.d.ts +102 -0
  66. package/dist/utils/backup.d.ts.map +1 -0
  67. package/dist/utils/backup.js +352 -0
  68. package/dist/utils/backup.js.map +1 -0
  69. package/dist/utils/detect-project.d.ts +1 -9
  70. package/dist/utils/detect-project.d.ts.map +1 -1
  71. package/dist/utils/detect-project.js +9 -4
  72. package/dist/utils/detect-project.js.map +1 -1
  73. package/dist/utils/file-comparison.d.ts +89 -0
  74. package/dist/utils/file-comparison.d.ts.map +1 -0
  75. package/dist/utils/file-comparison.js +301 -0
  76. package/dist/utils/file-comparison.js.map +1 -0
  77. package/dist/utils/file-merger.d.ts +74 -0
  78. package/dist/utils/file-merger.d.ts.map +1 -0
  79. package/dist/utils/file-merger.js +350 -0
  80. package/dist/utils/file-merger.js.map +1 -0
  81. package/dist/utils/logger.d.ts +16 -0
  82. package/dist/utils/logger.d.ts.map +1 -1
  83. package/dist/utils/logger.js +43 -1
  84. package/dist/utils/logger.js.map +1 -1
  85. package/dist/utils/migration.d.ts +74 -0
  86. package/dist/utils/migration.d.ts.map +1 -0
  87. package/dist/utils/migration.js +342 -0
  88. package/dist/utils/migration.js.map +1 -0
  89. package/dist/utils/validate-config.d.ts +1 -21
  90. package/dist/utils/validate-config.d.ts.map +1 -1
  91. package/dist/utils/validate-config.js.map +1 -1
  92. package/dist/utils/version-tracker.d.ts +130 -0
  93. package/dist/utils/version-tracker.d.ts.map +1 -0
  94. package/dist/utils/version-tracker.js +298 -0
  95. package/dist/utils/version-tracker.js.map +1 -0
  96. package/package.json +7 -1
  97. package/templates/core-agents/iteration-controller.md +584 -584
@@ -1,6 +1,8 @@
1
1
  import inquirer from 'inquirer';
2
2
  import chalk from 'chalk';
3
3
  import ora from 'ora';
4
+ import fs from 'fs-extra';
5
+ import path from 'path';
4
6
  import { scaffoldProject } from '../scaffold/create-structure.js';
5
7
  import { copyCoreAgents } from '../scaffold/copy-core-agents.js';
6
8
  import { createConfigTemplate } from '../scaffold/create-config.js';
@@ -8,6 +10,12 @@ import { createProjectDescription } from '../scaffold/create-project-description
8
10
  import { createClaudeSettings } from '../scaffold/create-claude-settings.js';
9
11
  import { detectProject } from '../utils/detect-project.js';
10
12
  import { logger } from '../utils/logger.js';
13
+ import { createBackup, pruneOldBackups } from '../utils/backup.js';
14
+ import { loadVersionFile, saveVersionFile, createVersionFile, bulkUpdateFileHashes, } from '../utils/version-tracker.js';
15
+ import { detectLegacyInstall, hasExistingInstall, migrateFromLegacy, getArchaiVersion, getTemplateFiles, } from '../utils/migration.js';
16
+ import { hashFile, } from '../utils/file-comparison.js';
17
+ import { mergeFile, applyMerge, } from '../utils/file-merger.js';
18
+ import { analyzeAllFiles } from '../utils/analyze-files.js';
11
19
  const PROJECT_TYPES = [
12
20
  { name: 'Web Application (frontend/backend/fullstack)', value: 'web' },
13
21
  { name: 'Mobile Application', value: 'mobile' },
@@ -17,25 +25,144 @@ const PROJECT_TYPES = [
17
25
  { name: 'DevOps / Infrastructure', value: 'devops' },
18
26
  { name: 'Other', value: 'other' },
19
27
  ];
20
- const PACKAGE_MANAGERS = [
21
- { name: 'npm', value: 'npm' },
22
- { name: 'pnpm', value: 'pnpm' },
23
- { name: 'yarn', value: 'yarn' },
24
- { name: 'bun', value: 'bun' },
25
- { name: 'pip (Python)', value: 'pip' },
26
- { name: 'cargo (Rust)', value: 'cargo' },
27
- { name: 'go mod (Go)', value: 'go' },
28
- { name: 'Other / None', value: 'other' },
28
+ const CLAUDE_DIR = '.claude';
29
+ // Folders that will be created by scaffoldProject
30
+ const FOLDERS = [
31
+ '.claude/agents',
32
+ '.claude/state',
33
+ '.claude/state/archived',
34
+ '.claude/plans',
35
+ '.claude/plans/archived',
36
+ '.claude/templates',
37
+ '.knowledge/context',
38
+ '.knowledge/decisions',
39
+ '.knowledge/learnings',
40
+ '.knowledge/planning',
41
+ '.knowledge/status',
42
+ '.tasks/inbox',
43
+ '.tasks/done',
44
+ '.tasks/epics',
45
+ '.tasks/review',
46
+ '.tasks/blocked',
47
+ '.tasks/templates',
48
+ '.supervisor',
49
+ '.agents/plans',
50
+ '.agents/scratch',
51
+ '.agents/thoughts',
29
52
  ];
30
53
  export async function init(options) {
31
54
  logger.banner();
32
- // Check for existing setup
33
- const hasExisting = await checkExistingSetup();
34
- if (hasExisting && !options.force) {
55
+ const archaiVersion = await getArchaiVersion();
56
+ const existingInstall = await hasExistingInstall(CLAUDE_DIR);
57
+ const isLegacy = await detectLegacyInstall(CLAUDE_DIR);
58
+ // Handle existing installation
59
+ if (existingInstall && !options.force) {
60
+ // Check if we can do a smart update instead of failing
61
+ if (isLegacy) {
62
+ logger.info('Found existing archai installation (pre-0.2.0).');
63
+ logger.info('Running migration to enable smart updates...');
64
+ console.log('');
65
+ const migration = await migrateFromLegacy(CLAUDE_DIR, archaiVersion);
66
+ if (!migration.success) {
67
+ logger.error(`Migration failed: ${migration.error}`);
68
+ return;
69
+ }
70
+ if (migration.warnings.length > 0) {
71
+ for (const warning of migration.warnings) {
72
+ logger.warn(warning);
73
+ }
74
+ }
75
+ logger.success('Migration complete!');
76
+ logger.info('Your files have been analyzed. Run "archai update" to check for updates.');
77
+ return;
78
+ }
79
+ // Already has version.json - suggest using update command
35
80
  logger.warn('archai is already initialized in this directory.');
36
- logger.info('Use --force to overwrite existing configuration.');
81
+ logger.info('To update archai, run: archai update');
82
+ logger.info('To reinitialize from scratch, use: archai init --force');
37
83
  return;
38
84
  }
85
+ // --force mode with existing installation: do smart comparison
86
+ if (existingInstall && options.force) {
87
+ await handleForceInit(options, archaiVersion);
88
+ return;
89
+ }
90
+ // Fresh installation
91
+ await handleFreshInit(options, archaiVersion);
92
+ }
93
+ /**
94
+ * Handle --force init on existing installation.
95
+ * Creates backup, compares files, and updates intelligently.
96
+ */
97
+ async function handleForceInit(options, archaiVersion) {
98
+ // First, ensure we have version.json (migrate if needed)
99
+ const isLegacy = await detectLegacyInstall(CLAUDE_DIR);
100
+ if (isLegacy) {
101
+ const migration = await migrateFromLegacy(CLAUDE_DIR, archaiVersion);
102
+ if (!migration.success) {
103
+ logger.error(`Migration failed: ${migration.error}`);
104
+ return;
105
+ }
106
+ }
107
+ // Create backup before any changes
108
+ logger.info('Creating backup...');
109
+ const backup = await createBackup(CLAUDE_DIR, 'pre-force-init', archaiVersion);
110
+ if (!backup.success) {
111
+ logger.error(`Backup failed: ${backup.error}`);
112
+ logger.info('Aborting to prevent data loss. Fix the issue and try again.');
113
+ return;
114
+ }
115
+ logger.success(`Backup created: ${backup.timestamp}`);
116
+ // Prune old backups
117
+ await pruneOldBackups(CLAUDE_DIR, 5);
118
+ // Analyze all files
119
+ console.log('');
120
+ const analysisSpinner = ora('Analyzing files...').start();
121
+ const analyses = await analyzeAllFiles(CLAUDE_DIR);
122
+ analysisSpinner.succeed(`Analyzed ${analyses.length} files`);
123
+ // Categorize files
124
+ const newFiles = analyses.filter(a => a.status === 'new');
125
+ const unchanged = analyses.filter(a => a.status === 'unchanged');
126
+ const modifiedByArchai = analyses.filter(a => a.status === 'modified_by_archai');
127
+ const modifiedByUser = analyses.filter(a => a.status === 'modified_by_user');
128
+ const conflicts = analyses.filter(a => a.status === 'conflict');
129
+ // Show summary
130
+ console.log('');
131
+ logger.section('File Analysis');
132
+ console.log('');
133
+ if (newFiles.length > 0)
134
+ console.log(chalk.green(` New files: ${newFiles.length}`));
135
+ if (unchanged.length > 0)
136
+ console.log(chalk.gray(` Unchanged: ${unchanged.length}`));
137
+ if (modifiedByArchai.length > 0)
138
+ console.log(chalk.cyan(` Updated by archai: ${modifiedByArchai.length}`));
139
+ if (modifiedByUser.length > 0)
140
+ console.log(chalk.yellow(` Modified by you: ${modifiedByUser.length}`));
141
+ if (conflicts.length > 0)
142
+ console.log(chalk.red(` Conflicts: ${conflicts.length}`));
143
+ console.log('');
144
+ // Handle dry run
145
+ if (options.dryRun) {
146
+ await showDryRunSummary(analyses);
147
+ return;
148
+ }
149
+ // Handle files that need attention
150
+ const needsAttention = [...modifiedByUser, ...conflicts.filter(c => !c.canAutoMerge)];
151
+ if (needsAttention.length > 0 && !options.yes) {
152
+ // Interactive mode - ask user about each file
153
+ await handleConflictsInteractively(needsAttention);
154
+ }
155
+ else if (needsAttention.length > 0 && options.yes) {
156
+ // Non-interactive mode - skip user-modified files
157
+ logger.warn(`Skipping ${needsAttention.length} user-modified files (use interactive mode to resolve).`);
158
+ }
159
+ // Apply changes
160
+ await applyFileChanges(analyses, options, archaiVersion);
161
+ }
162
+ /**
163
+ * Handle fresh installation (no existing .claude directory).
164
+ */
165
+ async function handleFreshInit(options, archaiVersion) {
39
166
  // Detect project info
40
167
  const spinner = ora('Detecting project information...').start();
41
168
  const detected = await detectProject();
@@ -44,98 +171,413 @@ export async function init(options) {
44
171
  logger.info(`Found: ${detected.languages.join(', ')}`);
45
172
  }
46
173
  let answers;
47
- if (options.skipWizard) {
48
- // Only use detected values, don't assume defaults
49
- // This allows TODO placeholders to appear for undetected fields
174
+ if (options.skipWizard || options.yes) {
175
+ // Non-interactive mode
50
176
  answers = {
51
177
  projectName: detected.name || 'my-project',
52
- projectType: detected.languages.length > 0 ? 'web' : '', // Only assume web if we detected languages
53
- languages: detected.languages.join(', ') || '',
54
- frameworks: detected.frameworks?.join(', ') || '',
55
- testFramework: detected.testFramework || '',
178
+ projectType: detected.languages.length > 0 ? 'web' : '',
56
179
  isMonorepo: detected.isMonorepo,
57
- packageManager: detected.packageManager || '',
180
+ permissionPreset: 'permissive',
58
181
  };
59
182
  }
60
183
  else {
61
184
  // Interactive wizard
185
+ answers = await runWizard(detected);
186
+ }
187
+ // Dry run mode
188
+ if (options.dryRun) {
189
+ showFreshDryRun(answers);
190
+ return;
191
+ }
192
+ // Initialize rollback context
193
+ const rollback = {
194
+ createdPaths: [],
195
+ modifiedFiles: new Map(),
196
+ };
197
+ try {
62
198
  console.log('');
63
- logger.section('Project Setup');
64
- console.log(chalk.gray(' (Press Enter to skip questions you\'re unsure about)'));
199
+ logger.section('Creating Agent System');
65
200
  console.log('');
66
- answers = await inquirer.prompt([
67
- {
68
- type: 'input',
69
- name: 'projectName',
70
- message: '1. Project name:',
71
- default: detected.name || 'my-project',
72
- },
73
- {
74
- type: 'list',
75
- name: 'projectType',
76
- message: '2. What type of project is this?',
77
- choices: [...PROJECT_TYPES, { name: 'Skip (decide later)', value: '' }],
78
- },
79
- {
80
- type: 'input',
81
- name: 'languages',
82
- message: '3. Primary programming language(s):',
83
- default: detected.languages.join(', ') || '',
84
- },
201
+ // Backup .gitignore if exists
202
+ if (await fs.pathExists('.gitignore')) {
203
+ const content = await fs.readFile('.gitignore', 'utf-8');
204
+ rollback.modifiedFiles.set('.gitignore', content);
205
+ }
206
+ // Create folder structure
207
+ const structureSpinner = ora('Creating folder structure...').start();
208
+ await scaffoldProject();
209
+ rollback.createdPaths.push(...FOLDERS.filter(f => !f.includes('/archived')));
210
+ structureSpinner.succeed('Created folder structure');
211
+ // Copy core agents
212
+ const agentsSpinner = ora('Installing core agents (10 agents)...').start();
213
+ await copyCoreAgents();
214
+ agentsSpinner.succeed('Installed core agents');
215
+ // Create config template
216
+ const configSpinner = ora('Creating configuration template...').start();
217
+ await createConfigTemplate(answers);
218
+ rollback.createdPaths.push('archai.config.md');
219
+ configSpinner.succeed('Created archai.config.md');
220
+ // Create project description template
221
+ const descSpinner = ora('Creating project description template...').start();
222
+ await createProjectDescription(answers);
223
+ descSpinner.succeed('Created project-description.md');
224
+ // Create permission settings
225
+ const packageManager = detected.packageManager || 'npm';
226
+ if (answers.permissionPreset !== 'skip') {
227
+ const permSpinner = ora('Creating permission settings...').start();
228
+ await createClaudeSettings({
229
+ preset: answers.permissionPreset,
230
+ packageManager,
231
+ customAllowCommands: answers.customAllowCommands,
232
+ customDenyCommands: answers.customDenyCommands,
233
+ });
234
+ rollback.createdPaths.push('.claude/settings.local.json');
235
+ const presetLabel = answers.permissionPreset.charAt(0).toUpperCase() + answers.permissionPreset.slice(1);
236
+ permSpinner.succeed(`Created .claude/settings.local.json (${presetLabel} mode)`);
237
+ }
238
+ else {
239
+ logger.info('Skipped permission settings (configure manually later)');
240
+ }
241
+ // Create version.json
242
+ const versionSpinner = ora('Creating version tracking...').start();
243
+ await createVersionTracking(archaiVersion);
244
+ versionSpinner.succeed('Created version.json');
245
+ // Final output
246
+ showSuccessMessage();
247
+ }
248
+ catch (error) {
249
+ logger.error('Initialization failed. Rolling back changes...');
250
+ await performRollback(rollback);
251
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
252
+ logger.debug('Init failed', { error: errorMessage });
253
+ throw error;
254
+ }
255
+ }
256
+ /**
257
+ * Run the interactive wizard.
258
+ */
259
+ async function runWizard(detected) {
260
+ console.log('');
261
+ logger.section('Project Setup');
262
+ console.log(chalk.gray(' (Press Enter to skip questions you\'re unsure about)'));
263
+ console.log('');
264
+ const mainAnswers = await inquirer.prompt([
265
+ {
266
+ type: 'input',
267
+ name: 'projectName',
268
+ message: '1. Project name:',
269
+ default: detected.name || 'my-project',
270
+ },
271
+ {
272
+ type: 'list',
273
+ name: 'projectType',
274
+ message: '2. What type of project is this?',
275
+ choices: [...PROJECT_TYPES, { name: 'Skip (decide later)', value: '' }],
276
+ },
277
+ {
278
+ type: 'confirm',
279
+ name: 'isMonorepo',
280
+ message: '3. Do you use a monorepo structure?',
281
+ default: detected.isMonorepo,
282
+ },
283
+ ]);
284
+ // Permission preset section
285
+ console.log('');
286
+ logger.section('Permissions');
287
+ console.log(chalk.gray(' Configure what commands Claude agents can execute.'));
288
+ console.log('');
289
+ const { permissionPreset } = await inquirer.prompt([{
290
+ type: 'list',
291
+ name: 'permissionPreset',
292
+ message: '4. Permission level:',
293
+ choices: [
294
+ { name: `${chalk.green('Permissive')} - Everything except irreversible actions`, value: 'permissive' },
295
+ { name: `${chalk.yellow('Strict')} - Only safe operations (no network, no push)`, value: 'strict' },
296
+ { name: `${chalk.cyan('Custom')} - Define your own commands`, value: 'custom' },
297
+ { name: `${chalk.gray('Skip')} - Configure manually later`, value: 'skip' },
298
+ ],
299
+ default: 'permissive',
300
+ }]);
301
+ let customAllowCommands;
302
+ let customDenyCommands;
303
+ if (permissionPreset === 'custom') {
304
+ console.log('');
305
+ console.log(chalk.gray(' Enter commands separated by commas. Use * as wildcard.'));
306
+ console.log(chalk.gray(' Example: npm *, git status*, tsc'));
307
+ console.log('');
308
+ const customAnswers = await inquirer.prompt([
85
309
  {
86
310
  type: 'input',
87
- name: 'frameworks',
88
- message: '4. Main frameworks/libraries (skip if unsure):',
89
- default: detected.frameworks?.join(', ') || '',
311
+ name: 'allowCommands',
312
+ message: 'Allowed commands:',
313
+ default: `${detected.packageManager || 'npm'} *, git status*, git diff*, git log*`,
90
314
  },
91
315
  {
92
316
  type: 'input',
93
- name: 'testFramework',
94
- message: '5. Testing framework (skip if unsure):',
95
- default: detected.testFramework || '',
96
- },
97
- {
98
- type: 'confirm',
99
- name: 'isMonorepo',
100
- message: '6. Do you use a monorepo structure?',
101
- default: detected.isMonorepo,
102
- },
103
- {
104
- type: 'list',
105
- name: 'packageManager',
106
- message: '7. What\'s your package manager / build tool?',
107
- choices: [...PACKAGE_MANAGERS, { name: 'Skip (decide later)', value: '' }],
108
- default: detected.packageManager || 'npm',
317
+ name: 'denyCommands',
318
+ message: 'Additional denied commands (dangerous ones are always blocked):',
319
+ default: '',
109
320
  },
110
321
  ]);
322
+ customAllowCommands = customAnswers.allowCommands
323
+ .split(',')
324
+ .map((c) => c.trim())
325
+ .filter((c) => c);
326
+ customDenyCommands = customAnswers.denyCommands
327
+ .split(',')
328
+ .map((c) => c.trim())
329
+ .filter((c) => c);
330
+ }
331
+ return {
332
+ ...mainAnswers,
333
+ permissionPreset,
334
+ customAllowCommands,
335
+ customDenyCommands,
336
+ };
337
+ }
338
+ /**
339
+ * Show dry run summary.
340
+ */
341
+ async function showDryRunSummary(analyses) {
342
+ console.log('');
343
+ logger.section('Would Process');
344
+ console.log('');
345
+ const newFiles = analyses.filter(a => a.status === 'new');
346
+ const updates = analyses.filter(a => a.status === 'modified_by_archai');
347
+ const userModified = analyses.filter(a => a.status === 'modified_by_user');
348
+ const conflicts = analyses.filter(a => a.status === 'conflict');
349
+ if (newFiles.length > 0) {
350
+ console.log(chalk.green(' New files:'));
351
+ newFiles.forEach(f => console.log(chalk.gray(` + ${f.path}`)));
352
+ console.log('');
353
+ }
354
+ if (updates.length > 0) {
355
+ console.log(chalk.cyan(' Would update:'));
356
+ updates.forEach(f => console.log(chalk.gray(` ~ ${f.path}`)));
357
+ console.log('');
358
+ }
359
+ if (userModified.length > 0) {
360
+ console.log(chalk.yellow(' User-modified (would skip):'));
361
+ userModified.forEach(f => console.log(chalk.gray(` ! ${f.path}`)));
362
+ console.log('');
111
363
  }
364
+ if (conflicts.length > 0) {
365
+ console.log(chalk.red(' Conflicts (would need resolution):'));
366
+ conflicts.forEach(f => {
367
+ const auto = f.canAutoMerge ? chalk.gray(' (can auto-merge)') : '';
368
+ console.log(chalk.gray(` ⚠ ${f.path}${auto}`));
369
+ });
370
+ console.log('');
371
+ }
372
+ logger.info('Dry run complete. No files were modified.');
373
+ }
374
+ /**
375
+ * Show dry run for fresh install.
376
+ */
377
+ function showFreshDryRun(answers) {
378
+ console.log('');
379
+ logger.section('Would Create');
380
+ console.log('');
381
+ console.log(chalk.cyan(' Directories:'));
382
+ FOLDERS.forEach(f => console.log(chalk.gray(` ${f}`)));
383
+ console.log('');
384
+ console.log(chalk.cyan(' Files:'));
385
+ console.log(chalk.gray(' .claude/agents/*.md (10 core agents)'));
386
+ console.log(chalk.gray(' .claude/version.json'));
387
+ console.log(chalk.gray(' archai.config.md'));
388
+ console.log(chalk.gray(' .knowledge/context/project-description.md'));
389
+ console.log(chalk.gray(' PROMPTS.md'));
390
+ console.log(chalk.gray(' ARCHAI_README.md'));
391
+ if (answers.permissionPreset !== 'skip') {
392
+ console.log(chalk.gray(' .claude/settings.local.json'));
393
+ }
394
+ console.log('');
395
+ console.log(chalk.cyan(' Modifications:'));
396
+ console.log(chalk.gray(' .gitignore (append archai entries)'));
397
+ console.log('');
398
+ logger.info('Dry run complete. No files were created.');
399
+ }
400
+ /**
401
+ * Handle conflicts interactively.
402
+ */
403
+ async function handleConflictsInteractively(files) {
112
404
  console.log('');
113
- logger.section('Creating Agent System');
114
- console.log('');
115
- // Create folder structure
116
- const structureSpinner = ora('Creating folder structure...').start();
117
- await scaffoldProject();
118
- structureSpinner.succeed('Created folder structure');
119
- // Copy core agents
120
- const agentsSpinner = ora('Installing core agents (10 agents)...').start();
121
- await copyCoreAgents();
122
- agentsSpinner.succeed('Installed core agents');
123
- // Create config template
124
- const configSpinner = ora('Creating configuration template...').start();
125
- await createConfigTemplate(answers);
126
- configSpinner.succeed('Created archai.config.md');
127
- // Create project description template
128
- const descSpinner = ora('Creating project description template...').start();
129
- await createProjectDescription(answers);
130
- descSpinner.succeed('Created project-description.md');
131
- // Create default permission settings (permissive preset)
132
- const permSpinner = ora('Creating permission settings...').start();
133
- await createClaudeSettings({
134
- preset: 'permissive',
135
- packageManager: answers.packageManager || 'npm',
136
- });
137
- permSpinner.succeed('Created .claude/settings.local.json (permissive mode)');
138
- // Final output
405
+ logger.section('Files Requiring Attention');
406
+ console.log('');
407
+ for (const file of files) {
408
+ console.log(chalk.yellow(` ${file.path}`));
409
+ console.log(chalk.gray(` Status: ${file.status}`));
410
+ const { action } = await inquirer.prompt([{
411
+ type: 'list',
412
+ name: 'action',
413
+ message: `What would you like to do with ${file.path}?`,
414
+ choices: [
415
+ { name: 'Keep my version', value: 'keep' },
416
+ { name: 'Use new template', value: 'overwrite' },
417
+ { name: 'Try to merge', value: 'merge' },
418
+ { name: 'Skip', value: 'skip' },
419
+ ],
420
+ }]);
421
+ // Store the decision for later processing
422
+ file.userAction = action;
423
+ console.log('');
424
+ }
425
+ }
426
+ /**
427
+ * Apply file changes based on analysis.
428
+ */
429
+ async function applyFileChanges(analyses, options, archaiVersion) {
430
+ const spinner = ora('Applying changes...').start();
431
+ let applied = 0;
432
+ let skipped = 0;
433
+ const templates = await getTemplateFiles();
434
+ const templateMap = new Map(templates.map(t => [t.relativePath, t]));
435
+ for (const analysis of analyses) {
436
+ const template = templateMap.get(analysis.path);
437
+ if (!template)
438
+ continue;
439
+ const localPath = analysis.path;
440
+ try {
441
+ // Handle based on status
442
+ switch (analysis.status) {
443
+ case 'new':
444
+ // Copy new file from template
445
+ await fs.ensureDir(path.dirname(localPath));
446
+ await fs.copy(template.templatePath, localPath);
447
+ applied++;
448
+ break;
449
+ case 'modified_by_archai':
450
+ // Update from template (user hasn't modified)
451
+ await fs.copy(template.templatePath, localPath, { overwrite: true });
452
+ applied++;
453
+ break;
454
+ case 'conflict':
455
+ if (analysis.canAutoMerge) {
456
+ // Try auto-merge
457
+ const result = await mergeFile(localPath, template.templatePath);
458
+ if (result.success) {
459
+ await applyMerge(localPath, result);
460
+ applied++;
461
+ }
462
+ else {
463
+ skipped++;
464
+ }
465
+ }
466
+ else {
467
+ const conflictAction = analysis.userAction;
468
+ if (conflictAction === 'overwrite') {
469
+ await fs.copy(template.templatePath, localPath, { overwrite: true });
470
+ applied++;
471
+ }
472
+ else if (conflictAction === 'merge') {
473
+ const result = await mergeFile(localPath, template.templatePath);
474
+ if (result.success) {
475
+ await applyMerge(localPath, result);
476
+ applied++;
477
+ }
478
+ else {
479
+ skipped++;
480
+ }
481
+ }
482
+ else {
483
+ skipped++;
484
+ }
485
+ }
486
+ break;
487
+ case 'modified_by_user':
488
+ // Skip unless user specified action
489
+ const action = analysis.userAction;
490
+ if (action === 'overwrite') {
491
+ await fs.copy(template.templatePath, localPath, { overwrite: true });
492
+ applied++;
493
+ }
494
+ else if (action === 'merge') {
495
+ const result = await mergeFile(localPath, template.templatePath);
496
+ if (result.success) {
497
+ await applyMerge(localPath, result);
498
+ applied++;
499
+ }
500
+ else {
501
+ skipped++;
502
+ }
503
+ }
504
+ else {
505
+ skipped++;
506
+ }
507
+ break;
508
+ case 'unchanged':
509
+ case 'generated':
510
+ // No action needed
511
+ break;
512
+ }
513
+ }
514
+ catch (error) {
515
+ logger.debug(`Failed to process ${analysis.path}`, error);
516
+ skipped++;
517
+ }
518
+ }
519
+ spinner.succeed(`Applied ${applied} changes, skipped ${skipped}`);
520
+ // Update version.json
521
+ await updateVersionTracking(archaiVersion, analyses);
522
+ console.log('');
523
+ logger.success('Initialization complete!');
524
+ console.log('');
525
+ }
526
+ /**
527
+ * Create version tracking for fresh install.
528
+ */
529
+ async function createVersionTracking(archaiVersion) {
530
+ const templates = await getTemplateFiles();
531
+ const fileHashes = [];
532
+ for (const template of templates) {
533
+ const localPath = template.relativePath;
534
+ if (await fs.pathExists(localPath)) {
535
+ const hash = await hashFile(localPath);
536
+ fileHashes.push({ path: template.relativePath, hash, isGenerated: false });
537
+ }
538
+ }
539
+ let versionFile = createVersionFile(archaiVersion);
540
+ versionFile = bulkUpdateFileHashes(versionFile, fileHashes);
541
+ versionFile.expectedFiles = templates.map(t => t.relativePath);
542
+ await saveVersionFile(CLAUDE_DIR, versionFile);
543
+ }
544
+ /**
545
+ * Update version tracking after init/update.
546
+ */
547
+ async function updateVersionTracking(archaiVersion, analyses) {
548
+ let versionFile = await loadVersionFile(CLAUDE_DIR);
549
+ if (!versionFile) {
550
+ versionFile = createVersionFile(archaiVersion);
551
+ }
552
+ else {
553
+ versionFile.archai = archaiVersion;
554
+ versionFile.lastUpdated = new Date().toISOString();
555
+ }
556
+ // Update hashes for all processed files
557
+ const updates = [];
558
+ for (const analysis of analyses) {
559
+ const userAction = analysis.userAction;
560
+ const wasApplied = analysis.status === 'new' ||
561
+ analysis.status === 'modified_by_archai' ||
562
+ (analysis.status === 'conflict' && analysis.canAutoMerge) ||
563
+ (analysis.status === 'conflict' && (userAction === 'overwrite' || userAction === 'merge')) ||
564
+ (analysis.status === 'modified_by_user' && (userAction === 'overwrite' || userAction === 'merge'));
565
+ if (wasApplied) {
566
+ const hash = await hashFile(analysis.path);
567
+ updates.push({
568
+ path: analysis.path,
569
+ hash,
570
+ isGenerated: analysis.isGenerated,
571
+ });
572
+ }
573
+ }
574
+ versionFile = bulkUpdateFileHashes(versionFile, updates);
575
+ await saveVersionFile(CLAUDE_DIR, versionFile);
576
+ }
577
+ /**
578
+ * Show success message after initialization.
579
+ */
580
+ function showSuccessMessage() {
139
581
  console.log('');
140
582
  logger.divider();
141
583
  console.log('');
@@ -163,8 +605,46 @@ export async function init(options) {
163
605
  console.log(chalk.cyan(' → PROMPTS.md'));
164
606
  console.log('');
165
607
  }
166
- async function checkExistingSetup() {
167
- const fs = await import('fs-extra');
168
- return fs.pathExists('.claude/agents');
608
+ async function performRollback(ctx) {
609
+ const rollbackSpinner = ora('Rolling back...').start();
610
+ // Restore modified files
611
+ for (const [filePath, content] of ctx.modifiedFiles) {
612
+ try {
613
+ await fs.writeFile(filePath, content);
614
+ logger.debug(`Restored: ${filePath}`);
615
+ }
616
+ catch (error) {
617
+ logger.debug(`Failed to restore ${filePath}`, error);
618
+ }
619
+ }
620
+ // Remove created paths (reverse order for proper cleanup)
621
+ for (const filePath of ctx.createdPaths.reverse()) {
622
+ try {
623
+ if (await fs.pathExists(filePath)) {
624
+ await fs.remove(filePath);
625
+ logger.debug(`Removed: ${filePath}`);
626
+ }
627
+ }
628
+ catch (error) {
629
+ logger.debug(`Failed to remove ${filePath}`, error);
630
+ }
631
+ }
632
+ // Try to clean up parent directories if empty
633
+ const parentDirs = ['.claude', '.knowledge', '.tasks', '.agents', '.supervisor'];
634
+ for (const dir of parentDirs) {
635
+ try {
636
+ if (await fs.pathExists(dir)) {
637
+ const contents = await fs.readdir(dir);
638
+ if (contents.length === 0 || contents.every(f => f === '.gitkeep')) {
639
+ await fs.remove(dir);
640
+ logger.debug(`Removed empty dir: ${dir}`);
641
+ }
642
+ }
643
+ }
644
+ catch (error) {
645
+ logger.debug(`Failed to clean up ${dir}`, error);
646
+ }
647
+ }
648
+ rollbackSpinner.succeed('Rolled back changes');
169
649
  }
170
650
  //# sourceMappingURL=init.js.map