@wundr.io/cli 1.0.0 → 1.0.2-dev.20260530174250.ef0ec927
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.
- package/README.md +696 -280
- package/bin/wundr.js +13 -5
- package/package.json +30 -9
- package/src/ai/ai-service.ts +6 -4
- package/src/ai/claude-client.ts +6 -2
- package/src/ai/conversation-manager.ts +12 -5
- package/src/cli.ts +42 -13
- package/src/commands/ai.ts +340 -64
- package/src/commands/alignment.ts +1212 -0
- package/src/commands/analyze-optimized.ts +371 -33
- package/src/commands/analyze.ts +8 -6
- package/src/commands/batch.ts +166 -26
- package/src/commands/chat.ts +20 -10
- package/src/commands/claude-init.ts +31 -27
- package/src/commands/claude-setup.ts +761 -81
- package/src/commands/computer-setup.ts +524 -12
- package/src/commands/create-command.ts +3 -3
- package/src/commands/create.ts +9 -6
- package/src/commands/dashboard.ts +11 -6
- package/src/commands/govern.ts +11 -6
- package/src/commands/governance.ts +1005 -0
- package/src/commands/guardian.ts +887 -0
- package/src/commands/init.ts +104 -11
- package/src/commands/orchestrator.ts +789 -0
- package/src/commands/performance-optimizer.ts +15 -10
- package/src/commands/plugins.ts +8 -5
- package/src/commands/project-update.ts +1156 -0
- package/src/commands/rag.ts +1011 -0
- package/src/commands/session.ts +631 -0
- package/src/commands/setup.ts +42 -344
- package/src/commands/test-init.ts +3 -2
- package/src/commands/test.ts +3 -2
- package/src/commands/watch.ts +21 -11
- package/src/commands/worktree.ts +1057 -0
- package/src/context/context-manager.ts +5 -2
- package/src/context/session-manager.ts +18 -7
- package/src/framework/command-interface.ts +520 -0
- package/src/framework/command-registry.ts +942 -0
- package/src/framework/completion-exporter.ts +383 -0
- package/src/framework/debug-logger.ts +519 -0
- package/src/framework/error-handler.ts +867 -0
- package/src/framework/help-generator.ts +540 -0
- package/src/framework/index.ts +169 -0
- package/src/framework/interactive-repl.ts +703 -0
- package/src/framework/output-formatter.ts +834 -0
- package/src/framework/progress-manager.ts +539 -0
- package/src/index.ts +3 -2
- package/src/interactive/interactive-mode.ts +14 -7
- package/src/lib/conflict-resolution.ts +818 -0
- package/src/lib/merge-strategy.ts +550 -0
- package/src/lib/safety-mechanisms.ts +451 -0
- package/src/lib/state-detection.ts +1030 -0
- package/src/nlp/command-mapper.ts +8 -3
- package/src/nlp/command-parser.ts +5 -2
- package/src/nlp/intent-parser.ts +23 -9
- package/src/plugins/plugin-manager.ts +50 -24
- package/src/tests/computer-setup-integration.test.ts +470 -0
- package/src/types/index.ts +1 -1
- package/src/types/modules.d.ts +425 -1
- package/src/utils/backup-rollback-manager.ts +366 -0
- package/src/utils/claude-config-installer.ts +823 -0
- package/src/utils/config-manager.ts +9 -6
- package/src/utils/error-handler.ts +3 -1
- package/src/utils/logger.ts +35 -12
- package/templates/batch/ci-cd.yaml +7 -7
- package/test-suites/api/health.spec.ts +20 -23
- package/test-suites/helpers/test-config.ts +14 -13
- package/test-suites/ui/accessibility.spec.ts +27 -22
- package/test-suites/ui/smoke.spec.ts +26 -21
- package/dist/ai/ai-service.d.ts +0 -152
- package/dist/ai/ai-service.d.ts.map +0 -1
- package/dist/ai/ai-service.js +0 -430
- package/dist/ai/ai-service.js.map +0 -1
- package/dist/ai/claude-client.d.ts +0 -130
- package/dist/ai/claude-client.d.ts.map +0 -1
- package/dist/ai/claude-client.js +0 -339
- package/dist/ai/claude-client.js.map +0 -1
- package/dist/ai/conversation-manager.d.ts +0 -164
- package/dist/ai/conversation-manager.d.ts.map +0 -1
- package/dist/ai/conversation-manager.js +0 -612
- package/dist/ai/conversation-manager.js.map +0 -1
- package/dist/ai/index.d.ts +0 -5
- package/dist/ai/index.d.ts.map +0 -1
- package/dist/ai/index.js +0 -8
- package/dist/ai/index.js.map +0 -1
- package/dist/cli.d.ts +0 -36
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -173
- package/dist/cli.js.map +0 -1
- package/dist/commands/ai.d.ts +0 -89
- package/dist/commands/ai.d.ts.map +0 -1
- package/dist/commands/ai.js +0 -735
- package/dist/commands/ai.js.map +0 -1
- package/dist/commands/analyze-optimized.d.ts +0 -14
- package/dist/commands/analyze-optimized.d.ts.map +0 -1
- package/dist/commands/analyze-optimized.js +0 -437
- package/dist/commands/analyze-optimized.js.map +0 -1
- package/dist/commands/analyze.d.ts +0 -65
- package/dist/commands/analyze.d.ts.map +0 -1
- package/dist/commands/analyze.js +0 -435
- package/dist/commands/analyze.js.map +0 -1
- package/dist/commands/batch.d.ts +0 -71
- package/dist/commands/batch.d.ts.map +0 -1
- package/dist/commands/batch.js +0 -738
- package/dist/commands/batch.js.map +0 -1
- package/dist/commands/chat.d.ts +0 -71
- package/dist/commands/chat.d.ts.map +0 -1
- package/dist/commands/chat.js +0 -674
- package/dist/commands/chat.js.map +0 -1
- package/dist/commands/claude-init.d.ts +0 -28
- package/dist/commands/claude-init.d.ts.map +0 -1
- package/dist/commands/claude-init.js +0 -587
- package/dist/commands/claude-init.js.map +0 -1
- package/dist/commands/claude-setup.d.ts +0 -32
- package/dist/commands/claude-setup.d.ts.map +0 -1
- package/dist/commands/claude-setup.js +0 -570
- package/dist/commands/claude-setup.js.map +0 -1
- package/dist/commands/computer-setup-commands.d.ts +0 -39
- package/dist/commands/computer-setup-commands.d.ts.map +0 -1
- package/dist/commands/computer-setup-commands.js +0 -563
- package/dist/commands/computer-setup-commands.js.map +0 -1
- package/dist/commands/computer-setup.d.ts +0 -7
- package/dist/commands/computer-setup.d.ts.map +0 -1
- package/dist/commands/computer-setup.js +0 -481
- package/dist/commands/computer-setup.js.map +0 -1
- package/dist/commands/create-command.d.ts +0 -7
- package/dist/commands/create-command.d.ts.map +0 -1
- package/dist/commands/create-command.js +0 -158
- package/dist/commands/create-command.js.map +0 -1
- package/dist/commands/create.d.ts +0 -74
- package/dist/commands/create.d.ts.map +0 -1
- package/dist/commands/create.js +0 -556
- package/dist/commands/create.js.map +0 -1
- package/dist/commands/dashboard.d.ts +0 -91
- package/dist/commands/dashboard.d.ts.map +0 -1
- package/dist/commands/dashboard.js +0 -537
- package/dist/commands/dashboard.js.map +0 -1
- package/dist/commands/govern.d.ts +0 -70
- package/dist/commands/govern.d.ts.map +0 -1
- package/dist/commands/govern.js +0 -480
- package/dist/commands/govern.js.map +0 -1
- package/dist/commands/init.d.ts +0 -55
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -584
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/performance-optimizer.d.ts +0 -30
- package/dist/commands/performance-optimizer.d.ts.map +0 -1
- package/dist/commands/performance-optimizer.js +0 -649
- package/dist/commands/performance-optimizer.js.map +0 -1
- package/dist/commands/plugins.d.ts +0 -87
- package/dist/commands/plugins.d.ts.map +0 -1
- package/dist/commands/plugins.js +0 -685
- package/dist/commands/plugins.js.map +0 -1
- package/dist/commands/setup.d.ts +0 -29
- package/dist/commands/setup.d.ts.map +0 -1
- package/dist/commands/setup.js +0 -399
- package/dist/commands/setup.js.map +0 -1
- package/dist/commands/test-init.d.ts +0 -9
- package/dist/commands/test-init.d.ts.map +0 -1
- package/dist/commands/test-init.js +0 -222
- package/dist/commands/test-init.js.map +0 -1
- package/dist/commands/test.d.ts +0 -25
- package/dist/commands/test.d.ts.map +0 -1
- package/dist/commands/test.js +0 -217
- package/dist/commands/test.js.map +0 -1
- package/dist/commands/watch.d.ts +0 -76
- package/dist/commands/watch.d.ts.map +0 -1
- package/dist/commands/watch.js +0 -610
- package/dist/commands/watch.js.map +0 -1
- package/dist/context/context-manager.d.ts +0 -155
- package/dist/context/context-manager.d.ts.map +0 -1
- package/dist/context/context-manager.js +0 -383
- package/dist/context/context-manager.js.map +0 -1
- package/dist/context/index.d.ts +0 -3
- package/dist/context/index.d.ts.map +0 -1
- package/dist/context/index.js +0 -6
- package/dist/context/index.js.map +0 -1
- package/dist/context/session-manager.d.ts +0 -207
- package/dist/context/session-manager.d.ts.map +0 -1
- package/dist/context/session-manager.js +0 -682
- package/dist/context/session-manager.js.map +0 -1
- package/dist/index.d.ts +0 -8
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -51
- package/dist/index.js.map +0 -1
- package/dist/interactive/interactive-mode.d.ts +0 -76
- package/dist/interactive/interactive-mode.d.ts.map +0 -1
- package/dist/interactive/interactive-mode.js +0 -730
- package/dist/interactive/interactive-mode.js.map +0 -1
- package/dist/nlp/command-mapper.d.ts +0 -174
- package/dist/nlp/command-mapper.d.ts.map +0 -1
- package/dist/nlp/command-mapper.js +0 -623
- package/dist/nlp/command-mapper.js.map +0 -1
- package/dist/nlp/command-parser.d.ts +0 -106
- package/dist/nlp/command-parser.d.ts.map +0 -1
- package/dist/nlp/command-parser.js +0 -416
- package/dist/nlp/command-parser.js.map +0 -1
- package/dist/nlp/index.d.ts +0 -5
- package/dist/nlp/index.d.ts.map +0 -1
- package/dist/nlp/index.js +0 -8
- package/dist/nlp/index.js.map +0 -1
- package/dist/nlp/intent-classifier.d.ts +0 -59
- package/dist/nlp/intent-classifier.d.ts.map +0 -1
- package/dist/nlp/intent-classifier.js +0 -384
- package/dist/nlp/intent-classifier.js.map +0 -1
- package/dist/nlp/intent-parser.d.ts +0 -152
- package/dist/nlp/intent-parser.d.ts.map +0 -1
- package/dist/nlp/intent-parser.js +0 -739
- package/dist/nlp/intent-parser.js.map +0 -1
- package/dist/plugins/plugin-manager.d.ts +0 -120
- package/dist/plugins/plugin-manager.d.ts.map +0 -1
- package/dist/plugins/plugin-manager.js +0 -595
- package/dist/plugins/plugin-manager.js.map +0 -1
- package/dist/types/index.d.ts +0 -224
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -3
- package/dist/types/index.js.map +0 -1
- package/dist/utils/config-manager.d.ts +0 -73
- package/dist/utils/config-manager.d.ts.map +0 -1
- package/dist/utils/config-manager.js +0 -339
- package/dist/utils/config-manager.js.map +0 -1
- package/dist/utils/error-handler.d.ts +0 -46
- package/dist/utils/error-handler.d.ts.map +0 -1
- package/dist/utils/error-handler.js +0 -169
- package/dist/utils/error-handler.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -25
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -94
- package/dist/utils/logger.js.map +0 -1
- package/src/commands/computer-setup-commands.ts +0 -709
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
1
2
|
import { EventEmitter } from 'events';
|
|
2
|
-
|
|
3
|
+
|
|
3
4
|
import { logger } from '../utils/logger';
|
|
4
|
-
|
|
5
|
+
|
|
6
|
+
import type { IntentResult } from './intent-parser';
|
|
7
|
+
import type { ChildProcess } from 'child_process';
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Command execution result
|
|
@@ -807,7 +810,9 @@ export class CommandMapper extends EventEmitter {
|
|
|
807
810
|
case 'directory_exists':
|
|
808
811
|
const fsDir = await import('fs-extra');
|
|
809
812
|
const dirPath = parameters[rule.rule as string];
|
|
810
|
-
if (!dirPath)
|
|
813
|
+
if (!dirPath) {
|
|
814
|
+
return false;
|
|
815
|
+
}
|
|
811
816
|
const stats = await fsDir.stat(dirPath).catch(() => null);
|
|
812
817
|
return stats ? stats.isDirectory() : false;
|
|
813
818
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { logger } from '../utils/logger';
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import type { AIService, ConversationContext } from '../ai/ai-service';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Natural language command parsing result
|
|
@@ -141,7 +142,9 @@ export class CommandParser {
|
|
|
141
142
|
|
|
142
143
|
for (let i = 0; i < steps.length; i++) {
|
|
143
144
|
const step = steps[i];
|
|
144
|
-
if (!step)
|
|
145
|
+
if (!step) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
145
148
|
|
|
146
149
|
const stepResult = await this.parseCommand(step, context);
|
|
147
150
|
|
package/src/nlp/intent-parser.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import { logger } from '../utils/logger';
|
|
4
4
|
|
|
5
|
+
import type { ClaudeClient, ClaudeMessage } from '../ai/claude-client';
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Intent classification result
|
|
7
9
|
*/
|
|
@@ -559,7 +561,9 @@ Extract parameters from the user input and respond with JSON only:
|
|
|
559
561
|
};
|
|
560
562
|
|
|
561
563
|
for (const [intent, pattern] of this.commandPatterns) {
|
|
562
|
-
if (!availableCommands.includes(intent))
|
|
564
|
+
if (!availableCommands.includes(intent)) {
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
563
567
|
|
|
564
568
|
const confidence = this.calculatePatternScore(input, pattern);
|
|
565
569
|
|
|
@@ -592,7 +596,9 @@ Extract parameters from the user input and respond with JSON only:
|
|
|
592
596
|
|
|
593
597
|
let matchCount = 0;
|
|
594
598
|
for (const word of patternWords) {
|
|
595
|
-
if (word.startsWith('{') && word.endsWith('}'))
|
|
599
|
+
if (word.startsWith('{') && word.endsWith('}')) {
|
|
600
|
+
continue;
|
|
601
|
+
} // Skip parameters
|
|
596
602
|
if (inputWords.includes(word)) {
|
|
597
603
|
matchCount++;
|
|
598
604
|
}
|
|
@@ -752,12 +758,13 @@ Analyze and respond with JSON only:
|
|
|
752
758
|
|
|
753
759
|
if (patternResult.intent === aiResult.intent) {
|
|
754
760
|
finalResult.confidence = Math.min(0.98, aiResult.confidence + 0.1);
|
|
755
|
-
finalResult.reasoning +=
|
|
761
|
+
finalResult.reasoning += ' (confirmed by pattern matching)';
|
|
756
762
|
} else if (patternResult.confidence > 0.8) {
|
|
757
763
|
// High-confidence pattern match might override AI
|
|
758
764
|
if (patternResult.confidence > aiResult.confidence) {
|
|
759
765
|
finalResult = patternResult;
|
|
760
|
-
finalResult.reasoning +=
|
|
766
|
+
finalResult.reasoning +=
|
|
767
|
+
' (overridden by high-confidence pattern match)';
|
|
761
768
|
}
|
|
762
769
|
}
|
|
763
770
|
|
|
@@ -784,7 +791,8 @@ Analyze and respond with JSON only:
|
|
|
784
791
|
// Add safety warnings for destructive commands
|
|
785
792
|
const commandPattern = this.commandPatterns.get(result.intent);
|
|
786
793
|
if (commandPattern?.destructive) {
|
|
787
|
-
result.clarification =
|
|
794
|
+
result.clarification =
|
|
795
|
+
'This is a destructive operation. Are you sure you want to proceed?';
|
|
788
796
|
}
|
|
789
797
|
|
|
790
798
|
// Enrich with context-aware suggestions
|
|
@@ -922,7 +930,9 @@ Analyze and respond with JSON only:
|
|
|
922
930
|
|
|
923
931
|
for (const command of availableCommands) {
|
|
924
932
|
const pattern = this.commandPatterns.get(command);
|
|
925
|
-
if (!pattern)
|
|
933
|
+
if (!pattern) {
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
926
936
|
|
|
927
937
|
// Check if partial input matches command or examples
|
|
928
938
|
const confidence = Math.max(
|
|
@@ -1063,8 +1073,12 @@ Provide ${limit} suggestions in JSON format:
|
|
|
1063
1073
|
negativeWords.includes(word)
|
|
1064
1074
|
).length;
|
|
1065
1075
|
|
|
1066
|
-
if (positiveCount > negativeCount)
|
|
1067
|
-
|
|
1076
|
+
if (positiveCount > negativeCount) {
|
|
1077
|
+
return 'positive';
|
|
1078
|
+
}
|
|
1079
|
+
if (negativeCount > positiveCount) {
|
|
1080
|
+
return 'negative';
|
|
1081
|
+
}
|
|
1068
1082
|
return 'neutral';
|
|
1069
1083
|
}
|
|
1070
1084
|
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
1
|
import { spawn, type ChildProcess } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
-
import
|
|
6
|
-
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
|
|
7
7
|
import { errorHandler } from '../utils/error-handler';
|
|
8
|
-
import {
|
|
8
|
+
import { logger } from '../utils/logger';
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
Plugin,
|
|
12
|
+
PluginContext,
|
|
13
|
+
PluginCommand,
|
|
14
|
+
PluginHook,
|
|
15
|
+
} from '../types';
|
|
16
|
+
import type { ConfigManager } from '../utils/config-manager';
|
|
9
17
|
|
|
10
18
|
/**
|
|
11
19
|
* Plugin management system for CLI extensibility
|
|
@@ -306,28 +314,46 @@ export class PluginManager {
|
|
|
306
314
|
}
|
|
307
315
|
|
|
308
316
|
/**
|
|
309
|
-
* Get available plugins
|
|
317
|
+
* Get available plugins by scanning the plugins directory on the filesystem.
|
|
318
|
+
* Returns an empty array if the directory does not exist or contains no valid plugins.
|
|
310
319
|
*/
|
|
311
320
|
async getAvailablePlugins(): Promise<any[]> {
|
|
312
321
|
try {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
322
|
+
if (!(await fs.pathExists(this.pluginsDir))) {
|
|
323
|
+
logger.debug(`Plugin directory not found: ${this.pluginsDir}`);
|
|
324
|
+
return [];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const entries = await fs.readdir(this.pluginsDir);
|
|
328
|
+
const plugins: any[] = [];
|
|
329
|
+
|
|
330
|
+
for (const entry of entries) {
|
|
331
|
+
const entryPath = path.join(this.pluginsDir, entry);
|
|
332
|
+
const packageJsonPath = path.join(entryPath, 'package.json');
|
|
333
|
+
|
|
334
|
+
if (!(await fs.pathExists(packageJsonPath))) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
340
|
+
const stat = await fs.stat(entryPath);
|
|
341
|
+
plugins.push({
|
|
342
|
+
name: packageJson.name || entry,
|
|
343
|
+
version: packageJson.version || 'unknown',
|
|
344
|
+
description: packageJson.description || '',
|
|
345
|
+
author: packageJson.author || '',
|
|
346
|
+
updated: stat.mtime.toISOString(),
|
|
347
|
+
});
|
|
348
|
+
} catch (parseError) {
|
|
349
|
+
logger.debug(
|
|
350
|
+
`Failed to read package.json for plugin ${entry}:`,
|
|
351
|
+
parseError
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return plugins;
|
|
331
357
|
} catch (error) {
|
|
332
358
|
logger.debug('Failed to get available plugins:', error);
|
|
333
359
|
return [];
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for computer-setup with Claude Code configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
describe,
|
|
7
|
+
it,
|
|
8
|
+
expect,
|
|
9
|
+
beforeEach,
|
|
10
|
+
afterEach,
|
|
11
|
+
jest,
|
|
12
|
+
} from '@jest/globals';
|
|
13
|
+
import * as fs from 'fs/promises';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
import { existsSync } from 'fs';
|
|
16
|
+
import { BackupRollbackManager } from '../utils/backup-rollback-manager';
|
|
17
|
+
import { ClaudeConfigInstaller } from '../utils/claude-config-installer';
|
|
18
|
+
|
|
19
|
+
describe('Computer Setup Integration Tests', () => {
|
|
20
|
+
const testDir = path.join(__dirname, '.test-temp');
|
|
21
|
+
const backupDir = path.join(testDir, 'backups');
|
|
22
|
+
const claudeDir = path.join(testDir, '.claude');
|
|
23
|
+
|
|
24
|
+
beforeEach(async () => {
|
|
25
|
+
// Create test directories
|
|
26
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
27
|
+
await fs.mkdir(backupDir, { recursive: true });
|
|
28
|
+
await fs.mkdir(claudeDir, { recursive: true });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(async () => {
|
|
32
|
+
// Clean up test directories
|
|
33
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('BackupRollbackManager', () => {
|
|
37
|
+
let backupManager: BackupRollbackManager;
|
|
38
|
+
|
|
39
|
+
beforeEach(async () => {
|
|
40
|
+
backupManager = new BackupRollbackManager(backupDir);
|
|
41
|
+
await backupManager.initialize();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should initialize backup directory', async () => {
|
|
45
|
+
expect(existsSync(backupDir)).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should create backup of files', async () => {
|
|
49
|
+
// Create test files
|
|
50
|
+
const testFile1 = path.join(testDir, 'test1.txt');
|
|
51
|
+
const testFile2 = path.join(testDir, 'test2.txt');
|
|
52
|
+
await fs.writeFile(testFile1, 'content1');
|
|
53
|
+
await fs.writeFile(testFile2, 'content2');
|
|
54
|
+
|
|
55
|
+
// Create backup
|
|
56
|
+
const metadata = await backupManager.createBackup(
|
|
57
|
+
[testFile1, testFile2],
|
|
58
|
+
'Test backup'
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(metadata.success).toBe(true);
|
|
62
|
+
expect(metadata.files).toHaveLength(2);
|
|
63
|
+
expect(metadata.backupId).toBeTruthy();
|
|
64
|
+
|
|
65
|
+
// Verify backup files exist
|
|
66
|
+
for (const file of metadata.files) {
|
|
67
|
+
expect(existsSync(file.backupPath)).toBe(true);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should list backups', async () => {
|
|
72
|
+
// Create test file and backup
|
|
73
|
+
const testFile = path.join(testDir, 'test.txt');
|
|
74
|
+
await fs.writeFile(testFile, 'content');
|
|
75
|
+
|
|
76
|
+
await backupManager.createBackup([testFile], 'Backup 1');
|
|
77
|
+
await backupManager.createBackup([testFile], 'Backup 2');
|
|
78
|
+
|
|
79
|
+
const backups = await backupManager.listBackups();
|
|
80
|
+
expect(backups.length).toBeGreaterThanOrEqual(2);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should restore files from backup', async () => {
|
|
84
|
+
// Create original file
|
|
85
|
+
const testFile = path.join(testDir, 'test.txt');
|
|
86
|
+
await fs.writeFile(testFile, 'original content');
|
|
87
|
+
|
|
88
|
+
// Create backup
|
|
89
|
+
const metadata = await backupManager.createBackup(
|
|
90
|
+
[testFile],
|
|
91
|
+
'Test backup'
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Modify file
|
|
95
|
+
await fs.writeFile(testFile, 'modified content');
|
|
96
|
+
|
|
97
|
+
// Restore from backup
|
|
98
|
+
const success = await backupManager.rollback({
|
|
99
|
+
backupId: metadata.backupId,
|
|
100
|
+
dryRun: false,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(success).toBe(true);
|
|
104
|
+
|
|
105
|
+
// Verify content restored
|
|
106
|
+
const content = await fs.readFile(testFile, 'utf-8');
|
|
107
|
+
expect(content).toBe('original content');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should verify backup integrity', async () => {
|
|
111
|
+
// Create test file and backup
|
|
112
|
+
const testFile = path.join(testDir, 'test.txt');
|
|
113
|
+
await fs.writeFile(testFile, 'content');
|
|
114
|
+
|
|
115
|
+
const metadata = await backupManager.createBackup(
|
|
116
|
+
[testFile],
|
|
117
|
+
'Test backup'
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const isValid = await backupManager.verifyBackup(metadata.backupId);
|
|
121
|
+
expect(isValid).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should clean up old backups', async () => {
|
|
125
|
+
// Create multiple backups
|
|
126
|
+
const testFile = path.join(testDir, 'test.txt');
|
|
127
|
+
await fs.writeFile(testFile, 'content');
|
|
128
|
+
|
|
129
|
+
for (let i = 0; i < 10; i++) {
|
|
130
|
+
await backupManager.createBackup([testFile], `Backup ${i}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Clean up, keeping only 5
|
|
134
|
+
await backupManager.cleanupOldBackups(5);
|
|
135
|
+
|
|
136
|
+
const backups = await backupManager.listBackups();
|
|
137
|
+
expect(backups.length).toBeLessThanOrEqual(5);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should handle dry-run rollback', async () => {
|
|
141
|
+
const testFile = path.join(testDir, 'test.txt');
|
|
142
|
+
await fs.writeFile(testFile, 'original');
|
|
143
|
+
|
|
144
|
+
const metadata = await backupManager.createBackup([testFile], 'Test');
|
|
145
|
+
|
|
146
|
+
await fs.writeFile(testFile, 'modified');
|
|
147
|
+
|
|
148
|
+
const success = await backupManager.rollback({
|
|
149
|
+
backupId: metadata.backupId,
|
|
150
|
+
dryRun: true,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(success).toBe(true);
|
|
154
|
+
|
|
155
|
+
// File should not be restored in dry-run
|
|
156
|
+
const content = await fs.readFile(testFile, 'utf-8');
|
|
157
|
+
expect(content).toBe('modified');
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('ClaudeConfigInstaller', () => {
|
|
162
|
+
let installer: ClaudeConfigInstaller;
|
|
163
|
+
|
|
164
|
+
beforeEach(async () => {
|
|
165
|
+
installer = new ClaudeConfigInstaller({
|
|
166
|
+
claudeDir,
|
|
167
|
+
sourceDir: testDir,
|
|
168
|
+
});
|
|
169
|
+
await installer.initialize();
|
|
170
|
+
|
|
171
|
+
// Create mock CLAUDE.md
|
|
172
|
+
await fs.writeFile(
|
|
173
|
+
path.join(testDir, 'CLAUDE.md'),
|
|
174
|
+
'# Claude Configuration\nTest content'
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should initialize directory structure', async () => {
|
|
179
|
+
expect(existsSync(claudeDir)).toBe(true);
|
|
180
|
+
expect(existsSync(path.join(claudeDir, 'hooks'))).toBe(true);
|
|
181
|
+
expect(existsSync(path.join(claudeDir, 'agents'))).toBe(true);
|
|
182
|
+
expect(existsSync(path.join(claudeDir, 'workflows'))).toBe(true);
|
|
183
|
+
expect(existsSync(path.join(claudeDir, 'scripts'))).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should install CLAUDE.md', async () => {
|
|
187
|
+
const result = await installer.install({
|
|
188
|
+
dryRun: false,
|
|
189
|
+
skipBackup: true,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
expect(result.success).toBe(true);
|
|
193
|
+
expect(result.installed).toContain('CLAUDE.md');
|
|
194
|
+
expect(existsSync(path.join(claudeDir, 'CLAUDE.md'))).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should install hooks', async () => {
|
|
198
|
+
const result = await installer.install({
|
|
199
|
+
dryRun: false,
|
|
200
|
+
skipBackup: true,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
expect(result.success).toBe(true);
|
|
204
|
+
|
|
205
|
+
const hooksDir = path.join(claudeDir, 'hooks');
|
|
206
|
+
expect(existsSync(path.join(hooksDir, 'pre-commit'))).toBe(true);
|
|
207
|
+
expect(existsSync(path.join(hooksDir, 'post-checkout'))).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should install conventions', async () => {
|
|
211
|
+
const result = await installer.install({
|
|
212
|
+
dryRun: false,
|
|
213
|
+
skipBackup: true,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
expect(result.success).toBe(true);
|
|
217
|
+
expect(result.installed).toContain('conventions.json');
|
|
218
|
+
|
|
219
|
+
const conventionsPath = path.join(claudeDir, 'conventions.json');
|
|
220
|
+
expect(existsSync(conventionsPath)).toBe(true);
|
|
221
|
+
|
|
222
|
+
const conventions = JSON.parse(
|
|
223
|
+
await fs.readFile(conventionsPath, 'utf-8')
|
|
224
|
+
);
|
|
225
|
+
expect(conventions).toHaveProperty('fileNaming');
|
|
226
|
+
expect(conventions).toHaveProperty('codeStyle');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should install agent templates', async () => {
|
|
230
|
+
const result = await installer.install({
|
|
231
|
+
dryRun: false,
|
|
232
|
+
skipBackup: true,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
expect(result.success).toBe(true);
|
|
236
|
+
|
|
237
|
+
const agentsDir = path.join(claudeDir, 'agents');
|
|
238
|
+
expect(existsSync(path.join(agentsDir, 'backend-developer.json'))).toBe(
|
|
239
|
+
true
|
|
240
|
+
);
|
|
241
|
+
expect(existsSync(path.join(agentsDir, 'frontend-developer.json'))).toBe(
|
|
242
|
+
true
|
|
243
|
+
);
|
|
244
|
+
expect(existsSync(path.join(agentsDir, 'fullstack-developer.json'))).toBe(
|
|
245
|
+
true
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should install git-worktree workflows', async () => {
|
|
250
|
+
const result = await installer.install({
|
|
251
|
+
dryRun: false,
|
|
252
|
+
skipBackup: true,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
expect(result.success).toBe(true);
|
|
256
|
+
|
|
257
|
+
const workflowsDir = path.join(claudeDir, 'workflows');
|
|
258
|
+
expect(
|
|
259
|
+
existsSync(path.join(workflowsDir, 'feature-development.json'))
|
|
260
|
+
).toBe(true);
|
|
261
|
+
expect(existsSync(path.join(workflowsDir, 'bug-fix.json'))).toBe(true);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should install validation scripts', async () => {
|
|
265
|
+
const result = await installer.install({
|
|
266
|
+
dryRun: false,
|
|
267
|
+
skipBackup: true,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
expect(result.success).toBe(true);
|
|
271
|
+
|
|
272
|
+
const scriptsDir = path.join(claudeDir, 'scripts');
|
|
273
|
+
expect(existsSync(path.join(scriptsDir, 'validate-setup.sh'))).toBe(true);
|
|
274
|
+
expect(existsSync(path.join(scriptsDir, 'check-config.sh'))).toBe(true);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('should handle dry-run installation', async () => {
|
|
278
|
+
const result = await installer.install({
|
|
279
|
+
dryRun: true,
|
|
280
|
+
skipBackup: true,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
expect(result.success).toBe(true);
|
|
284
|
+
expect(result.installed.every(f => f.includes('dry-run'))).toBe(true);
|
|
285
|
+
|
|
286
|
+
// Files should not exist in dry-run
|
|
287
|
+
expect(existsSync(path.join(claudeDir, 'CLAUDE.md'))).toBe(false);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should skip existing files without overwrite', async () => {
|
|
291
|
+
// Create existing file
|
|
292
|
+
await fs.writeFile(path.join(claudeDir, 'CLAUDE.md'), 'existing content');
|
|
293
|
+
|
|
294
|
+
const result = await installer.install({
|
|
295
|
+
dryRun: false,
|
|
296
|
+
skipBackup: true,
|
|
297
|
+
overwrite: false,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
expect(result.skipped).toContain('CLAUDE.md');
|
|
301
|
+
|
|
302
|
+
// Content should remain unchanged
|
|
303
|
+
const content = await fs.readFile(
|
|
304
|
+
path.join(claudeDir, 'CLAUDE.md'),
|
|
305
|
+
'utf-8'
|
|
306
|
+
);
|
|
307
|
+
expect(content).toBe('existing content');
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('should overwrite existing files with overwrite flag', async () => {
|
|
311
|
+
// Create existing file
|
|
312
|
+
await fs.writeFile(path.join(claudeDir, 'CLAUDE.md'), 'existing content');
|
|
313
|
+
|
|
314
|
+
const result = await installer.install({
|
|
315
|
+
dryRun: false,
|
|
316
|
+
skipBackup: true,
|
|
317
|
+
overwrite: true,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
expect(result.installed).toContain('CLAUDE.md');
|
|
321
|
+
|
|
322
|
+
// Content should be updated
|
|
323
|
+
const content = await fs.readFile(
|
|
324
|
+
path.join(claudeDir, 'CLAUDE.md'),
|
|
325
|
+
'utf-8'
|
|
326
|
+
);
|
|
327
|
+
expect(content).toContain('Test content');
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('should create backup before installation', async () => {
|
|
331
|
+
// Create existing configurations
|
|
332
|
+
await fs.writeFile(path.join(claudeDir, 'CLAUDE.md'), 'existing');
|
|
333
|
+
await fs.writeFile(
|
|
334
|
+
path.join(claudeDir, 'conventions.json'),
|
|
335
|
+
JSON.stringify({ test: true })
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
const result = await installer.install({
|
|
339
|
+
dryRun: false,
|
|
340
|
+
skipBackup: false,
|
|
341
|
+
overwrite: true,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
expect(result.backupId).toBeTruthy();
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
describe('End-to-End Integration', () => {
|
|
349
|
+
it('should complete full installation workflow', async () => {
|
|
350
|
+
const backupManager = new BackupRollbackManager(backupDir);
|
|
351
|
+
const installer = new ClaudeConfigInstaller({
|
|
352
|
+
claudeDir,
|
|
353
|
+
sourceDir: testDir,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
await backupManager.initialize();
|
|
357
|
+
await installer.initialize();
|
|
358
|
+
|
|
359
|
+
// Create mock source file
|
|
360
|
+
await fs.writeFile(
|
|
361
|
+
path.join(testDir, 'CLAUDE.md'),
|
|
362
|
+
'# Enhanced Claude Configuration'
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
// Install configurations
|
|
366
|
+
const installResult = await installer.install({
|
|
367
|
+
dryRun: false,
|
|
368
|
+
skipBackup: false,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
expect(installResult.success).toBe(true);
|
|
372
|
+
expect(installResult.installed.length).toBeGreaterThan(0);
|
|
373
|
+
|
|
374
|
+
// Verify all components installed
|
|
375
|
+
expect(existsSync(path.join(claudeDir, 'CLAUDE.md'))).toBe(true);
|
|
376
|
+
expect(existsSync(path.join(claudeDir, 'conventions.json'))).toBe(true);
|
|
377
|
+
expect(existsSync(path.join(claudeDir, 'hooks', 'pre-commit'))).toBe(
|
|
378
|
+
true
|
|
379
|
+
);
|
|
380
|
+
expect(
|
|
381
|
+
existsSync(path.join(claudeDir, 'agents', 'backend-developer.json'))
|
|
382
|
+
).toBe(true);
|
|
383
|
+
expect(
|
|
384
|
+
existsSync(
|
|
385
|
+
path.join(claudeDir, 'workflows', 'feature-development.json')
|
|
386
|
+
)
|
|
387
|
+
).toBe(true);
|
|
388
|
+
expect(
|
|
389
|
+
existsSync(path.join(claudeDir, 'scripts', 'validate-setup.sh'))
|
|
390
|
+
).toBe(true);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('should support complete backup and rollback cycle', async () => {
|
|
394
|
+
const backupManager = new BackupRollbackManager(backupDir);
|
|
395
|
+
const installer = new ClaudeConfigInstaller({
|
|
396
|
+
claudeDir,
|
|
397
|
+
sourceDir: testDir,
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
await backupManager.initialize();
|
|
401
|
+
await installer.initialize();
|
|
402
|
+
|
|
403
|
+
// Create initial config
|
|
404
|
+
const initialConfig = path.join(claudeDir, 'CLAUDE.md');
|
|
405
|
+
await fs.writeFile(initialConfig, 'Initial configuration');
|
|
406
|
+
|
|
407
|
+
// Create backup
|
|
408
|
+
const backup = await backupManager.createBackup(
|
|
409
|
+
[initialConfig],
|
|
410
|
+
'Pre-update backup'
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
// Update configuration
|
|
414
|
+
await fs.writeFile(initialConfig, 'Updated configuration');
|
|
415
|
+
|
|
416
|
+
// Rollback to original
|
|
417
|
+
const success = await backupManager.rollback({
|
|
418
|
+
backupId: backup.backupId,
|
|
419
|
+
dryRun: false,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
expect(success).toBe(true);
|
|
423
|
+
|
|
424
|
+
// Verify rollback
|
|
425
|
+
const content = await fs.readFile(initialConfig, 'utf-8');
|
|
426
|
+
expect(content).toBe('Initial configuration');
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
describe('Error Handling', () => {
|
|
431
|
+
it('should handle missing source files gracefully', async () => {
|
|
432
|
+
const installer = new ClaudeConfigInstaller({
|
|
433
|
+
claudeDir,
|
|
434
|
+
sourceDir: testDir,
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
await installer.initialize();
|
|
438
|
+
|
|
439
|
+
// Don't create CLAUDE.md source
|
|
440
|
+
const result = await installer.install({
|
|
441
|
+
dryRun: false,
|
|
442
|
+
skipBackup: true,
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Should skip missing files
|
|
446
|
+
expect(result.skipped).toContain('CLAUDE.md');
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('should handle permission errors', async () => {
|
|
450
|
+
const installer = new ClaudeConfigInstaller({
|
|
451
|
+
claudeDir: '/root/unauthorized', // Likely no permission
|
|
452
|
+
sourceDir: testDir,
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
await expect(installer.initialize()).rejects.toThrow();
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('should handle corrupted backup metadata', async () => {
|
|
459
|
+
const backupManager = new BackupRollbackManager(backupDir);
|
|
460
|
+
await backupManager.initialize();
|
|
461
|
+
|
|
462
|
+
// Corrupt metadata file
|
|
463
|
+
const metadataPath = path.join(backupDir, 'metadata.json');
|
|
464
|
+
await fs.writeFile(metadataPath, 'invalid json {');
|
|
465
|
+
|
|
466
|
+
const backups = await backupManager.listBackups();
|
|
467
|
+
expect(backups).toEqual([]);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
});
|
package/src/types/index.ts
CHANGED