@xelth/eck-snapshot 5.9.0 → 6.4.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.
Potentially problematic release.
This version of @xelth/eck-snapshot might be problematic. Click here for more details.
- package/README.md +267 -190
- package/package.json +15 -2
- package/scripts/mcp-eck-core.js +61 -13
- package/setup.json +114 -80
- package/src/cli/cli.js +235 -385
- package/src/cli/commands/createSnapshot.js +336 -122
- package/src/cli/commands/recon.js +244 -0
- package/src/cli/commands/setupMcp.js +278 -19
- package/src/cli/commands/trainTokens.js +42 -32
- package/src/cli/commands/updateSnapshot.js +128 -76
- package/src/core/depthConfig.js +54 -0
- package/src/core/skeletonizer.js +71 -18
- package/src/templates/architect-prompt.template.md +34 -0
- package/src/templates/multiAgent.md +18 -10
- package/src/templates/opencode/coder.template.md +44 -17
- package/src/templates/opencode/junior-architect.template.md +45 -15
- package/src/templates/skeleton-instruction.md +1 -1
- package/src/utils/aiHeader.js +57 -27
- package/src/utils/claudeMdGenerator.js +136 -78
- package/src/utils/fileUtils.js +1011 -1016
- package/src/utils/gitUtils.js +12 -8
- package/src/utils/opencodeAgentsGenerator.js +8 -2
- package/src/utils/projectDetector.js +66 -21
- package/src/utils/tokenEstimator.js +11 -7
- package/src/cli/commands/consilium.js +0 -86
- package/src/cli/commands/detectProfiles.js +0 -98
- package/src/cli/commands/envSync.js +0 -319
- package/src/cli/commands/generateProfileGuide.js +0 -144
- package/src/cli/commands/pruneSnapshot.js +0 -106
- package/src/cli/commands/restoreSnapshot.js +0 -173
- package/src/cli/commands/setupGemini.js +0 -149
- package/src/cli/commands/setupGemini.test.js +0 -115
- package/src/cli/commands/showFile.js +0 -39
- package/src/services/claudeCliService.js +0 -626
- package/src/services/claudeCliService.test.js +0 -267
package/src/utils/gitUtils.js
CHANGED
|
@@ -25,14 +25,18 @@ export async function getGitAnchor(repoPath) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export async function getChangedFiles(repoPath, anchorHash) {
|
|
29
|
-
try {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
export async function getChangedFiles(repoPath, anchorHash, includeWorkingTree = false) {
|
|
29
|
+
try {
|
|
30
|
+
const args = ['diff', '--name-only', anchorHash];
|
|
31
|
+
if (!includeWorkingTree) {
|
|
32
|
+
args.push('HEAD');
|
|
33
|
+
}
|
|
34
|
+
const { stdout } = await execa('git', args, { cwd: repoPath });
|
|
35
|
+
return stdout.split('\n').filter(Boolean);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
throw new Error(`Failed to get git diff: ${e.message}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
36
40
|
|
|
37
41
|
export async function getGitDiffOutput(repoPath, anchorHash, excludeFiles = []) {
|
|
38
42
|
try {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
9
|
* Generates AGENTS.md for OpenCode integration (GLM Z.AI ecosystem only)
|
|
@@ -30,11 +34,12 @@ export async function generateOpenCodeAgents(repoPath, mode, tree, confidentialF
|
|
|
30
34
|
color: '#10a37f'
|
|
31
35
|
};
|
|
32
36
|
|
|
33
|
-
const templatePath = path.join(
|
|
37
|
+
const templatePath = path.join(__dirname, '..', 'templates', 'opencode', 'junior-architect.template.md');
|
|
34
38
|
try {
|
|
35
39
|
let templateContent = await fs.readFile(templatePath, 'utf-8');
|
|
36
40
|
body = templateContent.replace('{{tree}}', tree);
|
|
37
41
|
} catch (error) {
|
|
42
|
+
console.warn(`⚠️ Could not load JAZ template from ${templatePath}: ${error.message}`);
|
|
38
43
|
body = `# 🧠 ROLE: Swarm Orchestrator (GLM-4.7)\n\nDirectory:\n\`\`\`\n${tree}\n\`\`\``;
|
|
39
44
|
}
|
|
40
45
|
} else {
|
|
@@ -48,10 +53,11 @@ export async function generateOpenCodeAgents(repoPath, mode, tree, confidentialF
|
|
|
48
53
|
color: '#44BA81'
|
|
49
54
|
};
|
|
50
55
|
|
|
51
|
-
const templatePath = path.join(
|
|
56
|
+
const templatePath = path.join(__dirname, '..', 'templates', 'opencode', 'coder.template.md');
|
|
52
57
|
try {
|
|
53
58
|
body = await fs.readFile(templatePath, 'utf-8');
|
|
54
59
|
} catch (error) {
|
|
60
|
+
console.warn(`⚠️ Could not load Coder template from ${templatePath}: ${error.message}`);
|
|
55
61
|
body = `# 🛠️ ROLE: Expert Developer`;
|
|
56
62
|
}
|
|
57
63
|
}
|
|
@@ -101,6 +101,10 @@ async function calculateTypeScore(projectPath, pattern) {
|
|
|
101
101
|
if (file === 'package.json') {
|
|
102
102
|
commonSubdirs.push('codex-cli', 'cli', 'client', 'web', 'ui');
|
|
103
103
|
}
|
|
104
|
+
// Android/Gradle files often live inside android/ subdirectory (polyglot monorepos)
|
|
105
|
+
if (file.startsWith('build.gradle') || file.startsWith('settings.gradle')) {
|
|
106
|
+
commonSubdirs.push('android');
|
|
107
|
+
}
|
|
104
108
|
|
|
105
109
|
for (const subdir of commonSubdirs) {
|
|
106
110
|
const subdirExists = await fileExists(path.join(projectPath, subdir, file));
|
|
@@ -120,8 +124,8 @@ async function calculateTypeScore(projectPath, pattern) {
|
|
|
120
124
|
if (rootExists) {
|
|
121
125
|
score += 20; // Each required directory adds points
|
|
122
126
|
} else {
|
|
123
|
-
// Check in common project subdirectories
|
|
124
|
-
const projectSubdirs = ['codex-rs', 'codex-cli', 'src', 'lib', 'app'];
|
|
127
|
+
// Check in common project subdirectories (including android/ for polyglot monorepos)
|
|
128
|
+
const projectSubdirs = ['codex-rs', 'codex-cli', 'src', 'lib', 'app', 'android'];
|
|
125
129
|
for (const projDir of projectSubdirs) {
|
|
126
130
|
const subdirExists = await directoryExists(path.join(projectPath, projDir, dir));
|
|
127
131
|
if (subdirExists) {
|
|
@@ -285,9 +289,12 @@ async function getNodejsDetails(projectPath) {
|
|
|
285
289
|
|
|
286
290
|
try {
|
|
287
291
|
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
292
|
+
if (!(await fileExists(packageJsonPath))) {
|
|
293
|
+
return details;
|
|
294
|
+
}
|
|
288
295
|
const content = await fs.readFile(packageJsonPath, 'utf-8');
|
|
289
296
|
const packageJson = JSON.parse(content);
|
|
290
|
-
|
|
297
|
+
|
|
291
298
|
details.name = packageJson.name;
|
|
292
299
|
details.version = packageJson.version;
|
|
293
300
|
details.hasTypescript = !!packageJson.devDependencies?.typescript || !!packageJson.dependencies?.typescript;
|
|
@@ -393,9 +400,12 @@ async function getReactNativeDetails(projectPath) {
|
|
|
393
400
|
|
|
394
401
|
try {
|
|
395
402
|
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
403
|
+
if (!(await fileExists(packageJsonPath))) {
|
|
404
|
+
return details;
|
|
405
|
+
}
|
|
396
406
|
const content = await fs.readFile(packageJsonPath, 'utf-8');
|
|
397
407
|
const packageJson = JSON.parse(content);
|
|
398
|
-
|
|
408
|
+
|
|
399
409
|
details.name = packageJson.name;
|
|
400
410
|
details.version = packageJson.version;
|
|
401
411
|
details.reactNativeVersion = packageJson.dependencies?.['react-native'];
|
|
@@ -680,25 +690,60 @@ async function findFileRecursive(basePath, fileName, maxDepth = 3) {
|
|
|
680
690
|
}
|
|
681
691
|
|
|
682
692
|
/**
|
|
683
|
-
* Gets project-specific filtering configuration
|
|
684
|
-
*
|
|
693
|
+
* Gets project-specific filtering configuration.
|
|
694
|
+
* Accepts a single type string OR an array of types (for polyglot monorepos).
|
|
695
|
+
* When multiple types are provided, filters from ALL types are merged (union).
|
|
696
|
+
* @param {string|string[]} projectTypes - The detected project type(s)
|
|
685
697
|
* @returns {object} Project-specific filtering rules
|
|
686
698
|
*/
|
|
687
|
-
export async function getProjectSpecificFiltering(
|
|
699
|
+
export async function getProjectSpecificFiltering(projectTypes) {
|
|
688
700
|
const config = await loadSetupConfig();
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
return {
|
|
700
|
-
filesToIgnore: projectSpecific.filesToIgnore || [],
|
|
701
|
-
dirsToIgnore: projectSpecific.dirsToIgnore || [],
|
|
702
|
-
extensionsToIgnore: projectSpecific.extensionsToIgnore || []
|
|
701
|
+
const allProjectSpecific = config.fileFiltering?.projectSpecific || {};
|
|
702
|
+
|
|
703
|
+
// Normalize to array
|
|
704
|
+
const types = Array.isArray(projectTypes) ? projectTypes : [projectTypes];
|
|
705
|
+
|
|
706
|
+
const merged = {
|
|
707
|
+
filesToIgnore: [],
|
|
708
|
+
dirsToIgnore: [],
|
|
709
|
+
extensionsToIgnore: []
|
|
703
710
|
};
|
|
711
|
+
|
|
712
|
+
for (const type of types) {
|
|
713
|
+
const specific = allProjectSpecific[type];
|
|
714
|
+
if (specific) {
|
|
715
|
+
merged.filesToIgnore.push(...(specific.filesToIgnore || []));
|
|
716
|
+
merged.dirsToIgnore.push(...(specific.dirsToIgnore || []));
|
|
717
|
+
merged.extensionsToIgnore.push(...(specific.extensionsToIgnore || []));
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Deduplicate
|
|
722
|
+
merged.filesToIgnore = [...new Set(merged.filesToIgnore)];
|
|
723
|
+
merged.dirsToIgnore = [...new Set(merged.dirsToIgnore)];
|
|
724
|
+
merged.extensionsToIgnore = [...new Set(merged.extensionsToIgnore)];
|
|
725
|
+
|
|
726
|
+
return merged;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Extracts all detected project types from a detection result.
|
|
731
|
+
* Returns array of type strings suitable for getProjectSpecificFiltering.
|
|
732
|
+
* @param {object} detection - Result from detectProjectType()
|
|
733
|
+
* @returns {string[]} All detected project types
|
|
734
|
+
*/
|
|
735
|
+
export function getAllDetectedTypes(detection) {
|
|
736
|
+
if (!detection || detection.type === 'unknown') return [];
|
|
737
|
+
|
|
738
|
+
const types = [detection.type];
|
|
739
|
+
|
|
740
|
+
if (detection.allDetections && detection.allDetections.length > 1) {
|
|
741
|
+
for (const d of detection.allDetections) {
|
|
742
|
+
if (!types.includes(d.type)) {
|
|
743
|
+
types.push(d.type);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return types;
|
|
704
749
|
}
|
|
@@ -83,7 +83,7 @@ export async function estimateTokensWithPolynomial(projectType, fileSizeInBytes)
|
|
|
83
83
|
export function generateTrainingCommand(projectType, estimatedTokens, fileSizeInBytes, projectPath) {
|
|
84
84
|
const projectName = path.basename(projectPath);
|
|
85
85
|
|
|
86
|
-
return `eck-snapshot
|
|
86
|
+
return `eck-snapshot '{"name": "eck_train_tokens", "arguments": {"projectType": "${projectType}", "fileSizeBytes": ${fileSizeInBytes}, "estimatedTokens": ${estimatedTokens}, "actualTokens": `;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/**
|
|
@@ -140,9 +140,9 @@ export async function addTrainingPoint(projectType, fileSizeInBytes, estimatedTo
|
|
|
140
140
|
/**
|
|
141
141
|
* Fetch global token weights from Telemetry Hub and merge them into local training data
|
|
142
142
|
*/
|
|
143
|
-
export async function syncTokenWeights() {
|
|
143
|
+
export async function syncTokenWeights(silent = false) {
|
|
144
144
|
try {
|
|
145
|
-
console.log('Fetching global token weights from Telemetry Hub...');
|
|
145
|
+
if (!silent) console.log('Fetching global token weights from Telemetry Hub...');
|
|
146
146
|
const res = await fetch('https://xelth.com/T/tokens/weights');
|
|
147
147
|
if (!res.ok) throw new Error(res.statusText);
|
|
148
148
|
const data = await res.json();
|
|
@@ -152,12 +152,12 @@ export async function syncTokenWeights() {
|
|
|
152
152
|
// Global coefficients override local ones
|
|
153
153
|
localData.coefficients = { ...localData.coefficients, ...data.coefficients };
|
|
154
154
|
await saveTrainingData(localData);
|
|
155
|
-
console.log('Global token weights synchronized successfully.');
|
|
155
|
+
if (!silent) console.log('Global token weights synchronized successfully.');
|
|
156
156
|
} else {
|
|
157
|
-
console.log('No global weights available yet.');
|
|
157
|
+
if (!silent) console.log('No global weights available yet.');
|
|
158
158
|
}
|
|
159
159
|
} catch (e) {
|
|
160
|
-
console.log('Failed to sync token weights: ' + e.message);
|
|
160
|
+
if (!silent) console.log('Failed to sync token weights: ' + e.message);
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
|
|
@@ -233,7 +233,11 @@ export async function showEstimationStats() {
|
|
|
233
233
|
console.log(` Training points: ${points.length}`);
|
|
234
234
|
|
|
235
235
|
if (points.length > 0) {
|
|
236
|
-
|
|
236
|
+
// Recalculate error against current coefficients, ignoring old stored estimate
|
|
237
|
+
const errors = points.map(p => {
|
|
238
|
+
const currentEstimate = evaluatePolynomial(coefficients, p.fileSizeInBytes);
|
|
239
|
+
return Math.abs(p.actualTokens - currentEstimate);
|
|
240
|
+
});
|
|
237
241
|
const avgError = errors.reduce((a, b) => a + b, 0) / errors.length;
|
|
238
242
|
console.log(` Average error: ${Math.round(avgError)} tokens`);
|
|
239
243
|
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Generate a consilium request for complex decisions
|
|
5
|
-
*/
|
|
6
|
-
async function generateConsiliumRequest(task, complexity, agentId) {
|
|
7
|
-
const request = {
|
|
8
|
-
consilium_request: {
|
|
9
|
-
request_id: `cons-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
10
|
-
timestamp: new Date().toISOString(),
|
|
11
|
-
requesting_agent: agentId,
|
|
12
|
-
complexity_score: complexity,
|
|
13
|
-
|
|
14
|
-
task: {
|
|
15
|
-
type: task.type || "technical_decision",
|
|
16
|
-
title: task.title,
|
|
17
|
-
description: task.description,
|
|
18
|
-
current_implementation: task.currentCode || "N/A",
|
|
19
|
-
proposed_solution: task.proposedSolution || "To be determined",
|
|
20
|
-
constraints: task.constraints || [],
|
|
21
|
-
success_criteria: task.criteria || []
|
|
22
|
-
},
|
|
23
|
-
|
|
24
|
-
consilium_instructions: `
|
|
25
|
-
You are a technical expert participating in a consilium decision.
|
|
26
|
-
|
|
27
|
-
RESPOND WITH:
|
|
28
|
-
1. Your expert opinion on the best approach
|
|
29
|
-
2. Specific technical recommendations
|
|
30
|
-
3. Potential risks and mitigation strategies
|
|
31
|
-
4. Your confidence level (0-100%)
|
|
32
|
-
|
|
33
|
-
FORMAT YOUR RESPONSE AS JSON:
|
|
34
|
-
{
|
|
35
|
-
"expert": "[Your Model Name]",
|
|
36
|
-
"role": "[Your assigned role]",
|
|
37
|
-
"recommendation": {
|
|
38
|
-
"approach": "Detailed technical solution",
|
|
39
|
-
"implementation_steps": ["step1", "step2"],
|
|
40
|
-
"key_benefits": ["benefit1", "benefit2"],
|
|
41
|
-
"risks": ["risk1", "risk2"],
|
|
42
|
-
"mitigation": ["strategy1", "strategy2"]
|
|
43
|
-
},
|
|
44
|
-
"alternatives_considered": ["alt1", "alt2"],
|
|
45
|
-
"confidence": 85,
|
|
46
|
-
"critical_warnings": []
|
|
47
|
-
}
|
|
48
|
-
`,
|
|
49
|
-
|
|
50
|
-
aggregation_rules: {
|
|
51
|
-
minimum_confidence_required: 60,
|
|
52
|
-
consensus_threshold: 0.66,
|
|
53
|
-
veto_roles: ["security_auditor"],
|
|
54
|
-
conflict_resolution: "weighted_average_with_discussion"
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
return request;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export async function generateConsilium(options) {
|
|
63
|
-
console.log('🧠 Generating Consilium Request...');
|
|
64
|
-
|
|
65
|
-
const task = {
|
|
66
|
-
type: options.type || 'technical_decision',
|
|
67
|
-
title: options.title || 'Technical Decision Required',
|
|
68
|
-
description: options.description || 'Please provide a description',
|
|
69
|
-
constraints: options.constraints ? options.constraints.split(',') : [],
|
|
70
|
-
currentCode: options.snapshot || null
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const complexity = options.complexity || 7;
|
|
74
|
-
const agentId = options.agent || 'AGENT_ORCHESTRATOR';
|
|
75
|
-
|
|
76
|
-
const request = await generateConsiliumRequest(task, complexity, agentId);
|
|
77
|
-
|
|
78
|
-
const outputFile = options.output || 'consilium_request.json';
|
|
79
|
-
await fs.writeFile(outputFile, JSON.stringify(request, null, 2));
|
|
80
|
-
|
|
81
|
-
console.log(`✅ Consilium request saved to: ${outputFile}`);
|
|
82
|
-
console.log('\n📋 Next steps:');
|
|
83
|
-
console.log('1. Send this request to multiple LLM experts');
|
|
84
|
-
console.log('2. Collect their responses');
|
|
85
|
-
console.log('3. Run: eck-snapshot process-consilium <responses.json>');
|
|
86
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import ora from 'ora';
|
|
4
|
-
import { executePrompt as askClaude } from '../../services/claudeCliService.js';
|
|
5
|
-
import { scanDirectoryRecursively, generateDirectoryTree, initializeEckManifest, loadConfig } from '../../utils/fileUtils.js';
|
|
6
|
-
import { loadSetupConfig } from '../../config.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Extracts a JSON object from a string that might contain markdown wrappers or log output.
|
|
10
|
-
* Finds the first opening brace '{' and the last closing brace '}' to extract the JSON.
|
|
11
|
-
*/
|
|
12
|
-
function extractJson(text) {
|
|
13
|
-
const match = text.match(/```(json)?([\s\S]*?)```/);
|
|
14
|
-
if (match && match[2]) {
|
|
15
|
-
return match[2].trim();
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const firstBrace = text.indexOf('{');
|
|
19
|
-
const lastBrace = text.lastIndexOf('}');
|
|
20
|
-
|
|
21
|
-
if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
|
|
22
|
-
return text.substring(firstBrace, lastBrace + 1).trim();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return text.trim();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Scans the project structure, saves the directory tree to a file, and asks an AI to generate
|
|
30
|
-
* context profiles, saving them to .eck/profiles.json.
|
|
31
|
-
*/
|
|
32
|
-
export async function detectProfiles(repoPath, options) {
|
|
33
|
-
const spinner = ora('Initializing and scanning project structure...').start();
|
|
34
|
-
try {
|
|
35
|
-
await initializeEckManifest(repoPath);
|
|
36
|
-
|
|
37
|
-
const setupConfig = await loadSetupConfig();
|
|
38
|
-
const userConfig = await loadConfig(options.config);
|
|
39
|
-
const config = {
|
|
40
|
-
...userConfig,
|
|
41
|
-
...setupConfig.fileFiltering,
|
|
42
|
-
...setupConfig.performance
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const allFiles = await scanDirectoryRecursively(repoPath, config, repoPath);
|
|
46
|
-
spinner.text = 'Generating directory tree...';
|
|
47
|
-
const dirTree = await generateDirectoryTree(repoPath, '', allFiles, 0, config.maxDepth, config);
|
|
48
|
-
|
|
49
|
-
if (!dirTree) {
|
|
50
|
-
throw new Error('Failed to generate directory tree or project is empty.');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
spinner.text = 'Saving directory tree to file...';
|
|
54
|
-
const treeFilePath = path.join(repoPath, '.eck', 'directory_tree_for_profiling.md');
|
|
55
|
-
await fs.writeFile(treeFilePath, dirTree);
|
|
56
|
-
|
|
57
|
-
const prompt = `You are a code architect. Based on the file directory tree found in the file at './.eck/directory_tree_for_profiling.md', please identify logical 'context profiles' for splitting the project.
|
|
58
|
-
Your output MUST be ONLY a valid JSON object.
|
|
59
|
-
The keys of the object MUST be the profile names (e.g., 'frontend', 'backend', 'core-logic', 'docs').
|
|
60
|
-
The values MUST be an object containing 'include' and 'exclude' arrays of glob patterns.
|
|
61
|
-
Example: {"frontend": {"include": ["packages/ui/**"], "exclude": []}, "docs": {"include": ["docs/**"], "exclude": []}}.
|
|
62
|
-
DO NOT add any conversational text, introductory sentences, or explanations. Your entire response must be ONLY the JSON object.`;
|
|
63
|
-
|
|
64
|
-
spinner.text = 'Asking AI to analyze directory tree and detect profiles...';
|
|
65
|
-
const aiResponseObject = await askClaude(prompt, { taskSize: allFiles.length });
|
|
66
|
-
const rawText = aiResponseObject.result;
|
|
67
|
-
|
|
68
|
-
if (!rawText || typeof rawText.replace !== 'function') {
|
|
69
|
-
throw new Error(`AI returned invalid content type: ${typeof rawText}`);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
spinner.text = 'Saving generated profiles...';
|
|
73
|
-
const cleanedJson = extractJson(rawText);
|
|
74
|
-
let parsedProfiles;
|
|
75
|
-
try {
|
|
76
|
-
parsedProfiles = JSON.parse(cleanedJson);
|
|
77
|
-
} catch (e) {
|
|
78
|
-
console.error('\nInvalid JSON received from AI:', cleanedJson);
|
|
79
|
-
throw new Error(`AI returned invalid JSON: ${e.message}`);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const outputPath = path.join(repoPath, '.eck', 'profiles.json');
|
|
83
|
-
await fs.writeFile(outputPath, JSON.stringify(parsedProfiles, null, 2));
|
|
84
|
-
|
|
85
|
-
const profileKeys = Object.keys(parsedProfiles);
|
|
86
|
-
spinner.succeed(`Successfully detected and saved ${profileKeys.length} profiles to ${outputPath}`);
|
|
87
|
-
|
|
88
|
-
console.log('\n✨ Detected Profiles:');
|
|
89
|
-
console.log('---------------------------');
|
|
90
|
-
for (const profileName of profileKeys) {
|
|
91
|
-
console.log(` - ${profileName}`);
|
|
92
|
-
}
|
|
93
|
-
console.log('\nYou can now use these profile names with the --profile flag.');
|
|
94
|
-
|
|
95
|
-
} catch (error) {
|
|
96
|
-
spinner.fail(`Failed to detect profiles: ${error.message}`);
|
|
97
|
-
}
|
|
98
|
-
}
|