@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.
@@ -1,16 +1,16 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
- import ora from 'ora';
4
- import chalk from 'chalk';
5
- import isBinaryPath from 'is-binary-path';
6
- import { getGitAnchor, getChangedFiles } from '../../utils/gitUtils.js';
7
- import { loadSetupConfig } from '../../config.js';
8
- import { readFileWithSizeCheck, parseSize, formatSize, matchesPattern, loadGitignore, generateTimestamp, getShortRepoName } from '../../utils/fileUtils.js';
9
- import { detectProjectType, getProjectSpecificFiltering } from '../../utils/projectDetector.js';
10
- import { execa } from 'execa';
11
- import { fileURLToPath } from 'url';
12
- import { pushTelemetry } from '../../utils/telemetry.js';
13
- import { syncTokenWeights } from '../../utils/tokenEstimator.js';
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import ora from 'ora';
4
+ import chalk from 'chalk';
5
+ import isBinaryPath from 'is-binary-path';
6
+ import { getGitAnchor, getChangedFiles } from '../../utils/gitUtils.js';
7
+ import { loadSetupConfig } from '../../config.js';
8
+ import { readFileWithSizeCheck, parseSize, formatSize, matchesPattern, loadGitignore, generateTimestamp, getShortRepoName, ensureSnapshotsInGitignore, readMlModelMetadata } from '../../utils/fileUtils.js';
9
+ import { detectProjectType, getProjectSpecificFiltering } from '../../utils/projectDetector.js';
10
+ import { execa } from 'execa';
11
+ import { fileURLToPath } from 'url';
12
+ import { pushTelemetry } from '../../utils/telemetry.js';
13
+ import { syncTokenWeights } from '../../utils/tokenEstimator.js';
14
14
 
15
15
  // Mirror the same hidden-path guard used in createSnapshot.js
