@xelth/eck-snapshot 2.2.0 → 4.0.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 (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -225
  3. package/index.js +14 -776
  4. package/package.json +25 -7
  5. package/setup.json +805 -0
  6. package/src/cli/cli.js +427 -0
  7. package/src/cli/commands/askGpt.js +29 -0
  8. package/src/cli/commands/autoDocs.js +150 -0
  9. package/src/cli/commands/consilium.js +86 -0
  10. package/src/cli/commands/createSnapshot.js +601 -0
  11. package/src/cli/commands/detectProfiles.js +98 -0
  12. package/src/cli/commands/detectProject.js +112 -0
  13. package/src/cli/commands/generateProfileGuide.js +91 -0
  14. package/src/cli/commands/pruneSnapshot.js +106 -0
  15. package/src/cli/commands/restoreSnapshot.js +173 -0
  16. package/src/cli/commands/setupGemini.js +149 -0
  17. package/src/cli/commands/setupGemini.test.js +115 -0
  18. package/src/cli/commands/trainTokens.js +38 -0
  19. package/src/config.js +81 -0
  20. package/src/services/authService.js +20 -0
  21. package/src/services/claudeCliService.js +621 -0
  22. package/src/services/claudeCliService.test.js +267 -0
  23. package/src/services/dispatcherService.js +33 -0
  24. package/src/services/gptService.js +302 -0
  25. package/src/services/gptService.test.js +120 -0
  26. package/src/templates/agent-prompt.template.md +29 -0
  27. package/src/templates/architect-prompt.template.md +50 -0
  28. package/src/templates/envScanRequest.md +4 -0
  29. package/src/templates/gitWorkflow.md +32 -0
  30. package/src/templates/multiAgent.md +164 -0
  31. package/src/templates/vectorMode.md +22 -0
  32. package/src/utils/aiHeader.js +303 -0
  33. package/src/utils/fileUtils.js +928 -0
  34. package/src/utils/projectDetector.js +704 -0
  35. package/src/utils/tokenEstimator.js +198 -0
  36. package/.ecksnapshot.config.js +0 -35
@@ -0,0 +1,115 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ describe('setupGemini integration', () => {
4
+ it('should validate path resolution logic', () => {
5
+ // Test path join functionality that setupGemini uses
6
+ const currentDir = '/test/project';
7
+ const indexJsPath = `${currentDir}/index.js`;
8
+
9
+ expect(indexJsPath).toBe('/test/project/index.js');
10
+ expect(indexJsPath).toContain('index.js');
11
+ });
12
+
13
+ it('should validate gemini tools directory structure', () => {
14
+ const homeDir = '/home/user';
15
+ const geminiToolsDir = `${homeDir}/.gemini/tools`;
16
+ const claudeTomlPath = `${geminiToolsDir}/claude.toml`;
17
+
18
+ expect(geminiToolsDir).toBe('/home/user/.gemini/tools');
19
+ expect(claudeTomlPath).toBe('/home/user/.gemini/tools/claude.toml');
20
+ });
21
+
22
+ it('should validate TOML content structure', () => {
23
+ const indexJsPath = '/test/project/index.js';
24
+ const envVars = { ECK_SNAPSHOT_PATH: '/test/project' };
25
+
26
+ // Test environment section generation
27
+ const envSection = Object.keys(envVars).length > 0
28
+ ? `# Environment variables from setup.json\n${Object.entries(envVars).map(([key, value]) => `${key} = "${value}"`).join('\n')}\n\n`
29
+ : '';
30
+
31
+ // Test main TOML structure
32
+ const tomlContent = `# Claude.toml - Dynamic configuration for eck-snapshot integration
33
+ # Generated automatically by 'eck-snapshot setup-gemini'
34
+
35
+ ${envSection}[claude]
36
+ name = "eck-snapshot"
37
+ description = "AI-powered repository snapshot and analysis tool with cross-platform support"
38
+ command = "node"
39
+ args = ["${indexJsPath}", "ask-claude"]
40
+
41
+ [claude.metadata]
42
+ version = "4.0.0"
43
+ author = "eck-snapshot"
44
+ platform = "${process.platform}"
45
+ working_directory = "${indexJsPath.replace('/index.js', '')}"`;
46
+
47
+ expect(tomlContent).toContain('[claude]');
48
+ expect(tomlContent).toContain('name = "eck-snapshot"');
49
+ expect(tomlContent).toContain(`args = ["${indexJsPath}", "ask-claude"]`);
50
+ expect(tomlContent).toContain('[claude.metadata]');
51
+ expect(tomlContent).toContain('ECK_SNAPSHOT_PATH = "/test/project"');
52
+ });
53
+
54
+ it('should handle cross-platform paths correctly', () => {
55
+ const testPaths = [
56
+ { platform: 'windows', path: 'C:\\Users\\test\\project\\index.js' },
57
+ { platform: 'unix', path: '/home/user/project/index.js' },
58
+ { platform: 'wsl', path: '/mnt/c/Users/test/project/index.js' }
59
+ ];
60
+
61
+ testPaths.forEach(({ platform, path }) => {
62
+ expect(path).toContain('index.js');
63
+ expect(path.length).toBeGreaterThan(0);
64
+
65
+ // Test that the path is absolute (platform-appropriate)
66
+ if (platform === 'windows') {
67
+ expect(path).toMatch(/^[A-Z]:\\/);
68
+ } else {
69
+ expect(path).toMatch(/^\//);
70
+ }
71
+ });
72
+ });
73
+
74
+ it('should validate error handling patterns', () => {
75
+ // Test error message patterns that setupGemini should handle
76
+ const errorPatterns = [
77
+ 'gemini-cli not found in PATH',
78
+ 'Could not find index.js',
79
+ 'Failed to create gemini tools directory',
80
+ 'Failed to write claude.toml'
81
+ ];
82
+
83
+ errorPatterns.forEach(pattern => {
84
+ expect(pattern).toBeDefined();
85
+ expect(typeof pattern).toBe('string');
86
+ expect(pattern.length).toBeGreaterThan(0);
87
+ });
88
+ });
89
+
90
+ it('should test JSON parsing for setup.json', () => {
91
+ const validSetupData = {
92
+ environmentDetection: {
93
+ detected: true
94
+ }
95
+ };
96
+
97
+ const jsonString = JSON.stringify(validSetupData);
98
+ const parsed = JSON.parse(jsonString);
99
+
100
+ expect(parsed.environmentDetection).toBeDefined();
101
+ expect(parsed.environmentDetection.detected).toBe(true);
102
+
103
+ // Test invalid JSON handling pattern
104
+ const invalidJson = 'invalid json {';
105
+ let parseError = null;
106
+ try {
107
+ JSON.parse(invalidJson);
108
+ } catch (e) {
109
+ parseError = e;
110
+ }
111
+
112
+ expect(parseError).toBeDefined();
113
+ expect(parseError.message).toContain('JSON');
114
+ });
115
+ });
@@ -0,0 +1,38 @@
1
+ import { addTrainingPoint, showEstimationStats } from '../../utils/tokenEstimator.js';
2
+
3
+ /**
4
+ * Train token estimation with actual results
5
+ * @param {string} projectType - Type of project (android, nodejs, etc.)
6
+ * @param {string} fileSizeStr - File size in bytes
7
+ * @param {string} estimatedStr - Estimated tokens
8
+ * @param {string} actualStr - Actual tokens (from user input)
9
+ */
10
+ export async function trainTokens(projectType, fileSizeStr, estimatedStr, actualStr) {
11
+ try {
12
+ const fileSizeInBytes = parseInt(fileSizeStr, 10);
13
+ const estimatedTokens = parseInt(estimatedStr, 10);
14
+
15
+ // Parse actual tokens from user input (remove any text like "tokens", commas, etc.)
16
+ const actualTokens = parseInt(actualStr.replace(/[^\d]/g, ''), 10);
17
+
18
+ if (isNaN(fileSizeInBytes) || isNaN(estimatedTokens) || isNaN(actualTokens)) {
19
+ throw new Error('Invalid numeric values provided');
20
+ }
21
+
22
+ await addTrainingPoint(projectType, fileSizeInBytes, estimatedTokens, actualTokens);
23
+
24
+ console.log('\nšŸ“ˆ Updated polynomial coefficients for improved estimation.');
25
+
26
+ } catch (error) {
27
+ console.error(`āŒ Error training token estimation: ${error.message}`);
28
+ console.error('Usage: eck-snapshot train-tokens <project-type> <file-size-bytes> <estimated-tokens> <actual-tokens>');
29
+ process.exit(1);
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Show token estimation statistics
35
+ */
36
+ export async function showTokenStats() {
37
+ await showEstimationStats();
38
+ }
package/src/config.js ADDED
@@ -0,0 +1,81 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+
8
+ let cachedConfig = null;
9
+
10
+ export async function loadSetupConfig() {
11
+ if (cachedConfig) {
12
+ return cachedConfig;
13
+ }
14
+
15
+ try {
16
+ const setupPath = path.join(__dirname, '..', 'setup.json');
17
+ const setupContent = await fs.readFile(setupPath, 'utf-8');
18
+ cachedConfig = JSON.parse(setupContent);
19
+ return cachedConfig;
20
+ } catch (error) {
21
+ console.error('Error loading setup.json:', error.message);
22
+ throw new Error('Failed to load setup.json configuration file');
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Loads and merges all profiles (local-first).
28
+ */
29
+ export async function getAllProfiles(repoPath) {
30
+ const globalConfig = await loadSetupConfig();
31
+ const globalProfiles = globalConfig.contextProfiles || {};
32
+
33
+ let localProfiles = {};
34
+ const localProfilePath = path.join(repoPath, '.eck', 'profiles.json');
35
+
36
+ try {
37
+ const localProfileContent = await fs.readFile(localProfilePath, 'utf-8');
38
+ localProfiles = JSON.parse(localProfileContent);
39
+ } catch (e) {
40
+ // No local profiles.json found, which is fine.
41
+ }
42
+
43
+ // Local profiles override global profiles
44
+ return { ...globalProfiles, ...localProfiles };
45
+ }
46
+
47
+ /**
48
+ * Smart profile loader (Step 2 of dynamic profiles).
49
+ * Reads local .eck/profiles.json first, then falls back to global setup.json profiles.
50
+ */
51
+ export async function getProfile(profileName, repoPath) {
52
+ const globalConfig = await loadSetupConfig();
53
+ const globalProfiles = globalConfig.contextProfiles || {};
54
+
55
+ let localProfiles = {};
56
+ const localProfilePath = path.join(repoPath, '.eck', 'profiles.json');
57
+
58
+ try {
59
+ const localProfileContent = await fs.readFile(localProfilePath, 'utf-8');
60
+ localProfiles = JSON.parse(localProfileContent);
61
+ } catch (e) {
62
+ // No local profiles.json found, which is fine. We just use globals.
63
+ }
64
+
65
+ // Local profiles override global profiles
66
+ const allProfiles = { ...globalProfiles, ...localProfiles };
67
+
68
+ return allProfiles[profileName] || null;
69
+ }
70
+
71
+ // Fallback default config for backwards compatibility
72
+ export const DEFAULT_CONFIG = {
73
+ smartModeTokenThreshold: 200000,
74
+ filesToIgnore: ['package-lock.json', '*.log', 'yarn.lock'],
75
+ extensionsToIgnore: ['.sqlite3', '.db', '.DS_Store', '.env', '.pyc'],
76
+ dirsToIgnore: ['node_modules/', '.git/', 'dist/', 'build/'],
77
+ maxFileSize: '10MB',
78
+ maxTotalSize: '100MB',
79
+ maxDepth: 10,
80
+ concurrency: 10
81
+ };
@@ -0,0 +1,20 @@
1
+ import ora from 'ora';
2
+ import { execa } from 'execa';
3
+
4
+ /**
5
+ * Initiates the interactive login flow by spawning 'codex login'.
6
+ * This will open a browser and wait for the user to complete authentication.
7
+ * @returns {Promise<void>}
8
+ */
9
+ export async function initiateLogin() {
10
+ const spinner = ora('Authentication required. Please follow the browser instructions.').start();
11
+ try {
12
+ // Run `codex login` interactively, inheriting stdio to show user instructions.
13
+ await execa('codex', ['login'], { stdio: 'inherit' });
14
+ spinner.succeed('Login successful. Retrying original command...');
15
+ } catch (e) {
16
+ spinner.fail('Login process failed or was cancelled.');
17
+ // Re-throw to notify p-retry that the attempt failed.
18
+ throw new Error(`Login failed: ${e.message}`);
19
+ }
20
+ }