@xelth/eck-snapshot 6.5.1 → 6.6.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.
- package/README.md +1 -0
- package/package.json +1 -1
- package/src/cli/cli.js +4 -0
- package/src/cli/commands/createSnapshot.js +18 -9
- package/src/cli/commands/recon.js +308 -283
- package/src/cli/commands/setupMcp.js +2 -0
- package/src/cli/commands/updateSnapshot.js +126 -114
- package/src/utils/fileUtils.js +1084 -1081
|
@@ -1,283 +1,308 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import micromatch from 'micromatch';
|
|
5
|
-
import isBinaryPath from 'is-binary-path';
|
|
6
|
-
import {
|
|
7
|
-
generateDirectoryTree,
|
|
8
|
-
generateTimestamp,
|
|
9
|
-
readFileWithSizeCheck,
|
|
10
|
-
parseSize,
|
|
11
|
-
loadGitignore,
|
|
12
|
-
getProjectFiles,
|
|
13
|
-
matchesPattern
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import micromatch from 'micromatch';
|
|
5
|
+
import isBinaryPath from 'is-binary-path';
|
|
6
|
+
import {
|
|
7
|
+
generateDirectoryTree,
|
|
8
|
+
generateTimestamp,
|
|
9
|
+
readFileWithSizeCheck,
|
|
10
|
+
parseSize,
|
|
11
|
+
loadGitignore,
|
|
12
|
+
getProjectFiles,
|
|
13
|
+
matchesPattern,
|
|
14
|
+
ensureSnapshotsInGitignore,
|
|
15
|
+
readMlModelMetadata
|
|
16
|
+
} from '../../utils/fileUtils.js';
|
|
17
|
+
import { detectProjectType, getProjectSpecificFiltering, getAllDetectedTypes } from '../../utils/projectDetector.js';
|
|
18
|
+
import { loadSetupConfig } from '../../config.js';
|
|
19
|
+
import { getDepthConfig, DEPTH_SCALE } from '../../core/depthConfig.js';
|
|
20
|
+
import { skeletonize } from '../../core/skeletonizer.js';
|
|
21
|
+
|
|
22
|
+
export async function runReconTool(payload) {
|
|
23
|
+
const toolName = payload.name;
|
|
24
|
+
const args = payload.arguments || {};
|
|
25
|
+
|
|
26
|
+
if (toolName === 'eck_scout') {
|
|
27
|
+
const depth = args.depth !== undefined ? parseInt(args.depth, 10) : 0;
|
|
28
|
+
await runScout(depth);
|
|
29
|
+
} else if (toolName === 'eck_fetch') {
|
|
30
|
+
if (!args.patterns || !Array.isArray(args.patterns)) {
|
|
31
|
+
console.log(chalk.red('❌ Error: eck_fetch requires an array of "patterns" in arguments.'));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
await runFetch(args.patterns);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function runScout(depth = 0) {
|
|
39
|
+
const depthCfg = getDepthConfig(depth);
|
|
40
|
+
const depthInfo = DEPTH_SCALE[depth] || DEPTH_SCALE[0];
|
|
41
|
+
console.log(chalk.blue(`🕵️ Scouting repository (depth ${depth}: ${depthInfo.mode})...`));
|
|
42
|
+
try {
|
|
43
|
+
const repoPath = process.cwd();
|
|
44
|
+
const repoName = path.basename(repoPath);
|
|
45
|
+
const setupConfig = await loadSetupConfig();
|
|
46
|
+
let config = { ...setupConfig.fileFiltering, ...setupConfig.performance };
|
|
47
|
+
|
|
48
|
+
// Apply project-specific filtering (was missing in previous versions)
|
|
49
|
+
const projectDetection = await detectProjectType(repoPath);
|
|
50
|
+
const allTypes = getAllDetectedTypes(projectDetection);
|
|
51
|
+
if (allTypes && allTypes.length > 0) {
|
|
52
|
+
const projectSpecific = await getProjectSpecificFiltering(allTypes);
|
|
53
|
+
config = {
|
|
54
|
+
...config,
|
|
55
|
+
dirsToIgnore: [...(config.dirsToIgnore || []), ...(projectSpecific.dirsToIgnore || [])],
|
|
56
|
+
filesToIgnore: [...(config.filesToIgnore || []), ...(projectSpecific.filesToIgnore || [])],
|
|
57
|
+
extensionsToIgnore: [...(config.extensionsToIgnore || []), ...(projectSpecific.extensionsToIgnore || [])]
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Use a deep maxDepth for scout so the AI can see the full structure
|
|
62
|
+
config.maxDepth = 15;
|
|
63
|
+
|
|
64
|
+
// Use getProjectFiles which respects git tracking natively
|
|
65
|
+
let allFiles = await getProjectFiles(repoPath, config);
|
|
66
|
+
const gitignore = await loadGitignore(repoPath);
|
|
67
|
+
|
|
68
|
+
// Filter binaries, gitignore/eckignore, and file-level ignores
|
|
69
|
+
allFiles = allFiles.filter(f => {
|
|
70
|
+
const normalized = f.replace(/\\/g, '/');
|
|
71
|
+
const mlExt = path.extname(f).toLowerCase();
|
|
72
|
+
const ML_EXTENSIONS = ['.safetensors', '.onnx', '.pt', '.pth', '.h5', '.pb', '.bin', '.ckpt', '.gguf'];
|
|
73
|
+
if (isBinaryPath(f) && !ML_EXTENSIONS.includes(mlExt)) return false;
|
|
74
|
+
if (gitignore.ignores(normalized)) return false;
|
|
75
|
+
if (config.filesToIgnore && matchesPattern(normalized, config.filesToIgnore)) return false;
|
|
76
|
+
return true;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const directoryTree = await generateDirectoryTree(repoPath, '', allFiles, 0, config.maxDepth, config);
|
|
80
|
+
|
|
81
|
+
// Build file contents section if depth > 0
|
|
82
|
+
let fileContentSection = '';
|
|
83
|
+
if (!depthCfg.skipContent) {
|
|
84
|
+
const maxFileSize = parseSize(config.maxFileSize || '10MB');
|
|
85
|
+
let processedCount = 0;
|
|
86
|
+
|
|
87
|
+
for (const file of allFiles) {
|
|
88
|
+
try {
|
|
89
|
+
const fullPath = path.join(repoPath, file);
|
|
90
|
+
const mlExt = path.extname(file).toLowerCase();
|
|
91
|
+
const ML_EXTENSIONS = ['.safetensors', '.onnx', '.pt', '.pth', '.h5', '.pb', '.bin', '.ckpt', '.gguf'];
|
|
92
|
+
|
|
93
|
+
let content;
|
|
94
|
+
if (ML_EXTENSIONS.includes(mlExt)) {
|
|
95
|
+
content = await readMlModelMetadata(fullPath);
|
|
96
|
+
} else {
|
|
97
|
+
content = await readFileWithSizeCheck(fullPath, maxFileSize);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Apply skeletonization
|
|
101
|
+
if (depthCfg.skeleton) {
|
|
102
|
+
content = await skeletonize(content, file, { preserveDocs: depthCfg.preserveDocs !== false });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Apply line truncation
|
|
106
|
+
if (depthCfg.maxLinesPerFile && depthCfg.maxLinesPerFile > 0) {
|
|
107
|
+
const lines = content.split('\n');
|
|
108
|
+
if (lines.length > depthCfg.maxLinesPerFile) {
|
|
109
|
+
content = lines.slice(0, depthCfg.maxLinesPerFile).join('\n');
|
|
110
|
+
content += `\n// ... truncated (${lines.length - depthCfg.maxLinesPerFile} more lines)`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fileContentSection += `--- File: /${file} ---\n\n\`\`\`\n${content}\n\`\`\`\n\n`;
|
|
115
|
+
processedCount++;
|
|
116
|
+
} catch (e) {
|
|
117
|
+
fileContentSection += `--- File: /${file} ---\n\n[ERROR: ${e.message}]\n\n`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log(chalk.gray(` Processed ${processedCount} files at depth ${depth}`));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const timestamp = generateTimestamp();
|
|
125
|
+
const suffix = depth > 0 ? `_d${depth}` : '';
|
|
126
|
+
const filename = `scout_tree_${repoName}_${timestamp}${suffix}.md`;
|
|
127
|
+
|
|
128
|
+
const depthScaleTable = DEPTH_SCALE.map(d => `| ${d.depth} | ${d.mode} | ${d.description} |`).join('\n');
|
|
129
|
+
|
|
130
|
+
let outputContent = `# ⚠️ EXTERNAL REPOSITORY SCOUT: [${repoName}]
|
|
131
|
+
|
|
132
|
+
**CRITICAL INSTRUCTION FOR AI:** You are currently working on your primary project. The data below is strictly for REFERENCE from an external repository named \`${repoName}\`. DO NOT assume the role of architect for this repository. DO NOT attempt to write code for this repository.
|
|
133
|
+
|
|
134
|
+
**Depth:** ${depth} (${depthInfo.mode} — ${depthInfo.description})
|
|
135
|
+
|
|
136
|
+
## How to request more data from this repository
|
|
137
|
+
Use the \`scout\` command with a higher depth level, or \`fetch\` for specific files:
|
|
138
|
+
|
|
139
|
+
**Scout with depth (0-9):**
|
|
140
|
+
\`\`\`bash
|
|
141
|
+
eck-snapshot scout 5 # skeleton mode
|
|
142
|
+
eck-snapshot scout 9 # full content
|
|
143
|
+
\`\`\`
|
|
144
|
+
|
|
145
|
+
**Fetch specific files (run inside this repo's directory):**
|
|
146
|
+
\`\`\`bash
|
|
147
|
+
cd ${repoPath.replace(/\\/g, '/')}
|
|
148
|
+
eck-snapshot fetch "src/**/*.js" "README.md"
|
|
149
|
+
\`\`\`
|
|
150
|
+
|
|
151
|
+
**⚠️ CRITICAL FETCH RULES:**
|
|
152
|
+
1. **\`fetch\` only works inside the repo it scans.** You MUST \`cd\` into the correct project directory first.
|
|
153
|
+
2. **Use RELATIVE paths or glob patterns**, never absolute paths. Files are matched against the repo root.
|
|
154
|
+
3. **If you need files from multiple repos**, issue SEPARATE fetch commands — one per repo, each with its own \`cd\`.
|
|
155
|
+
4. **Prefer glob patterns over exact paths** — tree paths are easy to misread:
|
|
156
|
+
- Instead of \`"src/utils/helper.js"\` use \`"**/helper.js"\`
|
|
157
|
+
- Use \`"**/<filename>"\` to find a file anywhere in the tree.
|
|
158
|
+
|
|
159
|
+
**Depth scale:**
|
|
160
|
+
| Depth | Mode | Description |
|
|
161
|
+
|-------|------|-------------|
|
|
162
|
+
${depthScaleTable}
|
|
163
|
+
|
|
164
|
+
## Directory Structure
|
|
165
|
+
\`\`\`text
|
|
166
|
+
${directoryTree}
|
|
167
|
+
\`\`\`
|
|
168
|
+
`;
|
|
169
|
+
|
|
170
|
+
if (fileContentSection) {
|
|
171
|
+
outputContent += `\n## File Contents (depth ${depth}: ${depthInfo.mode})\n\n${fileContentSection}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
await fs.mkdir(path.join(repoPath, '.eck', 'scouts'), { recursive: true });
|
|
175
|
+
await ensureSnapshotsInGitignore(repoPath);
|
|
176
|
+
const outputPath = path.join(repoPath, '.eck', 'scouts', filename);
|
|
177
|
+
await fs.writeFile(outputPath, outputContent, 'utf-8');
|
|
178
|
+
|
|
179
|
+
const sizeBytes = Buffer.byteLength(outputContent, 'utf-8');
|
|
180
|
+
const sizeStr = sizeBytes < 1024 ? `${sizeBytes} B` : sizeBytes < 1048576 ? `${(sizeBytes / 1024).toFixed(1)} KB` : `${(sizeBytes / 1048576).toFixed(1)} MB`;
|
|
181
|
+
const approxTokens = Math.round(outputContent.length / 4);
|
|
182
|
+
const tokensStr = approxTokens < 1000 ? `${approxTokens}` : `${(approxTokens / 1000).toFixed(1)}k`;
|
|
183
|
+
|
|
184
|
+
console.log(chalk.green(`✅ Scout complete. Saved to: .eck/scouts/${filename}`));
|
|
185
|
+
console.log(chalk.gray(` Size: ${sizeStr} | ~${tokensStr} tokens`));
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.error(chalk.red(`❌ Scout failed: ${error.message}`));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function runFetch(patterns) {
|
|
192
|
+
console.log(chalk.blue(`🚚 Fetching files matching patterns: ${patterns.join(', ')}...`));
|
|
193
|
+
try {
|
|
194
|
+
const repoPath = process.cwd();
|
|
195
|
+
const repoName = path.basename(repoPath);
|
|
196
|
+
const repoPathNorm = repoPath.replace(/\\/g, '/').replace(/\/$/, '') + '/';
|
|
197
|
+
const setupConfig = await loadSetupConfig();
|
|
198
|
+
let config = { ...setupConfig.fileFiltering, ...setupConfig.performance };
|
|
199
|
+
|
|
200
|
+
// Apply project-specific filtering
|
|
201
|
+
const projectDetection = await detectProjectType(repoPath);
|
|
202
|
+
const allTypes = getAllDetectedTypes(projectDetection);
|
|
203
|
+
if (allTypes && allTypes.length > 0) {
|
|
204
|
+
const projectSpecific = await getProjectSpecificFiltering(allTypes);
|
|
205
|
+
config = {
|
|
206
|
+
...config,
|
|
207
|
+
dirsToIgnore: [...(config.dirsToIgnore || []), ...(projectSpecific.dirsToIgnore || [])],
|
|
208
|
+
filesToIgnore: [...(config.filesToIgnore || []), ...(projectSpecific.filesToIgnore || [])],
|
|
209
|
+
extensionsToIgnore: [...(config.extensionsToIgnore || []), ...(projectSpecific.extensionsToIgnore || [])]
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let allFiles = await getProjectFiles(repoPath, config);
|
|
214
|
+
const gitignore = await loadGitignore(repoPath);
|
|
215
|
+
|
|
216
|
+
allFiles = allFiles.filter(f => {
|
|
217
|
+
const normalized = f.replace(/\\/g, '/');
|
|
218
|
+
const mlExt = path.extname(f).toLowerCase();
|
|
219
|
+
const ML_EXTENSIONS = ['.safetensors', '.onnx', '.pt', '.pth', '.h5', '.pb', '.bin', '.ckpt', '.gguf'];
|
|
220
|
+
if (isBinaryPath(f) && !ML_EXTENSIONS.includes(mlExt)) return false;
|
|
221
|
+
if (gitignore.ignores(normalized)) return false;
|
|
222
|
+
if (config.filesToIgnore && matchesPattern(normalized, config.filesToIgnore)) return false;
|
|
223
|
+
return true;
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Normalize patterns: strip absolute cwd prefix, convert backslashes,
|
|
227
|
+
// and auto-wrap bare filenames with **/ for convenience
|
|
228
|
+
const normalizedPatterns = patterns.map(p => {
|
|
229
|
+
let norm = p.replace(/\\/g, '/');
|
|
230
|
+
// Strip absolute path prefix matching cwd (case-insensitive on Windows)
|
|
231
|
+
if (norm.toLowerCase().startsWith(repoPathNorm.toLowerCase())) {
|
|
232
|
+
norm = norm.slice(repoPathNorm.length);
|
|
233
|
+
}
|
|
234
|
+
// If it looks like an absolute path from another project, extract just the filename
|
|
235
|
+
if (path.isAbsolute(norm) || /^[A-Za-z]:\//.test(norm)) {
|
|
236
|
+
const basename = path.basename(norm);
|
|
237
|
+
console.log(chalk.yellow(` ⚠️ Cross-repo absolute path detected, using: **/${basename}`));
|
|
238
|
+
norm = `**/${basename}`;
|
|
239
|
+
}
|
|
240
|
+
// If it's a plain filename with no glob chars and no path separators, wrap it
|
|
241
|
+
if (!norm.includes('/') && !norm.includes('*') && !norm.includes('?')) {
|
|
242
|
+
norm = `**/${norm}`;
|
|
243
|
+
}
|
|
244
|
+
return norm;
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const matchedFiles = micromatch(allFiles, normalizedPatterns);
|
|
248
|
+
|
|
249
|
+
if (matchedFiles.length === 0) {
|
|
250
|
+
console.log(chalk.yellow('⚠️ No files matched the requested patterns.'));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let fileContentStr = '';
|
|
255
|
+
let fetchedCount = 0;
|
|
256
|
+
const maxFileSize = parseSize(config.maxFileSize || '10MB');
|
|
257
|
+
|
|
258
|
+
for (const file of matchedFiles) {
|
|
259
|
+
try {
|
|
260
|
+
const fullPath = path.join(repoPath, file);
|
|
261
|
+
const mlExt = path.extname(file).toLowerCase();
|
|
262
|
+
const ML_EXTENSIONS = ['.safetensors', '.onnx', '.pt', '.pth', '.h5', '.pb', '.bin', '.ckpt', '.gguf'];
|
|
263
|
+
|
|
264
|
+
let content;
|
|
265
|
+
if (ML_EXTENSIONS.includes(mlExt)) {
|
|
266
|
+
content = await readMlModelMetadata(fullPath);
|
|
267
|
+
} else {
|
|
268
|
+
content = await readFileWithSizeCheck(fullPath, maxFileSize);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
fileContentStr += `--- File: /${file} ---\n\n\`\`\`\n${content}\n\`\`\`\n\n`;
|
|
272
|
+
fetchedCount++;
|
|
273
|
+
} catch (e) {
|
|
274
|
+
fileContentStr += `--- File: /${file} ---\n\n[ERROR: ${e.message}]\n\n`;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const timestamp = generateTimestamp();
|
|
279
|
+
const filename = `scout_data_${repoName}_${timestamp}.md`;
|
|
280
|
+
|
|
281
|
+
// Check how many patterns actually matched at least one file
|
|
282
|
+
const matchedPatternCount = normalizedPatterns.filter(p => micromatch(allFiles, [p]).length > 0).length;
|
|
283
|
+
const missedCount = normalizedPatterns.length - matchedPatternCount;
|
|
284
|
+
const missedWarning = missedCount > 0 ? `\n**⚠️ ${missedCount} of ${patterns.length} requested patterns returned no results.** You likely misread the directory tree. Re-check the tree carefully and retry with glob patterns like \`"**/<filename>"\` to match files regardless of nesting depth.\n` : '';
|
|
285
|
+
|
|
286
|
+
const finalContent = `# ⚠️ SCOUT FETCH RESULTS: [${repoName}]
|
|
287
|
+
|
|
288
|
+
Here are the file contents you requested from the external repository. Use this to inform your work on your primary project.
|
|
289
|
+
${missedWarning}
|
|
290
|
+
${fileContentStr}
|
|
291
|
+
`;
|
|
292
|
+
|
|
293
|
+
await fs.mkdir(path.join(repoPath, '.eck', 'scouts'), { recursive: true });
|
|
294
|
+
await ensureSnapshotsInGitignore(repoPath);
|
|
295
|
+
const outputPath = path.join(repoPath, '.eck', 'scouts', filename);
|
|
296
|
+
await fs.writeFile(outputPath, finalContent, 'utf-8');
|
|
297
|
+
|
|
298
|
+
const sizeBytes = Buffer.byteLength(finalContent, 'utf-8');
|
|
299
|
+
const sizeStr = sizeBytes < 1024 ? `${sizeBytes} B` : sizeBytes < 1048576 ? `${(sizeBytes / 1024).toFixed(1)} KB` : `${(sizeBytes / 1048576).toFixed(1)} MB`;
|
|
300
|
+
const approxTokens = Math.round(finalContent.length / 4);
|
|
301
|
+
const tokensStr = approxTokens < 1000 ? `${approxTokens}` : `${(approxTokens / 1000).toFixed(1)}k`;
|
|
302
|
+
|
|
303
|
+
console.log(chalk.green(`✅ Fetched ${fetchedCount} files. Saved to: .eck/scouts/${filename}`));
|
|
304
|
+
console.log(chalk.gray(` Size: ${sizeStr} | ~${tokensStr} tokens`));
|
|
305
|
+
} catch (error) {
|
|
306
|
+
console.error(chalk.red(`❌ Fetch failed: ${error.message}`));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
@@ -6,6 +6,7 @@ import ora from 'ora';
|
|
|
6
6
|
import os from 'os';
|
|
7
7
|
import { execa } from 'execa';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
|
+
import { ensureSnapshotsInGitignore } from '../../utils/fileUtils.js';
|
|
9
10
|
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
12
|
const __dirname = path.dirname(__filename);
|
|
@@ -218,6 +219,7 @@ async function setupForClaude(packageRoot, eckCorePath, glmZaiPath, options, pro
|
|
|
218
219
|
|
|
219
220
|
try {
|
|
220
221
|
await fs.mkdir(path.dirname(localConfigPath), { recursive: true });
|
|
222
|
+
await ensureSnapshotsInGitignore(projectRoot);
|
|
221
223
|
await fs.writeFile(localConfigPath, JSON.stringify(localConfig, null, 2));
|
|
222
224
|
spinner.succeed(`Local config updated: ${chalk.cyan(localConfigPath)}`);
|
|
223
225
|
} catch (e) {
|