@xelth/eck-snapshot 6.4.2 → 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 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 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xelth/eck-snapshot",
3
- "version": "6.4.2",
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) => ({ name: 'eck_update_auto', arguments: { fail: args.includes('--fail') || args.includes('-f') } }),
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) => ({ name: 'eck_update', arguments: { fail: args.includes('--fail') || args.includes('-f') } }),
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 anchor = await getGitAnchor(repoPath);
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 seqNum = 1;
207
- const counterPath = path.join(repoPath, '.eck', 'update_seq');
208
- try {
209
- const seqData = await fs.readFile(counterPath, 'utf-8');
210
- const [savedHash, savedCount] = seqData.split(':');
211
- if (savedHash && savedHash.trim() === anchor.substring(0, 7).trim()) {
212
- seqNum = parseInt(savedCount || '0') + 1;
213
- }
214
- } catch (e) {}
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
- try {
217
- await fs.writeFile(counterPath, `${anchor.substring(0, 7)}:${seqNum}`);
218
- } catch (e) {}
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${seqNum}_${sizeKB}kb.md`;
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 anchor = await getGitAnchor(repoPath);
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 seqNum = 1;
307
- const counterPath = path.join(repoPath, '.eck', 'update_seq');
308
- try {
309
- const seqData = await fs.readFile(counterPath, 'utf-8');
310
- const [savedHash, savedCount] = seqData.split(':');
311
- if (savedHash && savedHash.trim() === anchor.substring(0, 7).trim()) {
312
- seqNum = parseInt(savedCount || '0') + 1;
313
- }
314
- } catch (e) {}
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
- try {
317
- await fs.writeFile(counterPath, `${anchor.substring(0, 7)}:${seqNum}`);
318
- } catch (e) {}
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${seqNum}_${sizeKB}kb.md`;
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);