@xelth/eck-snapshot 5.4.2 → 5.5.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.
package/README.md CHANGED
@@ -1,11 +1,20 @@
1
1
  # eck-snapshot
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
+ A CLI tool that packs your entire Git repository into a single text file optimized for LLMs. Give any AI full project context in one copy-paste.
4
4
 
5
5
  ```bash
6
6
  npm install -g @xelth/eck-snapshot
7
7
  ```
8
8
 
9
+ ## Recommended AI Setup
10
+
11
+ For best results, we recommend splitting roles between models:
12
+
13
+ - **Architect** (large context window): Gemini, Grok Fast, ChatGPT — upload the full snapshot, design the architecture, plan tasks
14
+ - **Coder** (execution): Claude (via Claude Code), GLM (via OpenCode) — receive tasks from the architect, write and fix code
15
+
16
+ eck-snapshot generates tailored instructions (`CLAUDE.md`, `AGENTS.md`) for each role automatically.
17
+
9
18
  ## Core Workflow
10
19
 
11
20
  ### 1. Full Snapshot
@@ -17,7 +26,7 @@ eck-snapshot
17
26
  # -> .eck/snapshots/eckMyProject_26-02-15_12-00_abc1234.md
18
27
  ```
19
28
 
20
- That's it. Upload the file to your AI and start working.
29
+ Upload the file to your architect AI and start working.
21
30
 
22
31
  ### 2. Incremental Update
23
32
 
@@ -30,14 +39,6 @@ eck-snapshot update
30
39
 
31
40
  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.
32
41
 
33
- ### 3. Lazy Loading
34
-
35
- If the AI asks to see specific files that weren't in the snapshot, show them instantly:
36
-
37
- ```bash
38
- eck-snapshot show src/auth.rs src/handlers/sync.rs
39
- ```
40
-
41
42
  ## Context Profiles
42
43
 
43
44
  Large repositories waste tokens on irrelevant code. Profiles let you partition the codebase so the AI only sees what matters.
@@ -103,7 +104,7 @@ eck-snapshot setup-mcp --both # Setup for Claude Code + OpenCode
103
104
 
104
105
  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
 
106
- ## Skeleton Mode
107
+ ## Skeleton Mode & Lazy Loading
107
108
 
108
109
  For extremely large projects, skeleton mode strips function bodies and keeps only signatures, types, and structure:
109
110
 
@@ -111,6 +112,12 @@ For extremely large projects, skeleton mode strips function bodies and keeps onl
111
112
  eck-snapshot --skeleton
112
113
  ```
113
114
 
115
+ When using skeleton mode, the AI can request full content of specific files on demand:
116
+
117
+ ```bash
118
+ eck-snapshot show src/auth.rs src/handlers/sync.rs
119
+ ```
120
+
114
121
  Useful for initial orientation in massive codebases, but full snapshots with profiles are usually more practical.
115
122
 
116
123
  ## Other Commands
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xelth/eck-snapshot",
3
- "version": "5.4.2",
3
+ "version": "5.5.0",
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",
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * MCP Eck Core - Provides core project management capabilities to Claude
3
+ * MCP Eck Core - Unified task completion tool for Claude Code and OpenCode
4
4
  */
5
5
 
6
6
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
7
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
8
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
9
9
  import { execa } from "execa";
10
+ import fs from "fs/promises";
10
11
  import path from "path";
11
12
  import { fileURLToPath } from 'url';
12
13
 
@@ -15,7 +16,7 @@ const __dirname = path.dirname(__filename);
15
16
  const PROJECT_ROOT = path.resolve(__dirname, '..');
16
17
 
17
18
  const server = new Server(
18
- { name: "eck-core", version: "1.0.0" },
19
+ { name: "eck-core", version: "2.0.0" },
19
20
  { capabilities: { tools: {} } }
20
21
  );
21
22
 