16
16
  function isHiddenPath(filePath) {
@@ -52,95 +52,105 @@ async function generateSnapshotContent(repoPath, changedFiles, anchor, config, g
52
52
  let includedCount = 0;
53
53
  const fileList = [];
54
54
 
55
- // Include Agent Report if it exists and hasn't been embedded yet
56
- let agentReport = null;
57
- const reportPath = path.join(repoPath, '.eck', 'lastsnapshot', 'AnswerToSA.md');
58
- const lockPath = path.join(repoPath, '.eck', 'lastsnapshot', 'AnswerToSA.lock');
59
- try {
60
- // Use atomic directory creation as a lock to prevent race conditions
61
- await fs.mkdir(lockPath);
62
- const reportContent = await fs.readFile(reportPath, 'utf-8');
63
-
64
- if (!reportContent.includes('[SYSTEM: EMBEDDED]')) {
65
- agentReport = reportContent;
66
-
67
- // Immediately mark as embedded to release the race window
68
- await fs.appendFile(reportPath, '\n\n[SYSTEM: EMBEDDED]\n', 'utf-8');
69
-
70
- // Auto-Journaling: prepend agent report to JOURNAL.md
71
- const journalPath = path.join(repoPath, '.eck', 'JOURNAL.md');
72
- try {
73
- const dateStr = new Date().toISOString().split('T')[0];
74
- const journalEntry = `## ${dateStr} — Agent Report\n\n${reportContent.trim()}\n`;
75
-
76
- let existingJournal = '';
77
- try {
78
- existingJournal = await fs.readFile(journalPath, 'utf-8');
79
- } catch (e) { /* might not exist */ }
80
-
81
- const insertPos = existingJournal.indexOf('\n## ');
82
- if (insertPos !== -1) {
83
- const newJournal = existingJournal.slice(0, insertPos) + '\n\n' + journalEntry + existingJournal.slice(insertPos);
84
- await fs.writeFile(journalPath, newJournal, 'utf-8');
85
- } else {
86
- await fs.writeFile(journalPath, (existingJournal ? existingJournal + '\n\n' : '') + journalEntry + '\n', 'utf-8');
87
- }
88
- } catch (je) {
89
- console.warn('Could not auto-update JOURNAL.md', je.message);
90
- }
91
- }
92
- await fs.rmdir(lockPath);
93
- } catch (e) {
94
- // File not found or locked by another process
95
- try { await fs.rmdir(lockPath); } catch (_) {}
96
- }
55
+ // Include Agent Report if it exists and hasn't been embedded yet
56
+ let agentReport = null;
57
+ const reportPath = path.join(repoPath, '.eck', 'lastsnapshot', 'AnswerToSA.md');
58
+ const lockPath = path.join(repoPath, '.eck', 'lastsnapshot', 'AnswerToSA.lock');
59
+ try {
60
+ // Use atomic directory creation as a lock to prevent race conditions
61
+ await fs.mkdir(lockPath);
62
+ const reportContent = await fs.readFile(reportPath, 'utf-8');
63
+
64
+ if (!reportContent.includes('[SYSTEM: EMBEDDED]')) {
65
+ agentReport = reportContent;
66
+
67
+ // Immediately mark as embedded to release the race window
68
+ await fs.appendFile(reportPath, '\n\n[SYSTEM: EMBEDDED]\n', 'utf-8');
69
+
70
+ // Auto-Journaling: prepend agent report to JOURNAL.md
71
+ const journalPath = path.join(repoPath, '.eck', 'JOURNAL.md');
72
+ try {
73
+ const dateStr = new Date().toISOString().split('T')[0];
74
+ const journalEntry = `## ${dateStr} — Agent Report\n\n${reportContent.trim()}\n`;
75
+
76
+ let existingJournal = '';
77
+ try {
78
+ existingJournal = await fs.readFile(journalPath, 'utf-8');
79
+ } catch (e) { /* might not exist */ }
80
+
81
+ const insertPos = existingJournal.indexOf('\n## ');
82
+ if (insertPos !== -1) {
83
+ const newJournal = existingJournal.slice(0, insertPos) + '\n\n' + journalEntry + existingJournal.slice(insertPos);
84
+ await fs.writeFile(journalPath, newJournal, 'utf-8');
85
+ } else {
86
+ await fs.writeFile(journalPath, (existingJournal ? existingJournal + '\n\n' : '') + journalEntry + '\n', 'utf-8');
87
+ }
88
+ } catch (je) {
89
+ console.warn('Could not auto-update JOURNAL.md', je.message);
90
+ }
91
+ }
92
+ await fs.rmdir(lockPath);
93
+ } catch (e) {
94
+ // File not found or locked by another process
95
+ try { await fs.rmdir(lockPath); } catch (_) {}
96
+ }
97
97
 
98
98
  const cleanDirsToIgnore = (config.dirsToIgnore || []).map(d => d.replace(/\/$/, ''));
99
99
 
100
- for (const filePath of changedFiles) {
101
- const normalizedPath = filePath.replace(/\\/g, '/');
102
-
103
- // Skip hidden paths (.idea/, .vscode/, etc.) — mirrors createSnapshot.js
104
- if (isHiddenPath(normalizedPath)) continue;
105
-
106
- // Skip binary files — mirrors createSnapshot.js
107
- if (isBinaryPath(filePath)) continue;
108
-
109
- const pathParts = normalizedPath.split('/');
110
- let isIgnoredDir = false;
111
- for (let i = 0; i < pathParts.length - 1; i++) {
112
- if (cleanDirsToIgnore.includes(pathParts[i])) {
113
- isIgnoredDir = true;
114
- break;
115
- }
116
- }
117
- if (isIgnoredDir) continue;
118
-
119
- const fileExt = path.extname(filePath);
120
- // Use matchesPattern (glob support) instead of exact includes() — mirrors createSnapshot.js
121
- if (config.filesToIgnore && matchesPattern(normalizedPath, config.filesToIgnore)) continue;
122
- if (fileExt && config.extensionsToIgnore?.includes(fileExt)) continue;
123
- if (gitignore.ignores(normalizedPath)) continue;
124
-
125
- try {
126
- const fullPath = path.join(repoPath, filePath);
127
-
128
- // Explicitly check if file was deleted
129
- try {
130
- await fs.access(fullPath);
131
- } catch (accessErr) {
132
- contentOutput += `--- File: /${normalizedPath} ---\n\n[FILE DELETED]\n\n`;
133
- fileList.push(`- ${normalizedPath} (Deleted)`);
134
- includedCount++;
135
- continue;
136
- }
137
-
138
- const content = await readFileWithSizeCheck(fullPath, parseSize(config.maxFileSize));
139
- contentOutput += `--- File: /${normalizedPath} ---\n\n${content}\n\n`;
140
- fileList.push(`- ${normalizedPath} (Modified/Added)`);
141
- includedCount++;
142
- } catch (e) { /* Skip */ }
143
- }
100
+ for (const filePath of changedFiles) {
101
+ const normalizedPath = filePath.replace(/\\/g, '/');
102
+
103
+ // Skip hidden paths (.idea/, .vscode/, etc.) — mirrors createSnapshot.js
104
+ if (isHiddenPath(normalizedPath)) continue;
105
+
106
+ const mlExt = path.extname(filePath).toLowerCase();
107
+ const ML_EXTENSIONS = ['.safetensors', '.onnx', '.pt', '.pth', '.h5', '.pb', '.bin', '.ckpt', '.gguf'];
108
+ const isMlModel = ML_EXTENSIONS.includes(mlExt);
109
+
110
+ // Skip binary files — mirrors createSnapshot.js
111
+ if (isBinaryPath(filePath) && !isMlModel) continue;
112
+
113
+ const pathParts = normalizedPath.split('/');
114
+ let isIgnoredDir = false;
115
+ for (let i = 0; i < pathParts.length - 1; i++) {
116
+ if (cleanDirsToIgnore.includes(pathParts[i])) {
117
+ isIgnoredDir = true;
118
+ break;
119
+ }
120
+ }
121
+ if (isIgnoredDir) continue;
122
+
123
+ const fileExt = path.extname(filePath);
124
+ // Use matchesPattern (glob support) instead of exact includes() — mirrors createSnapshot.js
125
+ if (config.filesToIgnore && matchesPattern(normalizedPath, config.filesToIgnore)) continue;
126
+ if (fileExt && config.extensionsToIgnore?.includes(fileExt)) continue;
127
+ if (gitignore.ignores(normalizedPath)) continue;
128
+
129
+ try {
130
+ const fullPath = path.join(repoPath, filePath);
131
+
132
+ // Explicitly check if file was deleted
133
+ try {
134
+ await fs.access(fullPath);
135
+ } catch (accessErr) {
136
+ contentOutput += `--- File: /${normalizedPath} ---\n\n[FILE DELETED]\n\n`;
137
+ fileList.push(`- ${normalizedPath} (Deleted)`);
138
+ includedCount++;
139
+ continue;
140
+ }
141
+
142
+ let content;
143
+ if (isMlModel) {
144
+ content = await readMlModelMetadata(fullPath);
145
+ } else {
146
+ content = await readFileWithSizeCheck(fullPath, parseSize(config.maxFileSize));
147
+ }
148
+
149
+ contentOutput += `--- File: /${normalizedPath} ---\n\n${content}\n\n`;
150
+ fileList.push(`- ${normalizedPath} (Modified/Added)`);
151
+ includedCount++;
152
+ } catch (e) { /* Skip */ }
153
+ }
144
154
 
145
155
  // Load Template
146
156
  const templatePath = path.join(__dirname, '../../templates/update-prompt.template.md');
@@ -196,11 +206,11 @@ export async function updateSnapshot(repoPath, options) {
196
206
 
197
207
  spinner.start('Generating update snapshot...');
198
208
 
199
- const changedFiles = await getChangedFiles(repoPath, anchor, options.fail);
200
- if (changedFiles.length === 0) {
201
- spinner.succeed('No changes detected since last full snapshot.');
202
- return;
203
- }
209
+ const changedFiles = await getChangedFiles(repoPath, anchor, options.fail);
210
+ if (changedFiles.length === 0) {
211
+ spinner.succeed('No changes detected since last full snapshot.');
212
+ return;
213
+ }
204
214
 
205
215
  const setupConfig = await loadSetupConfig();
206
216
  let config = { ...setupConfig.fileFiltering, ...setupConfig.performance, ...options };
@@ -247,6 +257,7 @@ export async function updateSnapshot(repoPath, options) {
247
257
  const outputPath = path.join(repoPath, '.eck', 'snapshots', outputFilename);
248
258
 
249
259
  await fs.mkdir(path.dirname(outputPath), { recursive: true });
260
+ await ensureSnapshotsInGitignore(repoPath);
250
261
  await fs.writeFile(outputPath, fullContent);
251
262
 
252
263
  spinner.succeed(`Update snapshot created: .eck/snapshots/${outputFilename}`);
@@ -352,6 +363,7 @@ export async function updateSnapshotJson(repoPath, options = {}) {
352
363
  const outputFilename = `eck${shortRepoName}${timestamp}_${anchor.substring(0, 7)}_up${seqStr}_${sizeKB}kb.md`;
353
364
  const outputPath = path.join(repoPath, '.eck', 'snapshots', outputFilename);
354
365
  await fs.mkdir(path.dirname(outputPath), { recursive: true });
366
+ await ensureSnapshotsInGitignore(repoPath);
355
367
  await fs.writeFile(outputPath, fullContent);
356
368
 
357
369
  // --- FEATURE: Active Snapshot (.eck/lastsnapshot/) ---
@@ -374,16 +386,16 @@ export async function updateSnapshotJson(repoPath, options = {}) {
374
386
  }
375
387
  // --------------------------------------------
376
388
 
377
- console.log(JSON.stringify({
378
- status: "success",
379
- snapshot_file: `.eck/snapshots/${outputFilename}`,
380
- files_count: includedCount,
381
- timestamp: timestamp
382
- }));
383
-
384
- // Auto-push telemetry and sync weights (fire and forget so it doesn't break JSON output)
385
- pushTelemetry(repoPath, true).catch(() => {});
386
- syncTokenWeights(true).catch(() => {});
389
+ console.log(JSON.stringify({
390
+ status: "success",
391
+ snapshot_file: `.eck/snapshots/${outputFilename}`,
392
+ files_count: includedCount,
393
+ timestamp: timestamp
394
+ }));
395
+
396
+ // Auto-push telemetry and sync weights (fire and forget so it doesn't break JSON output)
397
+ pushTelemetry(repoPath, true).catch(() => {});
398
+ syncTokenWeights(true).catch(() => {});
387
399
 
388
400
  } catch (error) {
389
401
  console.log(JSON.stringify({ status: "error", message: error.message }));