@xelth/eck-snapshot 5.4.0 ā 5.4.2
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 +84 -62
- 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/README.md
CHANGED
|
@@ -1,106 +1,128 @@
|
|
|
1
|
+
# eck-snapshot
|
|
1
2
|
|
|
2
|
-
|
|
3
|
+
A CLI tool that packs your entire Git repository into a single text file optimized for LLMs (Claude, Gemini, ChatGPT). Give any AI full project context in one copy-paste.
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
```bash
|
|
6
|
+
npm install -g @xelth/eck-snapshot
|
|
7
|
+
```
|
|
5
8
|
|
|
6
|
-
|
|
9
|
+
## Core Workflow
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
### 1. Full Snapshot
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
# Install globally via npm
|
|
12
|
-
npm install -g @xelth/eck-snapshot
|
|
13
|
+
Run `eck-snapshot` in your project root. It scans every tracked file, filters out noise (lock files, build artifacts, secrets), and produces a single `.md` file ready for an AI chat.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
```bash
|
|
15
16
|
eck-snapshot
|
|
17
|
+
# -> .eck/snapshots/eckMyProject_26-02-15_12-00_abc1234.md
|
|
16
18
|
```
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
That's it. Upload the file to your AI and start working.
|
|
21
|
+
|
|
22
|
+
### 2. Incremental Update
|
|
23
|
+
|
|
24
|
+
After you make changes, don't re-send the entire project. Send only what changed since the last full snapshot:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
eck-snapshot update
|
|
28
|
+
# -> .eck/snapshots/eckMyProject_26-02-15_14-30_abc1234_up1.md
|
|
29
|
+
```
|
|
19
30
|
|
|
20
|
-
|
|
21
|
-
- **Delta Updates:** Tracks changes via Git anchors and generates incremental snapshots (`eck-snapshot update`).
|
|
22
|
-
- **Royal Court Architecture:** Multi-agent protocol with dedicated modes for Claude Sonnet (JAS), Claude Opus (JAO), and Gemini (JAG).
|
|
23
|
-
- **GLM Z.AI Worker Fleet:** Built-in MCP server integration for delegating heavy coding tasks to specialized AI workers.
|
|
24
|
-
- **Security:** Built-in `SecretScanner` automatically redacts API keys and sensitive credentials before they hit the LLM context.
|
|
25
|
-
- **Context Profiles:** Smart filtering using auto-detected or manual profiles (e.g., `--profile backend`).
|
|
31
|
+
This uses a Git anchor (saved automatically during full snapshot) to detect all modified files and includes their full content. No redundant diffs, no wasted tokens.
|
|
26
32
|
|
|
27
|
-
|
|
33
|
+
### 3. Lazy Loading
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
If the AI asks to see specific files that weren't in the snapshot, show them instantly:
|
|
30
36
|
|
|
31
|
-
### 1. Initial Context (Maximum Compression)
|
|
32
|
-
Create a lightweight map of your entire project. Bodies of functions are hidden, allowing huge monoliths to fit into the AI's context window.
|
|
33
37
|
```bash
|
|
34
|
-
eck-snapshot
|
|
35
|
-
# -> Generates: .eck/snapshots/eck[Name]_[Hash]_sk.md
|
|
38
|
+
eck-snapshot show src/auth.rs src/handlers/sync.rs
|
|
36
39
|
```
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
## Context Profiles
|
|
42
|
+
|
|
43
|
+
Large repositories waste tokens on irrelevant code. Profiles let you partition the codebase so the AI only sees what matters.
|
|
44
|
+
|
|
45
|
+
### Auto-Detection
|
|
46
|
+
|
|
47
|
+
Let AI scan your directory tree and generate profiles automatically:
|
|
48
|
+
|
|
40
49
|
```bash
|
|
41
|
-
eck-snapshot
|
|
50
|
+
eck-snapshot profile-detect
|
|
51
|
+
# -> Saves profiles to .eck/profiles.json
|
|
42
52
|
```
|
|
43
53
|
|
|
44
|
-
###
|
|
45
|
-
|
|
54
|
+
### Manual Guide
|
|
55
|
+
|
|
56
|
+
For very large repos where auto-detection is too slow, generate a prompt guide, paste it into a powerful Web LLM (Gemini, ChatGPT), and save the resulting JSON:
|
|
57
|
+
|
|
46
58
|
```bash
|
|
47
|
-
eck-snapshot
|
|
48
|
-
# ->
|
|
59
|
+
eck-snapshot generate-profile-guide
|
|
60
|
+
# -> .eck/profile_generation_guide.md (paste into AI, get profiles back)
|
|
49
61
|
```
|
|
50
62
|
|
|
51
|
-
|
|
63
|
+
### Using Profiles
|
|
52
64
|
|
|
53
|
-
|
|
65
|
+
```bash
|
|
66
|
+
eck-snapshot --profile # List all available profiles
|
|
67
|
+
eck-snapshot --profile backend # Use a named profile
|
|
68
|
+
eck-snapshot --profile backend --skeleton # Profile + skeleton mode
|
|
69
|
+
eck-snapshot --profile "src/**/*.rs,-**/test_*" # Ad-hoc glob filtering
|
|
70
|
+
```
|
|
54
71
|
|
|
55
|
-
|
|
72
|
+
Profiles work with both full snapshots and incremental updates.
|
|
56
73
|
|
|
57
|
-
|
|
58
|
-
- **Junior Architects:**
|
|
59
|
-
- `JAS` (Sonnet 4.5): Fast manager for standard features. Run `eck-snapshot --jas`.
|
|
60
|
-
- `JAO` (Opus 4.5): Deep thinker for critical architecture. Run `eck-snapshot --jao`.
|
|
61
|
-
- `JAG` (Gemini 3 Pro): Massive context handler. Run `eck-snapshot --jag`.
|
|
74
|
+
## Smart Filtering
|
|
62
75
|
|
|
63
|
-
|
|
64
|
-
Delegate heavy coding tasks (>100 lines) to the **GLM Z.AI Worker Fleet** to save expensive context window tokens.
|
|
76
|
+
eck-snapshot automatically detects your project type (Rust, Node.js, Android, Python, etc.) and excludes language-specific noise:
|
|
65
77
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
3. Your AI will now have access to specialized tools: `glm_zai_frontend`, `glm_zai_backend`, `glm_zai_qa`, `glm_zai_refactor`, and the `eck_finish_task` commit tool.
|
|
78
|
+
- **Rust**: `Cargo.lock`, `target/`
|
|
79
|
+
- **Node.js**: `package-lock.json`, `node_modules/`
|
|
80
|
+
- **Android**: build artifacts, generated code
|
|
81
|
+
- **All projects**: `.git/`, IDE configs, binary files
|
|
72
82
|
|
|
73
|
-
|
|
83
|
+
The built-in `SecretScanner` also redacts API keys, tokens, and credentials before they reach the AI.
|
|
74
84
|
|
|
75
|
-
##
|
|
85
|
+
## Multi-Agent Architecture
|
|
76
86
|
|
|
77
|
-
|
|
87
|
+
eck-snapshot generates tailored `CLAUDE.md` instructions for different AI agent roles:
|
|
78
88
|
|
|
79
89
|
```bash
|
|
80
|
-
#
|
|
81
|
-
eck-snapshot
|
|
90
|
+
eck-snapshot --jas # Junior Architect Sonnet - fast, standard features
|
|
91
|
+
eck-snapshot --jao # Junior Architect Opus - deep, critical architecture
|
|
92
|
+
eck-snapshot --jag # Junior Architect Gemini - massive context tasks
|
|
93
|
+
```
|
|
82
94
|
|
|
83
|
-
|
|
84
|
-
eck-snapshot --profile
|
|
95
|
+
### MCP Server Integration
|
|
85
96
|
|
|
86
|
-
|
|
87
|
-
eck-snapshot --profile backend
|
|
97
|
+
Delegate coding tasks to the GLM Z.AI Worker Fleet via MCP:
|
|
88
98
|
|
|
89
|
-
|
|
90
|
-
|
|
99
|
+
```bash
|
|
100
|
+
export ZAI_API_KEY="your-key"
|
|
101
|
+
eck-snapshot setup-mcp --both # Setup for Claude Code + OpenCode
|
|
91
102
|
```
|
|
92
103
|
|
|
93
|
-
|
|
104
|
+
This gives your AI access to specialized workers: `glm_zai_frontend`, `glm_zai_backend`, `glm_zai_qa`, `glm_zai_refactor`, and the `eck_finish_task` commit tool.
|
|
105
|
+
|
|
106
|
+
## Skeleton Mode
|
|
94
107
|
|
|
95
|
-
|
|
108
|
+
For extremely large projects, skeleton mode strips function bodies and keeps only signatures, types, and structure:
|
|
96
109
|
|
|
97
110
|
```bash
|
|
98
|
-
|
|
99
|
-
|
|
111
|
+
eck-snapshot --skeleton
|
|
112
|
+
```
|
|
100
113
|
|
|
101
|
-
|
|
102
|
-
|
|
114
|
+
Useful for initial orientation in massive codebases, but full snapshots with profiles are usually more practical.
|
|
115
|
+
|
|
116
|
+
## Other Commands
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
eck-snapshot restore <snapshot> # Restore files from a snapshot to disk
|
|
120
|
+
eck-snapshot prune <snapshot> # AI-powered snapshot size reduction
|
|
121
|
+
eck-snapshot doctor # Check project health
|
|
122
|
+
eck-snapshot env push # Encrypt and sync .eck/ config between machines
|
|
123
|
+
eck-snapshot env pull # Restore .eck/ config on another machine
|
|
103
124
|
```
|
|
104
125
|
|
|
105
126
|
## License
|
|
106
|
-
|
|
127
|
+
|
|
128
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xelth/eck-snapshot",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.2",
|
|
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 '';
|