@@ -24,16 +25,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
24
25
  tools: [
25
26
  {
26
27
  name: "eck_finish_task",
27
- description: "Completes the current coding task. 1) Stages all changes. 2) Commits with the provided message. 3) Automatically updates the context snapshot. Use this instead of manual git commands.",
28
+ description: "Completes the current coding task. 1) Overwrites AnswerToSA.md with status for the Architect. 2) Stages all changes. 3) Commits with the provided message. 4) Automatically updates the context snapshot. Use this instead of manual git commands.",
28
29
  inputSchema: {
29
30
  type: "object",
30
31
  properties: {
32
+ status: {
33
+ type: "string",
34
+ description: "Status update for the Architect: what was done, what issues remain, what needs review"
35
+ },
31
36
  message: {
32
37
  type: "string",
33
38
  description: "Git commit message (follow Conventional Commits, e.g. 'feat: add login')"
34
39
  }
35
40
  },
36
- required: ["message"]
41
+ required: ["status", "message"]
37
42
  }
38
43
  }
39
44
  ]
@@ -42,20 +47,28 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
42
47
 
43
48
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
44
49
  if (request.params.name === "eck_finish_task") {
45
- const { message } = request.params.arguments;
50
+ const { status, message } = request.params.arguments;
51
+ const workDir = process.cwd();
46
52
 
47
53
  try {
48
- // 1. Git Add
49
- await execa("git", ["add", "."], { cwd: process.cwd(), timeout: 30000 });
54
+ // 1. Write fresh AnswerToSA.md (OVERWRITE, not append)
55
+ const answerDir = path.join(workDir, '.eck', 'lastsnapshot');
56
+ await fs.mkdir(answerDir, { recursive: true });
57
+ await fs.writeFile(
58
+ path.join(answerDir, 'AnswerToSA.md'),
59
+ `# Agent Report\n\n${status}\n`,
60
+ 'utf-8'
61
+ );
62
+
63
+ // 2. Git Add
64
+ await execa("git", ["add", "."], { cwd: workDir, timeout: 30000 });
50
65
 
51
- // 2. Git Commit
52
- // We allow empty commits just in case, though unlikely in a finish_task scenario
53
- await execa("git", ["commit", "--allow-empty", "-m", message], { cwd: process.cwd(), timeout: 30000 });
66
+ // 3. Git Commit
67
+ await execa("git", ["commit", "--allow-empty", "-m", message], { cwd: workDir, timeout: 30000 });
54
68
 
55
- // 3. Auto Update Snapshot via CLI
56
- // We use the local index.js to ensure we use the current version of the tool
69
+ // 4. Auto Update Snapshot
57
70
  const cliPath = path.join(PROJECT_ROOT, "index.js");
58
- const { stdout } = await execa("node", [cliPath, "update-auto"], { cwd: process.cwd(), timeout: 120000 });
71
+ const { stdout } = await execa("node", [cliPath, "update-auto"], { cwd: workDir, timeout: 120000 });
59
72
 
60
73
  let result;
61
74
  try {
@@ -70,22 +83,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
70
83
  return {
71
84
  content: [{
72
85
  type: "text",
73
- text: `✅ Task Completed Successfully.\n\n1. 💾 Git Commit: "${message}"\n2. 📸 Context Updated: ${result.snapshot_file} (+${result.files_count} files)\n\nReady for next instruction.`
86
+ text: `✅ Committed: "${message}"\n📝 AnswerToSA.md updated\n📸 Snapshot: ${result.snapshot_file} (${result.files_count} files)`
74
87
  }]
75
88
  };
76
- } else if (result.status === "no_changes") {
77
- return {
78
- content: [{ type: "text", text: `✅ Committed: "${message}"\nℹ️ No new changes for snapshot update.` }]
79
- };
80
89
  } else {
81
90
  return {
82
- content: [{ type: "text", text: `✅ Committed: "${message}"\n Snapshot Update Failed: ${result.message}` }]
91
+ content: [{ type: "text", text: `✅ Committed: "${message}"\nℹ️ Snapshot: ${result.message}` }]
83
92
  };
84
93
  }
85
94
 
86
95
  } catch (error) {
87
96
  return {
88
- content: [{ type: "text", text: `❌ Error finishing task: ${error.message}\n${error.stdout || ''}\n${error.stderr || ''}` }],
97
+ content: [{ type: "text", text: `❌ Error: ${error.message}\n${error.stderr || ''}` }],
89
98
  isError: true
90
99
  };
91
100
  }
@@ -17,15 +17,19 @@ async function generateSnapshotContent(repoPath, changedFiles, anchor, config, g
17
17
  let includedCount = 0;
18
18
  const fileList = [];
19
19
 
20
- // Check for Agent Report in .eck/lastsnapshot/AnswerToSA.md (STRICT LOCATION)
21
- const reportPath = path.join(repoPath, '.eck', 'lastsnapshot', 'AnswerToSA.md');
20
+ // Include Agent Report ONLY if it was modified in this commit cycle
21
+ const reportRelPath = '.eck/lastsnapshot/AnswerToSA.md';
22
+ const reportChanged = changedFiles.some(f =>
23
+ f === reportRelPath || f === reportRelPath.replace(/\//g, path.sep)
24
+ );
25
+
22
26
  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 */ }
27
+ if (reportChanged) {
28
+ try {
29
+ const reportPath = path.join(repoPath, '.eck', 'lastsnapshot', 'AnswerToSA.md');
30
+ agentReport = await fs.readFile(reportPath, 'utf-8');
31
+ } catch (e) { /* Failed to read */ }
32
+ }
29
33
 
30
34
  for (const filePath of changedFiles) {
31
35
  if (config.dirsToIgnore?.some(d => filePath.startsWith(d))) continue;
@@ -56,14 +56,14 @@ class EckSnapshotMCPServer {
56
56
  properties: {
57
57
  status: {
58
58
  type: 'string',
59
- description: 'Status update message for AnswerToSA.md (e.g., "Fixed bug X", "Implemented feature Y")',
59
+ description: 'Status update for the Architect: what was done, what issues remain, what needs review',
60
60
  },
61
- commitMessage: {
61
+ message: {
62
62
  type: 'string',
63
- description: 'Git commit message. Should follow conventional commits format (e.g., "feat: add user authentication", "fix: resolve login issue")',
63
+ description: 'Git commit message (follow Conventional Commits, e.g. "feat: add login")',
64
64
  },
65
65
  },
66
- required: ['status', 'commitMessage'],
66
+ required: ['status', 'message'],
67
67
  },
68
68
  },
69
69
  ],
@@ -75,10 +75,12 @@ class EckSnapshotMCPServer {
75
75
  throw new Error(`Unknown tool: ${request.params.name}`);
76
76
  }
77
77
 
78
- const { status, commitMessage } = request.params.arguments;
78
+ const { status, message } = request.params.arguments;
79
+ // Support legacy 'commitMessage' parameter
80
+ const commitMessage = message || request.params.arguments.commitMessage;
79
81
 
80
82
  if (!status || !commitMessage) {
81
- throw new Error('Missing required arguments: status and commitMessage are required');
83
+ throw new Error('Missing required arguments: status and message are required');
82
84
  }
83
85
 
84
86
  try {
@@ -111,23 +113,11 @@ class EckSnapshotMCPServer {
111
113
 
112
114
  const steps = [];
113
115
 
114
- // Step 1: Update AnswerToSA.md
116
+ // Step 1: Write AnswerToSA.md (OVERWRITE, not append)
115
117
  try {
116
- const timestamp = new Date().toISOString();
117
- const updateContent = `\n## Update - ${timestamp}\n\n${status}\n`;
118
-
119
- // Check if file exists
120
- try {
121
- await fs.access(answerFilePath);
122
- // Append to existing file
123
- await fs.appendFile(answerFilePath, updateContent);
124
- steps.push({ step: 'update_answer', success: true, message: 'Updated AnswerToSA.md' });
125
- } catch {
126
- // Create directory and file if not exists
127
- await fs.mkdir(path.dirname(answerFilePath), { recursive: true });
128
- await fs.writeFile(answerFilePath, `# Task Status\n${updateContent}`);
129
- steps.push({ step: 'update_answer', success: true, message: 'Created AnswerToSA.md' });
130
- }
118
+ await fs.mkdir(path.dirname(answerFilePath), { recursive: true });
119
+ await fs.writeFile(answerFilePath, `# Agent Report\n\n${status}\n`, 'utf-8');
120
+ steps.push({ step: 'update_answer', success: true, message: 'Updated AnswerToSA.md' });
131
121
  } catch (error) {
132
122
  steps.push({ step: 'update_answer', success: false, error: error.message });
133
123
  throw new Error(`Failed to update AnswerToSA.md: ${error.message}`);