@xelth/eck-snapshot 6.4.1 → 6.4.3
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 +10 -1
- package/package.json +1 -1
- package/src/cli/cli.js +11 -2
- package/src/cli/commands/updateSnapshot.js +54 -28
- package/src/templates/multiAgent.md +19 -4
- package/src/utils/fileUtils.js +18 -0
package/README.md
CHANGED
|
@@ -36,6 +36,8 @@ Pass the Architect's technical plan to your local **Coder agent** (Claude Code /
|
|
|
36
36
|
### 3. Auto-Updates
|
|
37
37
|
When the Coder agent finishes a task, it automatically calls the built-in MCP tool (`eck_finish_task`), which commits the code and generates an incremental delta update snapshot. Feed that update back to the Architect to keep it in sync.
|
|
38
38
|
|
|
39
|
+
> **Custom base:** Use `eck-snapshot update --base <snapshot.md>` to generate a delta relative to any past snapshot file. Pass the filename (e.g. `eckRepo26-04-01_f2e1bd4_up1_29kb.md`) — the anchor hash is extracted automatically. This doesn't disturb the automatic sequence counter — custom-base snapshots get a `_upcustom` suffix. A raw git hash (7+ hex chars) also works.
|
|
40
|
+
|
|
39
41
|
---
|
|
40
42
|
|
|
41
43
|
## 🧠 Which Models to Use
|
|
@@ -83,7 +85,7 @@ For humans typing in the terminal, short commands work too:
|
|
|
83
85
|
| # | Command | Description |
|
|
84
86
|
|---|---------|-------------|
|
|
85
87
|
| 1 | `eck-snapshot snapshot` | Full project snapshot |
|
|
86
|
-
| 2 | `eck-snapshot update` | Delta update (changed files only) |
|
|
88
|
+
| 2 | `eck-snapshot update` | Delta update (changed files only). Supports `--base <snapshot.md>` to compare against an old snapshot file. |
|
|
87
89
|
| 3 | `eck-snapshot profile [name]` | Snapshot filtered by profile (no arg = list profiles) |
|
|
88
90
|
| 4 | `eck-snapshot scout [0-9]` | Scout external repo (see depth scale below) |
|
|
89
91
|
| 5 | `eck-snapshot fetch "src/**/*.rs"` | Fetch specific files by glob |
|
|
@@ -171,6 +173,13 @@ Both `scout` and `link` use the same depth scale to control content granularity:
|
|
|
171
173
|
* **📚 NotebookLM Export:** Semantic chunking for Google's NotebookLM with "Brain + Body" architecture (see below).
|
|
172
174
|
* **🧠 Multi-Agent Protocol:** Junior Architect delegation system for multi-agent coding workflows (see below).
|
|
173
175
|
|
|
176
|
+
### 🤖 Autonomous AI Protocols
|
|
177
|
+
`eckSnapshot` automatically injects strict behavioral protocols into the AI Architect's prompt (`multiAgent.md`) to ensure high code quality and prevent context degradation:
|
|
178
|
+
1. **Context Hygiene Protocol:** The AI actively monitors the directory tree for bloat (logs, DB dumps, binaries). If detected, it autonomously constructs an `.eckignore` file to hide the garbage, saving tokens and context space.
|
|
179
|
+
2. **Proactive Tech Debt:** The AI scans for `TODO`, `FIXME`, and `HACK` comments, evaluating them against the actual code. It will autonomously delete obsolete comments, fix quick bugs, or document major issues in `.eck/TECH_DEBT.md`.
|
|
180
|
+
3. **The Boy Scout Rule:** Whenever the AI modifies or creates a function, it is forced to write or update its JSDoc/Docstring to explain *why* the code exists, keeping documentation perfectly synced.
|
|
181
|
+
4. **Zero-Broken-Windows (Reliability):** Blind commits are strictly forbidden. The AI must run the project's test suite (e.g., `npm test`, `cargo test`) and ensure all tests pass before calling the task completion tool.
|
|
182
|
+
|
|
174
183
|
---
|
|
175
184
|
|
|
176
185
|
## 📚 NotebookLM Integration (New in v6.3 — Testing)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xelth/eck-snapshot",
|
|
3
|
-
"version": "6.4.
|
|
3
|
+
"version": "6.4.3",
|
|
4
4
|
"description": "A powerful CLI tool to create and restore single-file text snapshots of Git repositories. Optimized for AI context, LLM workflows, and multi-agent Swarm coordination.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
package/src/cli/cli.js
CHANGED
|
@@ -43,9 +43,17 @@ import { runTokenTools } from './commands/trainTokens.js';
|
|
|
43
43
|
// Legacy command shims: translate old positional commands to JSON payloads
|
|
44
44
|
// so internal callers (mcp-eck-core.js) keep working after the JSON migration.
|
|
45
45
|
const LEGACY_COMMANDS = {
|
|
46
|
-
'update-auto': (args) =>
|
|
46
|
+
'update-auto': (args) => {
|
|
47
|
+
const baseIdx = args.indexOf('--base');
|
|
48
|
+
const base = baseIdx !== -1 && args[baseIdx + 1] ? args[baseIdx + 1] : undefined;
|
|
49
|
+
return { name: 'eck_update_auto', arguments: { fail: args.includes('--fail') || args.includes('-f'), base } };
|
|
50
|
+
},
|
|
47
51
|
'snapshot': () => ({ name: 'eck_snapshot', arguments: {} }),
|
|
48
|
-
'update': (args) =>
|
|
52
|
+
'update': (args) => {
|
|
53
|
+
const baseIdx = args.indexOf('--base');
|
|
54
|
+
const base = baseIdx !== -1 && args[baseIdx + 1] ? args[baseIdx + 1] : undefined;
|
|
55
|
+
return { name: 'eck_update', arguments: { fail: args.includes('--fail') || args.includes('-f'), base } };
|
|
56
|
+
},
|
|
49
57
|
'setup-mcp': (args) => ({ name: 'eck_setup_mcp', arguments: { opencode: args.includes('--opencode'), both: args.includes('--both') } }),
|
|
50
58
|
'detect': () => ({ name: 'eck_detect', arguments: {} }),
|
|
51
59
|
'doctor': () => ({ name: 'eck_doctor', arguments: {} }),
|
|
@@ -90,6 +98,7 @@ Ranked by frequency of use:
|
|
|
90
98
|
|
|
91
99
|
1. eck-snapshot snapshot Full project snapshot
|
|
92
100
|
2. eck-snapshot update Delta update (changed files only)
|
|
101
|
+
--base <snapshot.md> : Compare against an old snapshot file
|
|
93
102
|
3. eck-snapshot profile [name] Snapshot filtered by profile (from .eck/profiles.json)
|
|
94
103
|
No arg = list available profiles
|
|
95
104
|
Example: eck-snapshot profile backend
|
|
@@ -37,6 +37,15 @@ async function autoCommit(repoPath) {
|
|
|
37
37
|
const __filename = fileURLToPath(import.meta.url);
|
|
38
38
|
const __dirname = path.dirname(__filename);
|
|
39
39
|
|
|
40
|
+
function resolveBaseHash(base) {
|
|
41
|
+
if (!base) return null;
|
|
42
|
+
const basename = path.basename(base, '.md');
|
|
43
|
+
const match = basename.match(/_([0-9a-f]{7,40})_/);
|
|
44
|
+
if (match) return match[1];
|
|
45
|
+
if (/^[0-9a-f]{7,40}$/i.test(base)) return base;
|
|
46
|
+
throw new Error(`Invalid --base value: "${base}". Expected a snapshot filename or a git commit hash.`);
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
// Shared logic to generate the snapshot content string
|
|
41
50
|
async function generateSnapshotContent(repoPath, changedFiles, anchor, config, gitignore) {
|
|
42
51
|
let contentOutput = '';
|
|
@@ -160,7 +169,9 @@ async function generateSnapshotContent(repoPath, changedFiles, anchor, config, g
|
|
|
160
169
|
export async function updateSnapshot(repoPath, options) {
|
|
161
170
|
const spinner = ora('Generating update snapshot...').start();
|
|
162
171
|
try {
|
|
163
|
-
const
|
|
172
|
+
const isCustomBase = !!options.base;
|
|
173
|
+
const anchor = resolveBaseHash(options.base) || await getGitAnchor(repoPath);
|
|
174
|
+
|
|
164
175
|
if (!anchor) {
|
|
165
176
|
throw new Error('No snapshot anchor found. Run a full snapshot first: eck-snapshot snapshot');
|
|
166
177
|
}
|
|
@@ -175,6 +186,11 @@ export async function updateSnapshot(repoPath, options) {
|
|
|
175
186
|
} else {
|
|
176
187
|
spinner.info('Fail flag passed: skipping auto-commit.');
|
|
177
188
|
}
|
|
189
|
+
|
|
190
|
+
if (isCustomBase) {
|
|
191
|
+
spinner.info(`Using custom base: ${anchor.substring(0, 7)} (from ${path.basename(options.base)})`);
|
|
192
|
+
}
|
|
193
|
+
|
|
178
194
|
spinner.start('Generating update snapshot...');
|
|
179
195
|
|
|
180
196
|
const changedFiles = await getChangedFiles(repoPath, anchor, options.fail);
|
|
@@ -203,24 +219,28 @@ export async function updateSnapshot(repoPath, options) {
|
|
|
203
219
|
const { fullContent, includedCount, agentReport } = await generateSnapshotContent(repoPath, changedFiles, anchor, config, gitignore);
|
|
204
220
|
|
|
205
221
|
// Determine sequence number
|
|
206
|
-
let
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
222
|
+
let seqStr = 'custom';
|
|
223
|
+
if (!isCustomBase) {
|
|
224
|
+
let seqNum = 1;
|
|
225
|
+
const counterPath = path.join(repoPath, '.eck', 'update_seq');
|
|
226
|
+
try {
|
|
227
|
+
const seqData = await fs.readFile(counterPath, 'utf-8');
|
|
228
|
+
const [savedHash, savedCount] = seqData.split(':');
|
|
229
|
+
if (savedHash && savedHash.trim() === anchor.substring(0, 7).trim()) {
|
|
230
|
+
seqNum = parseInt(savedCount || '0') + 1;
|
|
231
|
+
}
|
|
232
|
+
} catch (e) {}
|
|
215
233
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
234
|
+
try {
|
|
235
|
+
await fs.writeFile(counterPath, `${anchor.substring(0, 7)}:${seqNum}`);
|
|
236
|
+
} catch (e) {}
|
|
237
|
+
seqStr = seqNum.toString();
|
|
238
|
+
}
|
|
219
239
|
|
|
220
240
|
const timestamp = generateTimestamp();
|
|
221
241
|
const shortRepoName = getShortRepoName(path.basename(repoPath));
|
|
222
242
|
const sizeKB = Math.max(1, Math.round(Buffer.byteLength(fullContent, 'utf-8') / 1024));
|
|
223
|
-
const outputFilename = `eck${shortRepoName}${timestamp}_${anchor.substring(0, 7)}_up${
|
|
243
|
+
const outputFilename = `eck${shortRepoName}${timestamp}_${anchor.substring(0, 7)}_up${seqStr}_${sizeKB}kb.md`;
|
|
224
244
|
const outputPath = path.join(repoPath, '.eck', 'snapshots', outputFilename);
|
|
225
245
|
|
|
226
246
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
@@ -267,7 +287,9 @@ export async function updateSnapshot(repoPath, options) {
|
|
|
267
287
|
// New Silent/JSON command for Agents
|
|
268
288
|
export async function updateSnapshotJson(repoPath, options = {}) {
|
|
269
289
|
try {
|
|
270
|
-
const
|
|
290
|
+
const isCustomBase = !!options.base;
|
|
291
|
+
const anchor = resolveBaseHash(options.base) || await getGitAnchor(repoPath);
|
|
292
|
+
|
|
271
293
|
if (!anchor) {
|
|
272
294
|
console.log(JSON.stringify({ status: "error", message: "No snapshot anchor found" }));
|
|
273
295
|
return;
|
|
@@ -303,24 +325,28 @@ export async function updateSnapshotJson(repoPath, options = {}) {
|
|
|
303
325
|
|
|
304
326
|
const { fullContent, includedCount, agentReport } = await generateSnapshotContent(repoPath, changedFiles, anchor, config, gitignore);
|
|
305
327
|
|
|
306
|
-
let
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
328
|
+
let seqStr = 'custom';
|
|
329
|
+
if (!isCustomBase) {
|
|
330
|
+
let seqNum = 1;
|
|
331
|
+
const counterPath = path.join(repoPath, '.eck', 'update_seq');
|
|
332
|
+
try {
|
|
333
|
+
const seqData = await fs.readFile(counterPath, 'utf-8');
|
|
334
|
+
const [savedHash, savedCount] = seqData.split(':');
|
|
335
|
+
if (savedHash && savedHash.trim() === anchor.substring(0, 7).trim()) {
|
|
336
|
+
seqNum = parseInt(savedCount || '0') + 1;
|
|
337
|
+
}
|
|
338
|
+
} catch (e) {}
|
|
315
339
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
340
|
+
try {
|
|
341
|
+
await fs.writeFile(counterPath, `${anchor.substring(0, 7)}:${seqNum}`);
|
|
342
|
+
} catch (e) {}
|
|
343
|
+
seqStr = seqNum.toString();
|
|
344
|
+
}
|
|
319
345
|
|
|
320
346
|
const timestamp = generateTimestamp();
|
|
321
347
|
const shortRepoName = getShortRepoName(path.basename(repoPath));
|
|
322
348
|
const sizeKB = Math.max(1, Math.round(Buffer.byteLength(fullContent, 'utf-8') / 1024));
|
|
323
|
-
const outputFilename = `eck${shortRepoName}${timestamp}_${anchor.substring(0, 7)}_up${
|
|
349
|
+
const outputFilename = `eck${shortRepoName}${timestamp}_${anchor.substring(0, 7)}_up${seqStr}_${sizeKB}kb.md`;
|
|
324
350
|
const outputPath = path.join(repoPath, '.eck', 'snapshots', outputFilename);
|
|
325
351
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
326
352
|
await fs.writeFile(outputPath, fullContent);
|
|
@@ -37,10 +37,16 @@ The `.eck/` directory files are your "Source of Knowledge".
|
|
|
37
37
|
### 🧹 CONTEXT HYGIENE PROTOCOL (CRITICAL)
|
|
38
38
|
|
|
39
39
|
You are responsible for keeping your own context window clean and efficient.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
**Mandatory Full Snapshot Evaluation:** Whenever you receive a FULL snapshot, you MUST evaluate the `Directory Structure` and file list for irrelevant bloat (e.g., compiled binaries, DB dumps like .wal, huge logs, decompiled code).
|
|
41
|
+
|
|
42
|
+
If bloat is detected:
|
|
43
|
+
1. **Report to User:** Briefly tell the user how much garbage was found.
|
|
44
|
+
- *Massive Bloat:* Suggest pausing the main task. Generate an `eck_task` ONLY to create/update `.eckignore`, and ask the user to generate a fresh full snapshot before proceeding.
|
|
45
|
+
- *Minor Bloat:* Append the `.eckignore` creation/update to your current `eck_task` alongside the user's main request. Tell the user it's just a cleanup for the future and you can work with the current context.
|
|
46
|
+
2. **Execution:** Instruct the Coder to use standard `.gitignore` syntax in `.eckignore` (e.g., `data/surreal_data/`, `*.wal`).
|
|
47
|
+
|
|
48
|
+
**🚨 MANUAL OVERRIDE (THE CODE WORD):**
|
|
49
|
+
If the human user includes the phrase **`[EXECUTE HYGIENE]`** or **`[ВРЕМЯ УБОРКИ]`**, immediately suspend all feature development and dedicate your response exclusively to cleaning the context via `.eckignore`.
|
|
44
50
|
|
|
45
51
|
### 📝 PROACTIVE TECH DEBT & TODO PROTOCOL
|
|
46
52
|
|
|
@@ -59,6 +65,15 @@ Whenever you instruct the Coder to **modify an existing function/class** or **cr
|
|
|
59
65
|
> *"Ensure the JSDoc / Docstring for this function is created or updated to accurately reflect its new behavior, parameters, and return types. Explain WHY it exists, not just WHAT it does."*
|
|
60
66
|
Do not rewrite documentation for the entire file—only strictly for the components you are touching.
|
|
61
67
|
|
|
68
|
+
### 🛡️ RELIABILITY & ZERO-BROKEN-WINDOWS PROTOCOL
|
|
69
|
+
|
|
70
|
+
You are the final gatekeeper of quality. A task is NEVER complete if the code doesn't compile or tests fail.
|
|
71
|
+
Whenever you delegate a task to the Coder, you MUST enforce the following verification steps:
|
|
72
|
+
1. **Identify the Test Suite:** Check the environment (e.g., `npm test`, `cargo test`, `pytest`).
|
|
73
|
+
2. **Mandate Verification:** Explicitly instruct the Coder:
|
|
74
|
+
> *"Before calling `eck_finish_task`, you MUST run the project's test suite and ensure all tests pass. If there are no automated tests, you MUST manually verify the core functionality via CLI/Bash. Do NOT finish the task if there are compilation errors or test failures."*
|
|
75
|
+
3. **No Blind Commits:** Never allow the Coder to commit code blindly. Verification is mandatory.
|
|
76
|
+
|
|
62
77
|
### CRITICAL WORKFLOW: Structured Commits via `journal_entry`
|
|
63
78
|
|
|
64
79
|
To ensure proper project history, all code changes **MUST** be committed using the project's built-in structured workflow.
|
package/src/utils/fileUtils.js
CHANGED
|
@@ -320,6 +320,24 @@ export async function generateDirectoryTree(dir, prefix = '', allFiles, depth =
|
|
|
320
320
|
const validEntries = [];
|
|
321
321
|
|
|
322
322
|
for (const entry of sortedEntries) {
|
|
323
|
+
// --- GLOBAL HARD IGNORES ---
|
|
324
|
+
if (entry.isDirectory() && (
|
|
325
|
+
entry.name === 'node_modules' ||
|
|
326
|
+
entry.name === '.git' ||
|
|
327
|
+
entry.name === '.idea' ||
|
|
328
|
+
entry.name === '.vscode' ||
|
|
329
|
+
entry.name === '.gradle' ||
|
|
330
|
+
entry.name === 'build' ||
|
|
331
|
+
entry.name === '__pycache__'
|
|
332
|
+
)) continue;
|
|
333
|
+
if (!entry.isDirectory() && (
|
|
334
|
+
entry.name === 'package-lock.json' ||
|
|
335
|
+
entry.name === 'yarn.lock' ||
|
|
336
|
+
entry.name === 'pnpm-lock.yaml' ||
|
|
337
|
+
entry.name === 'go.sum'
|
|
338
|
+
)) continue;
|
|
339
|
+
// ---------------------------
|
|
340
|
+
|
|
323
341
|
// Skip hidden directories and files (starting with '.')
|
|
324
342
|
// EXCEPT: Allow .eck to be visible
|
|
325
343
|
if (entry.name.startsWith('.') && entry.name !== '.eck') {
|