@xelth/eck-snapshot 5.4.0 ā 5.4.1
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/package.json +1 -1
- package/src/cli/commands/createSnapshot.js +71 -60
- package/src/cli/commands/updateSnapshot.js +128 -100
- package/src/config.js +0 -10
- package/src/utils/gitUtils.js +9 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xelth/eck-snapshot",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.1",
|
|
4
4
|
"description": "A powerful CLI tool to create and restore single-file text snapshots of Git repositories and directories. Optimized for AI context and LLM workflows.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -10,13 +10,13 @@ import ora from 'ora';
|
|
|
10
10
|
import micromatch from 'micromatch';
|
|
11
11
|
import chalk from 'chalk';
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
parseSize, formatSize, matchesPattern, checkGitRepository,
|
|
15
|
-
scanDirectoryRecursively, loadGitignore, readFileWithSizeCheck,
|
|
16
|
-
generateDirectoryTree, loadConfig, displayProjectInfo, loadProjectEckManifest,
|
|
17
|
-
ensureSnapshotsInGitignore, initializeEckManifest, generateTimestamp,
|
|
18
|
-
getShortRepoName, SecretScanner
|
|
19
|
-
} from '../../utils/fileUtils.js';
|
|
13
|
+
import {
|
|
14
|
+
parseSize, formatSize, matchesPattern, checkGitRepository,
|
|
15
|
+
scanDirectoryRecursively, loadGitignore, readFileWithSizeCheck,
|
|
16
|
+
generateDirectoryTree, loadConfig, displayProjectInfo, loadProjectEckManifest,
|
|
17
|
+
ensureSnapshotsInGitignore, initializeEckManifest, generateTimestamp,
|
|
18
|
+
getShortRepoName, SecretScanner
|
|
19
|
+
} from '../../utils/fileUtils.js';
|
|
20
20
|
import { detectProjectType, getProjectSpecificFiltering } from '../../utils/projectDetector.js';
|
|
21
21
|
import { estimateTokensWithPolynomial, generateTrainingCommand } from '../../utils/tokenEstimator.js';
|
|
22
22
|
import { loadSetupConfig, getProfile } from '../../config.js';
|
|
@@ -312,6 +312,17 @@ async function estimateProjectTokens(projectPath, config, projectType = null) {
|
|
|
312
312
|
}
|
|
313
313
|
|
|
314
314
|
async function processProjectFiles(repoPath, options, config, projectType = null) {
|
|
315
|
+
// Merge project-specific filtering rules (e.g., Cargo.lock for Rust)
|
|
316
|
+
if (projectType) {
|
|
317
|
+
const projectSpecific = await getProjectSpecificFiltering(projectType);
|
|
318
|
+
config = {
|
|
319
|
+
...config,
|
|
320
|
+
dirsToIgnore: [...(config.dirsToIgnore || []), ...(projectSpecific.dirsToIgnore || [])],
|
|
321
|
+
filesToIgnore: [...(config.filesToIgnore || []), ...(projectSpecific.filesToIgnore || [])],
|
|
322
|
+
extensionsToIgnore: [...(config.extensionsToIgnore || []), ...(projectSpecific.extensionsToIgnore || [])]
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
315
326
|
const originalCwd = process.cwd();
|
|
316
327
|
console.log(`\nšø Processing files for: ${path.basename(repoPath)}`);
|
|
317
328
|
|
|
@@ -703,59 +714,59 @@ export async function createRepoSnapshot(repoPath, options) {
|
|
|
703
714
|
|
|
704
715
|
// Helper to write snapshot file
|
|
705
716
|
const writeSnapshot = async (suffix, isAgentMode) => {
|
|
706
|
-
// CHANGE: Force agent to FALSE for the main snapshot header.
|
|
707
|
-
// The snapshot is read by the Human/Senior Arch, not the Agent itself.
|
|
708
|
-
// The Agent reads CLAUDE.md.
|
|
709
|
-
const opts = { ...options, agent: false, jag: isJag, jas: isJas, jao: isJao };
|
|
710
|
-
const header = await generateEnhancedAIHeader({ stats, repoName, mode: 'file', eckManifest, options: opts, repoPath: processedRepoPath }, isGitRepo);
|
|
711
|
-
|
|
712
|
-
// Compact filename format: eck{ShortName}{timestamp}_{hash}_{suffix}.md
|
|
713
|
-
// getShortRepoName ensures Capitalized Start/End (e.g. SnaOt)
|
|
714
|
-
const shortHash = gitHash ? gitHash.substring(0, 7) : '';
|
|
715
|
-
const shortRepoName = getShortRepoName(repoName);
|
|
716
|
-
|
|
717
|
-
let fname = `eck${shortRepoName}${timestamp}`;
|
|
718
|
-
if (shortHash) fname += `_${shortHash}`;
|
|
719
|
-
|
|
720
|
-
// Add mode suffix
|
|
721
|
-
if (options.skeleton) {
|
|
722
|
-
fname += '_sk';
|
|
723
|
-
} else if (suffix) {
|
|
724
|
-
fname += suffix;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
fname += `.${fileExtension}`;
|
|
728
|
-
const fpath = path.join(outputPath, fname);
|
|
729
|
-
const fullContent = header + fileBody;
|
|
730
|
-
await fs.writeFile(fpath, fullContent);
|
|
731
|
-
console.log(`š Generated Snapshot: ${fname}`);
|
|
732
|
-
|
|
733
|
-
// --- FEATURE: Active Snapshot (.eck/lastsnapshot/) ---
|
|
734
|
-
// Only create .eck/lastsnapshot/ entries for the main snapshot
|
|
735
|
-
if (!isAgentMode) {
|
|
736
|
-
try {
|
|
737
|
-
const snapDir = path.join(originalCwd, '.eck', 'lastsnapshot');
|
|
738
|
-
await fs.mkdir(snapDir, { recursive: true });
|
|
739
|
-
|
|
740
|
-
// 1. Clean up OLD snapshots in this specific folder
|
|
741
|
-
// We keep AnswerToSA.md, but remove old snapshots and legacy answer.md
|
|
742
|
-
const existingFiles = await fs.readdir(snapDir);
|
|
743
|
-
for (const file of existingFiles) {
|
|
744
|
-
if ((file.startsWith('eck') && file.endsWith('.md')) || file === 'answer.md') {
|
|
745
|
-
await fs.unlink(path.join(snapDir, file));
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
// 2. Save the NEW specific named file
|
|
750
|
-
await fs.writeFile(path.join(snapDir, fname), fullContent);
|
|
751
|
-
|
|
752
|
-
console.log(chalk.cyan(`š Active snapshot updated in .eck/lastsnapshot/: ${fname}`));
|
|
753
|
-
} catch (e) {
|
|
754
|
-
// Non-critical failure
|
|
755
|
-
console.warn(chalk.yellow(`ā ļø Could not update .eck/lastsnapshot/: ${e.message}`));
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
// --------------------------------------------
|
|
717
|
+
// CHANGE: Force agent to FALSE for the main snapshot header.
|
|
718
|
+
// The snapshot is read by the Human/Senior Arch, not the Agent itself.
|
|
719
|
+
// The Agent reads CLAUDE.md.
|
|
720
|
+
const opts = { ...options, agent: false, jag: isJag, jas: isJas, jao: isJao };
|
|
721
|
+
const header = await generateEnhancedAIHeader({ stats, repoName, mode: 'file', eckManifest, options: opts, repoPath: processedRepoPath }, isGitRepo);
|
|
722
|
+
|
|
723
|
+
// Compact filename format: eck{ShortName}{timestamp}_{hash}_{suffix}.md
|
|
724
|
+
// getShortRepoName ensures Capitalized Start/End (e.g. SnaOt)
|
|
725
|
+
const shortHash = gitHash ? gitHash.substring(0, 7) : '';
|
|
726
|
+
const shortRepoName = getShortRepoName(repoName);
|
|
727
|
+
|
|
728
|
+
let fname = `eck${shortRepoName}${timestamp}`;
|
|
729
|
+
if (shortHash) fname += `_${shortHash}`;
|
|
730
|
+
|
|
731
|
+
// Add mode suffix
|
|
732
|
+
if (options.skeleton) {
|
|
733
|
+
fname += '_sk';
|
|
734
|
+
} else if (suffix) {
|
|
735
|
+
fname += suffix;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
fname += `.${fileExtension}`;
|
|
739
|
+
const fpath = path.join(outputPath, fname);
|
|
740
|
+
const fullContent = header + fileBody;
|
|
741
|
+
await fs.writeFile(fpath, fullContent);
|
|
742
|
+
console.log(`š Generated Snapshot: ${fname}`);
|
|
743
|
+
|
|
744
|
+
// --- FEATURE: Active Snapshot (.eck/lastsnapshot/) ---
|
|
745
|
+
// Only create .eck/lastsnapshot/ entries for the main snapshot
|
|
746
|
+
if (!isAgentMode) {
|
|
747
|
+
try {
|
|
748
|
+
const snapDir = path.join(originalCwd, '.eck', 'lastsnapshot');
|
|
749
|
+
await fs.mkdir(snapDir, { recursive: true });
|
|
750
|
+
|
|
751
|
+
// 1. Clean up OLD snapshots in this specific folder
|
|
752
|
+
// We keep AnswerToSA.md, but remove old snapshots and legacy answer.md
|
|
753
|
+
const existingFiles = await fs.readdir(snapDir);
|
|
754
|
+
for (const file of existingFiles) {
|
|
755
|
+
if ((file.startsWith('eck') && file.endsWith('.md')) || file === 'answer.md') {
|
|
756
|
+
await fs.unlink(path.join(snapDir, file));
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// 2. Save the NEW specific named file
|
|
761
|
+
await fs.writeFile(path.join(snapDir, fname), fullContent);
|
|
762
|
+
|
|
763
|
+
console.log(chalk.cyan(`š Active snapshot updated in .eck/lastsnapshot/: ${fname}`));
|
|
764
|
+
} catch (e) {
|
|
765
|
+
// Non-critical failure
|
|
766
|
+
console.warn(chalk.yellow(`ā ļø Could not update .eck/lastsnapshot/: ${e.message}`));
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
// --------------------------------------------
|
|
759
770
|
|
|
760
771
|
return fpath;
|
|
761
772
|
};
|
|
@@ -2,9 +2,10 @@ import fs from 'fs/promises';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
-
import { getGitAnchor, getChangedFiles
|
|
5
|
+
import { getGitAnchor, getChangedFiles } from '../../utils/gitUtils.js';
|
|
6
6
|
import { loadSetupConfig } from '../../config.js';
|
|
7
7
|
import { readFileWithSizeCheck, parseSize, formatSize, matchesPattern, loadGitignore, generateTimestamp, getShortRepoName } from '../../utils/fileUtils.js';
|
|
8
|
+
import { detectProjectType, getProjectSpecificFiltering } from '../../utils/projectDetector.js';
|
|
8
9
|
import { fileURLToPath } from 'url';
|
|
9
10
|
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -16,18 +17,22 @@ async function generateSnapshotContent(repoPath, changedFiles, anchor, config, g
|
|
|
16
17
|
let includedCount = 0;
|
|
17
18
|
const fileList = [];
|
|
18
19
|
|
|
19
|
-
// Check for Agent Report in .eck/lastsnapshot/AnswerToSA.md (STRICT LOCATION)
|
|
20
|
-
const reportPath = path.join(repoPath, '.eck', 'lastsnapshot', 'AnswerToSA.md');
|
|
21
|
-
let agentReport = null;
|
|
22
|
-
try {
|
|
23
|
-
agentReport = await fs.readFile(reportPath, 'utf-8');
|
|
24
|
-
if (!changedFiles.includes('.eck/lastsnapshot/AnswerToSA.md')) {
|
|
25
|
-
changedFiles.push('.eck/lastsnapshot/AnswerToSA.md');
|
|
26
|
-
}
|
|
27
|
-
} catch (e) { /* No report */ }
|
|
20
|
+
// Check for Agent Report in .eck/lastsnapshot/AnswerToSA.md (STRICT LOCATION)
|
|
21
|
+
const reportPath = path.join(repoPath, '.eck', 'lastsnapshot', 'AnswerToSA.md');
|
|
22
|
+
let agentReport = null;
|
|
23
|
+
try {
|
|
24
|
+
agentReport = await fs.readFile(reportPath, 'utf-8');
|
|
25
|
+
if (!changedFiles.includes('.eck/lastsnapshot/AnswerToSA.md')) {
|
|
26
|
+
changedFiles.push('.eck/lastsnapshot/AnswerToSA.md');
|
|
27
|
+
}
|
|
28
|
+
} catch (e) { /* No report */ }
|
|
28
29
|
|
|
29
30
|
for (const filePath of changedFiles) {
|
|
30
|
-
if (config.dirsToIgnore
|
|
31
|
+
if (config.dirsToIgnore?.some(d => filePath.startsWith(d))) continue;
|
|
32
|
+
const fileName = path.basename(filePath);
|
|
33
|
+
const fileExt = path.extname(filePath);
|
|
34
|
+
if (config.filesToIgnore?.includes(fileName)) continue;
|
|
35
|
+
if (fileExt && config.extensionsToIgnore?.includes(fileExt)) continue;
|
|
31
36
|
if (gitignore.ignores(filePath) && filePath !== '.eck/lastsnapshot/AnswerToSA.md') continue;
|
|
32
37
|
|
|
33
38
|
try {
|
|
@@ -55,15 +60,12 @@ async function generateSnapshotContent(repoPath, changedFiles, anchor, config, g
|
|
|
55
60
|
|
|
56
61
|
header = reportSection + header;
|
|
57
62
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
anchor,
|
|
65
|
-
agentReport
|
|
66
|
-
};
|
|
63
|
+
return {
|
|
64
|
+
fullContent: header + contentOutput,
|
|
65
|
+
includedCount,
|
|
66
|
+
anchor,
|
|
67
|
+
agentReport
|
|
68
|
+
};
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
export async function updateSnapshot(repoPath, options) {
|
|
@@ -80,11 +82,24 @@ export async function updateSnapshot(repoPath, options) {
|
|
|
80
82
|
return;
|
|
81
83
|
}
|
|
82
84
|
|
|
83
|
-
const setupConfig = await loadSetupConfig();
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
85
|
+
const setupConfig = await loadSetupConfig();
|
|
86
|
+
let config = { ...setupConfig.fileFiltering, ...setupConfig.performance, ...options };
|
|
87
|
+
|
|
88
|
+
// Detect project type and merge project-specific filters
|
|
89
|
+
const projectDetection = await detectProjectType(repoPath);
|
|
90
|
+
if (projectDetection.type) {
|
|
91
|
+
const projectSpecific = await getProjectSpecificFiltering(projectDetection.type);
|
|
92
|
+
config = {
|
|
93
|
+
...config,
|
|
94
|
+
dirsToIgnore: [...(config.dirsToIgnore || []), ...(projectSpecific.dirsToIgnore || [])],
|
|
95
|
+
filesToIgnore: [...(config.filesToIgnore || []), ...(projectSpecific.filesToIgnore || [])],
|
|
96
|
+
extensionsToIgnore: [...(config.extensionsToIgnore || []), ...(projectSpecific.extensionsToIgnore || [])]
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const gitignore = await loadGitignore(repoPath);
|
|
101
|
+
|
|
102
|
+
const { fullContent, includedCount, agentReport } = await generateSnapshotContent(repoPath, changedFiles, anchor, config, gitignore);
|
|
88
103
|
|
|
89
104
|
// Determine sequence number
|
|
90
105
|
let seqNum = 1;
|
|
@@ -101,43 +116,43 @@ export async function updateSnapshot(repoPath, options) {
|
|
|
101
116
|
await fs.writeFile(counterPath, `${anchor.substring(0, 7)}:${seqNum}`);
|
|
102
117
|
} catch (e) {}
|
|
103
118
|
|
|
104
|
-
const timestamp = generateTimestamp();
|
|
105
|
-
const shortRepoName = getShortRepoName(path.basename(repoPath));
|
|
106
|
-
const outputFilename = `eck${shortRepoName}${timestamp}_${anchor.substring(0, 7)}_up${seqNum}.md`;
|
|
107
|
-
const outputPath = path.join(repoPath, '.eck', 'snapshots', outputFilename);
|
|
108
|
-
|
|
109
|
-
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
110
|
-
await fs.writeFile(outputPath, fullContent);
|
|
111
|
-
|
|
112
|
-
spinner.succeed(`Update snapshot created: .eck/snapshots/${outputFilename}`);
|
|
113
|
-
|
|
114
|
-
// --- FEATURE: Active Snapshot (.eck/lastsnapshot/) ---
|
|
115
|
-
try {
|
|
116
|
-
const snapDir = path.join(repoPath, '.eck', 'lastsnapshot');
|
|
117
|
-
await fs.mkdir(snapDir, { recursive: true });
|
|
118
|
-
|
|
119
|
-
// 1. Clean up OLD snapshots
|
|
120
|
-
const existingFiles = await fs.readdir(snapDir);
|
|
121
|
-
for (const file of existingFiles) {
|
|
122
|
-
if ((file.startsWith('eck') && file.endsWith('.md')) || file === 'answer.md') {
|
|
123
|
-
await fs.unlink(path.join(snapDir, file));
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// 2. Save new file
|
|
128
|
-
await fs.writeFile(path.join(snapDir, outputFilename), fullContent);
|
|
129
|
-
console.log(chalk.cyan(`š Active snapshot updated in .eck/lastsnapshot/: ${outputFilename}`));
|
|
130
|
-
} catch (e) {
|
|
131
|
-
// Non-critical failure
|
|
132
|
-
}
|
|
133
|
-
// --------------------------------------------
|
|
134
|
-
|
|
135
|
-
// Check if agent report was included
|
|
136
|
-
if (agentReport) {
|
|
137
|
-
console.log(chalk.green('šØ Included Agent Report (.eck/lastsnapshot/AnswerToSA.md)'));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
console.log(`š¦ Included ${includedCount} changed files.`);
|
|
119
|
+
const timestamp = generateTimestamp();
|
|
120
|
+
const shortRepoName = getShortRepoName(path.basename(repoPath));
|
|
121
|
+
const outputFilename = `eck${shortRepoName}${timestamp}_${anchor.substring(0, 7)}_up${seqNum}.md`;
|
|
122
|
+
const outputPath = path.join(repoPath, '.eck', 'snapshots', outputFilename);
|
|
123
|
+
|
|
124
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
125
|
+
await fs.writeFile(outputPath, fullContent);
|
|
126
|
+
|
|
127
|
+
spinner.succeed(`Update snapshot created: .eck/snapshots/${outputFilename}`);
|
|
128
|
+
|
|
129
|
+
// --- FEATURE: Active Snapshot (.eck/lastsnapshot/) ---
|
|
130
|
+
try {
|
|
131
|
+
const snapDir = path.join(repoPath, '.eck', 'lastsnapshot');
|
|
132
|
+
await fs.mkdir(snapDir, { recursive: true });
|
|
133
|
+
|
|
134
|
+
// 1. Clean up OLD snapshots
|
|
135
|
+
const existingFiles = await fs.readdir(snapDir);
|
|
136
|
+
for (const file of existingFiles) {
|
|
137
|
+
if ((file.startsWith('eck') && file.endsWith('.md')) || file === 'answer.md') {
|
|
138
|
+
await fs.unlink(path.join(snapDir, file));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 2. Save new file
|
|
143
|
+
await fs.writeFile(path.join(snapDir, outputFilename), fullContent);
|
|
144
|
+
console.log(chalk.cyan(`š Active snapshot updated in .eck/lastsnapshot/: ${outputFilename}`));
|
|
145
|
+
} catch (e) {
|
|
146
|
+
// Non-critical failure
|
|
147
|
+
}
|
|
148
|
+
// --------------------------------------------
|
|
149
|
+
|
|
150
|
+
// Check if agent report was included
|
|
151
|
+
if (agentReport) {
|
|
152
|
+
console.log(chalk.green('šØ Included Agent Report (.eck/lastsnapshot/AnswerToSA.md)'));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log(`š¦ Included ${includedCount} changed files.`);
|
|
141
156
|
|
|
142
157
|
} catch (error) {
|
|
143
158
|
spinner.fail(`Update failed: ${error.message}`);
|
|
@@ -159,11 +174,24 @@ export async function updateSnapshotJson(repoPath) {
|
|
|
159
174
|
return;
|
|
160
175
|
}
|
|
161
176
|
|
|
162
|
-
const setupConfig = await loadSetupConfig();
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
177
|
+
const setupConfig = await loadSetupConfig();
|
|
178
|
+
let config = { ...setupConfig.fileFiltering, ...setupConfig.performance };
|
|
179
|
+
|
|
180
|
+
// Detect project type and merge project-specific filters
|
|
181
|
+
const projectDetection = await detectProjectType(repoPath);
|
|
182
|
+
if (projectDetection.type) {
|
|
183
|
+
const projectSpecific = await getProjectSpecificFiltering(projectDetection.type);
|
|
184
|
+
config = {
|
|
185
|
+
...config,
|
|
186
|
+
dirsToIgnore: [...(config.dirsToIgnore || []), ...(projectSpecific.dirsToIgnore || [])],
|
|
187
|
+
filesToIgnore: [...(config.filesToIgnore || []), ...(projectSpecific.filesToIgnore || [])],
|
|
188
|
+
extensionsToIgnore: [...(config.extensionsToIgnore || []), ...(projectSpecific.extensionsToIgnore || [])]
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const gitignore = await loadGitignore(repoPath);
|
|
193
|
+
|
|
194
|
+
const { fullContent, includedCount, agentReport } = await generateSnapshotContent(repoPath, changedFiles, anchor, config, gitignore);
|
|
167
195
|
|
|
168
196
|
let seqNum = 1;
|
|
169
197
|
const counterPath = path.join(repoPath, '.eck', 'update_seq');
|
|
@@ -179,39 +207,39 @@ export async function updateSnapshotJson(repoPath) {
|
|
|
179
207
|
await fs.writeFile(counterPath, `${anchor.substring(0, 7)}:${seqNum}`);
|
|
180
208
|
} catch (e) {}
|
|
181
209
|
|
|
182
|
-
const timestamp = generateTimestamp();
|
|
183
|
-
const shortRepoName = getShortRepoName(path.basename(repoPath));
|
|
184
|
-
const outputFilename = `eck${shortRepoName}${timestamp}_${anchor.substring(0, 7)}_up${seqNum}.md`;
|
|
185
|
-
const outputPath = path.join(repoPath, '.eck', 'snapshots', outputFilename);
|
|
186
|
-
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
187
|
-
await fs.writeFile(outputPath, fullContent);
|
|
188
|
-
|
|
189
|
-
// --- FEATURE: Active Snapshot (.eck/lastsnapshot/) ---
|
|
190
|
-
try {
|
|
191
|
-
const snapDir = path.join(repoPath, '.eck', 'lastsnapshot');
|
|
192
|
-
await fs.mkdir(snapDir, { recursive: true });
|
|
193
|
-
|
|
194
|
-
// 1. Clean up OLD snapshots
|
|
195
|
-
const existingFiles = await fs.readdir(snapDir);
|
|
196
|
-
for (const file of existingFiles) {
|
|
197
|
-
if ((file.startsWith('eck') && file.endsWith('.md')) || file === 'answer.md') {
|
|
198
|
-
await fs.unlink(path.join(snapDir, file));
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// 2. Save new file
|
|
203
|
-
await fs.writeFile(path.join(snapDir, outputFilename), fullContent);
|
|
204
|
-
} catch (e) {
|
|
205
|
-
// Non-critical failure
|
|
206
|
-
}
|
|
207
|
-
// --------------------------------------------
|
|
208
|
-
|
|
209
|
-
console.log(JSON.stringify({
|
|
210
|
-
status: "success",
|
|
211
|
-
snapshot_file: `.eck/snapshots/${outputFilename}`,
|
|
212
|
-
files_count: includedCount,
|
|
213
|
-
timestamp: timestamp
|
|
214
|
-
}));
|
|
210
|
+
const timestamp = generateTimestamp();
|
|
211
|
+
const shortRepoName = getShortRepoName(path.basename(repoPath));
|
|
212
|
+
const outputFilename = `eck${shortRepoName}${timestamp}_${anchor.substring(0, 7)}_up${seqNum}.md`;
|
|
213
|
+
const outputPath = path.join(repoPath, '.eck', 'snapshots', outputFilename);
|
|
214
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
215
|
+
await fs.writeFile(outputPath, fullContent);
|
|
216
|
+
|
|
217
|
+
// --- FEATURE: Active Snapshot (.eck/lastsnapshot/) ---
|
|
218
|
+
try {
|
|
219
|
+
const snapDir = path.join(repoPath, '.eck', 'lastsnapshot');
|
|
220
|
+
await fs.mkdir(snapDir, { recursive: true });
|
|
221
|
+
|
|
222
|
+
// 1. Clean up OLD snapshots
|
|
223
|
+
const existingFiles = await fs.readdir(snapDir);
|
|
224
|
+
for (const file of existingFiles) {
|
|
225
|
+
if ((file.startsWith('eck') && file.endsWith('.md')) || file === 'answer.md') {
|
|
226
|
+
await fs.unlink(path.join(snapDir, file));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 2. Save new file
|
|
231
|
+
await fs.writeFile(path.join(snapDir, outputFilename), fullContent);
|
|
232
|
+
} catch (e) {
|
|
233
|
+
// Non-critical failure
|
|
234
|
+
}
|
|
235
|
+
// --------------------------------------------
|
|
236
|
+
|
|
237
|
+
console.log(JSON.stringify({
|
|
238
|
+
status: "success",
|
|
239
|
+
snapshot_file: `.eck/snapshots/${outputFilename}`,
|
|
240
|
+
files_count: includedCount,
|
|
241
|
+
timestamp: timestamp
|
|
242
|
+
}));
|
|
215
243
|
|
|
216
244
|
} catch (error) {
|
|
217
245
|
console.log(JSON.stringify({ status: "error", message: error.message }));
|
package/src/config.js
CHANGED
|
@@ -50,16 +50,6 @@ function validateConfigSchema(config) {
|
|
|
50
50
|
warnings.push('Missing "aiInstructions" section');
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
// Legacy support
|
|
54
|
-
if (!config.filesToIgnore || !Array.isArray(config.filesToIgnore)) {
|
|
55
|
-
warnings.push('filesToIgnore missing or not an array - using defaults');
|
|
56
|
-
config.filesToIgnore = DEFAULT_CONFIG.filesToIgnore;
|
|
57
|
-
}
|
|
58
|
-
if (!config.dirsToIgnore || !Array.isArray(config.dirsToIgnore)) {
|
|
59
|
-
warnings.push('dirsToIgnore missing or not an array - using defaults');
|
|
60
|
-
config.dirsToIgnore = DEFAULT_CONFIG.dirsToIgnore;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
53
|
if (warnings.length > 0) {
|
|
64
54
|
console.warn('\nā ļø Config Validation Warnings:');
|
|
65
55
|
warnings.forEach(w => console.warn(` - ${w}`));
|
package/src/utils/gitUtils.js
CHANGED
|
@@ -34,9 +34,16 @@ export async function getChangedFiles(repoPath, anchorHash) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
export async function getGitDiffOutput(repoPath, anchorHash) {
|
|
37
|
+
export async function getGitDiffOutput(repoPath, anchorHash, excludeFiles = []) {
|
|
38
38
|
try {
|
|
39
|
-
const
|
|
39
|
+
const args = ['diff', anchorHash, 'HEAD'];
|
|
40
|
+
if (excludeFiles.length > 0) {
|
|
41
|
+
args.push('--');
|
|
42
|
+
for (const file of excludeFiles) {
|
|
43
|
+
args.push(`:(exclude)${file}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const { stdout } = await execa('git', args, { cwd: repoPath });
|
|
40
47
|
return stdout;
|
|
41
48
|
} catch (e) {
|
|
42
49
|
return '';
|