@xelth/eck-snapshot 5.9.0 → 6.0.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 +46 -165
- package/package.json +2 -2
- package/scripts/mcp-eck-core.js +61 -13
- package/src/cli/cli.js +2 -0
- package/src/cli/commands/updateSnapshot.js +128 -76
- package/src/templates/opencode/coder.template.md +25 -16
- package/src/templates/opencode/junior-architect.template.md +28 -15
- package/src/utils/aiHeader.js +5 -4
- package/src/utils/claudeMdGenerator.js +84 -77
- package/src/utils/fileUtils.js +154 -89
- package/src/utils/gitUtils.js +12 -8
- package/src/utils/opencodeAgentsGenerator.js +8 -2
- package/src/utils/tokenEstimator.js +50 -46
package/README.md
CHANGED
|
@@ -1,190 +1,71 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 📸 eckSnapshot v6.0
|
|
2
2
|
|
|
3
|
-
A CLI tool
|
|
3
|
+
A specialized, AI-native CLI tool designed to create and restore single-file text snapshots of Git repositories. Optimized for providing full project context to Large Language Models (LLMs) and serving as the coordination hub for Multi-Agent AI Architectures.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
npm install -g @xelth/eck-snapshot
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
## Recommended AI Setup
|
|
10
|
-
|
|
11
|
-
For best results, we recommend splitting roles between models:
|
|
5
|
+
## 🌟 Key Features
|
|
12
6
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
7
|
+
* **🧠 Multi-Agent Protocol (Royal Court):** Built-in support for the "Royal Court" architecture. Delegate tasks from a Senior Architect (Claude/Gemini) to Junior Managers, who orchestrate a swarm of specialized GLM-4.7 workers.
|
|
8
|
+
* **☠️ Skeleton Mode:** Uses `Tree-sitter` and `Babel` to strip function bodies, drastically reducing token count while preserving structural context. Supports JS/TS, Rust, Go, Python, C, Java, and Kotlin.
|
|
9
|
+
* **🔄 Smart Delta Updates:** Tracks incremental changes via Git anchors with sequential numbering. Now accurately tracks and reports deleted files to prevent LLM hallucinations.
|
|
10
|
+
* **🛡️ Security (SecretScanner):** Automatically redacts API keys and credentials before sending context to LLMs. Features both Regex matching and **Shannon Entropy** analysis for catching non-standard hardcoded secrets.
|
|
11
|
+
* **📊 Telemetry Hub:** Integrated with a Rust-based microservice (`eck-telemetry`) for tracking agent execution metrics, auto-syncing token estimation weights via linear regression, and in-memory caching.
|
|
12
|
+
* **🔌 Native MCP Integration:** Instantly spins up Model Context Protocol (MCP) servers (`eck-core` for context sync and `glm-zai` for worker swarms) for Claude Code and OpenCode.
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
## Core Workflow
|
|
19
|
-
|
|
20
|
-
### 1. Full Snapshot
|
|
21
|
-
|
|
22
|
-
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.
|
|
14
|
+
## 🚀 Quick Start
|
|
23
15
|
|
|
16
|
+
### Installation
|
|
24
17
|
```bash
|
|
25
|
-
eck-snapshot
|
|
26
|
-
# -> .eck/snapshots/eckMyProject_26-02-15_12-00_abc1234.md
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Upload the file to your architect AI and start working.
|
|
30
|
-
|
|
31
|
-
### 2. Incremental Update
|
|
32
|
-
|
|
33
|
-
After you make changes, don't re-send the entire project. Send only what changed since the last full snapshot:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
eck-snapshot update
|
|
37
|
-
# -> .eck/snapshots/eckMyProject_26-02-15_14-30_abc1234_up1_42kb.md
|
|
38
|
-
```
|
|
39
|
-
|
|
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.
|
|
41
|
-
|
|
42
|
-
Notice the `_42kb` suffix in the filename — this is the **Anti-Truncation Guard** (see below).
|
|
43
|
-
|
|
44
|
-
## Context Profiles
|
|
45
|
-
|
|
46
|
-
Large repositories waste tokens on irrelevant code. Profiles let you partition the codebase so the AI only sees what matters.
|
|
47
|
-
|
|
48
|
-
### Auto-Detection
|
|
49
|
-
|
|
50
|
-
Let AI scan your directory tree and generate profiles automatically:
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
eck-snapshot profile-detect
|
|
54
|
-
# -> Saves profiles to .eck/profiles.json
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### Manual Guide
|
|
58
|
-
|
|
59
|
-
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:
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
eck-snapshot generate-profile-guide
|
|
63
|
-
# -> .eck/profile_generation_guide.md (paste into AI, get profiles back)
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Using Profiles
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
eck-snapshot --profile # List all available profiles
|
|
70
|
-
eck-snapshot --profile backend # Use a named profile
|
|
71
|
-
eck-snapshot --profile backend --skeleton # Profile + skeleton mode
|
|
72
|
-
eck-snapshot --profile "src/**/*.rs,-**/test_*" # Ad-hoc glob filtering
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Profiles work with both full snapshots and incremental updates.
|
|
76
|
-
|
|
77
|
-
## Anti-Truncation Guard
|
|
78
|
-
|
|
79
|
-
Web AI interfaces (Gemini, ChatGPT) sometimes silently drop the content of large uploaded files, replacing them with a small JSON metadata stub like `{"fileName": "...", "contentFetchId": "..."}`. The AI then hallucinates code it never actually received.
|
|
80
|
-
|
|
81
|
-
eck-snapshot counters this with two mechanisms:
|
|
82
|
-
|
|
83
|
-
**1. Size-stamped filenames.** Every snapshot — full and incremental — embeds its exact size in kilobytes directly in the filename:
|
|
84
|
-
|
|
85
|
-
```
|
|
86
|
-
eckMyProject_26-02-15_12-00_abc1234_1250kb.md ← full snapshot
|
|
87
|
-
eckMyProject_26-02-15_14-30_abc1234_up1_42kb.md ← incremental update
|
|
18
|
+
npm install -g @xelth/eck-snapshot
|
|
88
19
|
```
|
|
89
20
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
> 🚨 **System Error:** The web interface truncated the file `eckMyProject_..._1250kb.md`. I only received the metadata/JSON stub, not the actual 1250 kb of code. Please split the snapshot or paste the text directly.
|
|
93
|
-
|
|
94
|
-
This protection works on any web interface that embeds the filename (Gemini, ChatGPT, Grok, etc.).
|
|
95
|
-
|
|
96
|
-
## Smart Filtering
|
|
97
|
-
|
|
98
|
-
eck-snapshot automatically detects your project type (Rust, Node.js, Android, Python, etc.) and excludes language-specific noise:
|
|
99
|
-
|
|
100
|
-
- **Rust**: `Cargo.lock`, `target/`
|
|
101
|
-
- **Node.js**: `package-lock.json`, `node_modules/`
|
|
102
|
-
- **Android**: build artifacts, generated code
|
|
103
|
-
- **All projects**: `.git/`, IDE configs, binary files
|
|
104
|
-
|
|
105
|
-
The built-in `SecretScanner` also redacts API keys, tokens, and credentials before they reach the AI.
|
|
106
|
-
|
|
107
|
-
## Multi-Agent Architecture
|
|
108
|
-
|
|
109
|
-
eck-snapshot generates tailored `CLAUDE.md` instructions for different AI agent roles:
|
|
110
|
-
|
|
21
|
+
### Basic Usage
|
|
111
22
|
```bash
|
|
112
|
-
|
|
113
|
-
eck-snapshot
|
|
114
|
-
eck-snapshot --jag # Junior Architect Gemini - massive context tasks
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Chinese Delegation (`--zh`)
|
|
23
|
+
# Create a standard full snapshot
|
|
24
|
+
eck-snapshot snapshot
|
|
118
25
|
|
|
119
|
-
|
|
26
|
+
# Create a highly compressed skeleton snapshot
|
|
27
|
+
eck-snapshot snapshot --skeleton
|
|
120
28
|
|
|
121
|
-
|
|
122
|
-
eck-snapshot
|
|
123
|
-
eck-snapshot --zh # OpenCode/GLM: generate AGENTS.md with Chinese protocol
|
|
29
|
+
# Create an incremental update (only changed/deleted files)
|
|
30
|
+
eck-snapshot update
|
|
124
31
|
```
|
|
125
32
|
|
|
126
|
-
|
|
33
|
+
## 🤖 AI Swarm Setup (GLM Z.AI)
|
|
127
34
|
|
|
128
|
-
|
|
35
|
+
eckSnapshot v6 acts as the bridge between your primary AI IDE (Claude Code or OpenCode) and a cost-effective GLM-4.7 worker swarm.
|
|
129
36
|
|
|
130
|
-
|
|
37
|
+
1. **Get an API Key:** Register at [Z.AI](https://z.ai) and set `export ZAI_API_KEY="your-key"`.
|
|
38
|
+
2. **Setup MCP Servers:**
|
|
39
|
+
```bash
|
|
40
|
+
eck-snapshot setup-mcp --both
|
|
41
|
+
```
|
|
42
|
+
3. **Initialize Project Manifests:**
|
|
43
|
+
```bash
|
|
44
|
+
# Generates smart instructions (CLAUDE.md / AGENTS.md)
|
|
45
|
+
eck-snapshot snapshot --jas # For Claude Code (Sonnet 4.5)
|
|
46
|
+
eck-snapshot snapshot --jaz # For OpenCode (GLM-4.7)
|
|
47
|
+
```
|
|
131
48
|
|
|
132
|
-
|
|
133
|
-
export ZAI_API_KEY="your-key"
|
|
134
|
-
eck-snapshot setup-mcp --both # Setup for Claude Code + OpenCode
|
|
135
|
-
```
|
|
49
|
+
## 📁 The `.eck/` Manifest Directory
|
|
136
50
|
|
|
137
|
-
|
|
51
|
+
eckSnapshot automatically maintains a `.eck/` directory in your project to provide deep context to AI agents:
|
|
52
|
+
- `CONTEXT.md` - High-level architecture (Auto-generated)
|
|
53
|
+
- `ENVIRONMENT.md` - Runtime specifics (Auto-generated)
|
|
54
|
+
- `ROADMAP.md` & `TECH_DEBT.md` - Strategic planning
|
|
55
|
+
- `RUNTIME_STATE.md` - Live port/process status
|
|
138
56
|
|
|
139
|
-
|
|
57
|
+
*Note: The tool automatically filters confidential files like `SERVER_ACCESS.md` from snapshots to ensure security.*
|
|
140
58
|
|
|
141
|
-
|
|
59
|
+
## 📈 Token Estimation
|
|
142
60
|
|
|
61
|
+
Train the local estimator to perfectly predict token counts for your specific project:
|
|
143
62
|
```bash
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
When using skeleton mode, the AI can request full content of specific files on demand:
|
|
148
|
-
|
|
149
|
-
```bash
|
|
150
|
-
eck-snapshot show src/auth.rs src/handlers/sync.rs
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
Useful for initial orientation in massive codebases, but full snapshots with profiles are usually more practical.
|
|
154
|
-
|
|
155
|
-
## Other Commands
|
|
63
|
+
# Manually push agent telemetry
|
|
64
|
+
eck-snapshot telemetry push
|
|
156
65
|
|
|
157
|
-
|
|
158
|
-
eck-snapshot
|
|
159
|
-
eck-snapshot prune <snapshot> # AI-powered snapshot size reduction
|
|
160
|
-
eck-snapshot doctor # Check project health
|
|
161
|
-
eck-snapshot env push # Encrypt and sync .eck/ config between machines
|
|
162
|
-
eck-snapshot env pull # Restore .eck/ config on another machine
|
|
66
|
+
# Sync global token weights from the Telemetry Hub
|
|
67
|
+
eck-snapshot telemetry sync-weights
|
|
163
68
|
```
|
|
164
69
|
|
|
165
|
-
## Changelog
|
|
166
|
-
|
|
167
|
-
### v5.8.6
|
|
168
|
-
- **Anti-Truncation Guard:** Every snapshot filename now includes its size in KB (e.g., `_1250kb.md`). The snapshot header instructs the AI to verify the payload size matches the filename and alert the user if the web UI truncated the file instead of hallucinating missing code.
|
|
169
|
-
- **Incremental snapshot filtering parity:** `eck-snapshot update` now applies the same file guards as full snapshots — hidden paths (`.idea/`, `.vscode/`), binary files, and glob patterns in `filesToIgnore` are all properly filtered out, preventing IDE config files or committed binaries from bloating update snapshots.
|
|
170
|
-
|
|
171
|
-
### v5.8.5
|
|
172
|
-
- Re-publish to fix missing README on npmjs.com.
|
|
173
|
-
|
|
174
|
-
### v5.8.4
|
|
175
|
-
- Fixed directory filtering in incremental snapshots. Paths like `web/build/app.js` are now correctly ignored when `build/` is in the ignore list.
|
|
176
|
-
|
|
177
|
-
### v5.8.3
|
|
178
|
-
- Optimized agent report formatting with clean Markdown to improve token efficiency.
|
|
179
|
-
- Fixed report injection order so it appears correctly after system instructions.
|
|
180
|
-
|
|
181
|
-
### v5.8.2
|
|
182
|
-
- Fixed agent report injection in incremental snapshots. The `AnswerToSA.md` file is now preserved on disk for manual debugging and uses an internal `[SYSTEM: EMBEDDED]` marker to prevent duplicate injections into future snapshots.
|
|
183
|
-
|
|
184
|
-
### v5.8.1
|
|
185
|
-
- Improved Android project parsing by ignoring boilerplate and vector graphics.
|
|
186
|
-
- Removed duplicate `ecksnapshot` MCP server and fixed JSON parsing in `update-auto`.
|
|
187
|
-
|
|
188
70
|
## License
|
|
189
|
-
|
|
190
|
-
MIT
|
|
71
|
+
MIT © xelth-com
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xelth/eck-snapshot",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "A powerful CLI tool to create and restore single-file text snapshots of Git repositories
|
|
3
|
+
"version": "6.0.0",
|
|
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
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
package/scripts/mcp-eck-core.js
CHANGED
|
@@ -24,8 +24,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
24
24
|
return {
|
|
25
25
|
tools: [
|
|
26
26
|
{
|
|
27
|
-
name: "
|
|
28
|
-
description: "
|
|
27
|
+
name: "eck_fail_task",
|
|
28
|
+
description: "Use this if you are stuck, blocked, or unable to complete the task. It saves your report to AnswerToSA.md and generates an emergency snapshot WITHOUT committing broken code. Do NOT use this if tests pass.",
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
status: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "Detailed explanation of why you are blocked, what you tried, and what the Architect should know."
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
required: ["status"]
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "eck_finish_task",
|
|
42
|
+
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. WARNING: USE ONLY ONCE PER TASK WHEN 100% FINISHED. Do NOT use this for intermediate saves or testing during your debugging loop.",
|
|
29
43
|
inputSchema: {
|
|
30
44
|
type: "object",
|
|
31
45
|
properties: {
|
|
@@ -46,6 +60,40 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
46
60
|
});
|
|
47
61
|
|
|
48
62
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
63
|
+
if (request.params.name === "eck_fail_task") {
|
|
64
|
+
const { status } = request.params.arguments;
|
|
65
|
+
const workDir = process.cwd();
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const answerDir = path.join(workDir, '.eck', 'lastsnapshot');
|
|
69
|
+
await fs.mkdir(answerDir, { recursive: true });
|
|
70
|
+
await fs.writeFile(
|
|
71
|
+
path.join(answerDir, 'AnswerToSA.md'),
|
|
72
|
+
`# Agent Report (BLOCKED/FAILED)\n\n${status}\n`,
|
|
73
|
+
'utf-8'
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const cliPath = path.join(PROJECT_ROOT, "index.js");
|
|
77
|
+
const { stdout } = await execa("node", [cliPath, "update-auto", "--fail"], { cwd: workDir, timeout: 120000 });
|
|
78
|
+
|
|
79
|
+
let result;
|
|
80
|
+
try {
|
|
81
|
+
result = JSON.parse(stdout);
|
|
82
|
+
} catch (e) {
|
|
83
|
+
return { content: [{ type: "text", text: `⚠️ Task aborted, but snapshot update returned invalid JSON: ${stdout}` }] };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
content: [{
|
|
88
|
+
type: "text",
|
|
89
|
+
text: `🚨 Task marked as FAILED.\n📝 AnswerToSA.md updated\n📸 Emergency Snapshot: ${result.snapshot_file} (${result.files_count} files)`
|
|
90
|
+
}]
|
|
91
|
+
};
|
|
92
|
+
} catch (error) {
|
|
93
|
+
return { content: [{ type: "text", text: `❌ Error: ${error.message}\n${error.stderr || ''}` }], isError: true };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
49
97
|
if (request.params.name === "eck_finish_task") {
|
|
50
98
|
const { status, message } = request.params.arguments;
|
|
51
99
|
const workDir = process.cwd();
|
|
@@ -106,14 +154,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
106
154
|
};
|
|
107
155
|
});
|
|
108
156
|
|
|
109
|
-
const transport = new StdioServerTransport();
|
|
110
|
-
await server.connect(transport);
|
|
111
|
-
|
|
112
|
-
// --- Graceful Shutdown Handler ---
|
|
113
|
-
process.stdout.on('error', (err) => {
|
|
114
|
-
if (err.code === 'EPIPE') {
|
|
115
|
-
process.exit(0);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
process.on('SIGINT', () => process.exit(0));
|
|
119
|
-
process.on('SIGTERM', () => process.exit(0));
|
|
157
|
+
const transport = new StdioServerTransport();
|
|
158
|
+
await server.connect(transport);
|
|
159
|
+
|
|
160
|
+
// --- Graceful Shutdown Handler ---
|
|
161
|
+
process.stdout.on('error', (err) => {
|
|
162
|
+
if (err.code === 'EPIPE') {
|
|
163
|
+
process.exit(0);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
process.on('SIGINT', () => process.exit(0));
|
|
167
|
+
process.on('SIGTERM', () => process.exit(0));
|
package/src/cli/cli.js
CHANGED
|
@@ -205,6 +205,7 @@ Quick --profile Examples:
|
|
|
205
205
|
.description('Create a delta snapshot of changed files since the last full snapshot')
|
|
206
206
|
.argument('[repoPath]', 'Path to the repository', process.cwd())
|
|
207
207
|
.option('--config <path>', 'Configuration file path')
|
|
208
|
+
.option('-f, --fail', 'Create an emergency snapshot without git commit')
|
|
208
209
|
.action(updateSnapshot);
|
|
209
210
|
|
|
210
211
|
// Auto/Silent Update command for Agents
|
|
@@ -212,6 +213,7 @@ Quick --profile Examples:
|
|
|
212
213
|
.command('update-auto')
|
|
213
214
|
.description('Silent update for AI agents (JSON output)')
|
|
214
215
|
.argument('[repoPath]', 'Path to the repository', process.cwd())
|
|
216
|
+
.option('-f, --fail', 'Create an emergency snapshot without git commit')
|
|
215
217
|
.action(updateSnapshotJson);
|
|
216
218
|
|
|
217
219
|
// Restore command
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import ora from 'ora';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
import isBinaryPath from 'is-binary-path';
|
|
6
|
-
import { getGitAnchor, getChangedFiles } from '../../utils/gitUtils.js';
|
|
7
|
-
import { loadSetupConfig } from '../../config.js';
|
|
8
|
-
import { readFileWithSizeCheck, parseSize, formatSize, matchesPattern, loadGitignore, generateTimestamp, getShortRepoName } from '../../utils/fileUtils.js';
|
|
9
|
-
import { detectProjectType, getProjectSpecificFiltering } from '../../utils/projectDetector.js';
|
|
10
|
-
import { execa } from 'execa';
|
|
11
|
-
import { fileURLToPath } from 'url';
|
|
12
|
-
import { pushTelemetry } from '../../utils/telemetry.js';
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import isBinaryPath from 'is-binary-path';
|
|
6
|
+
import { getGitAnchor, getChangedFiles } from '../../utils/gitUtils.js';
|
|
7
|
+
import { loadSetupConfig } from '../../config.js';
|
|
8
|
+
import { readFileWithSizeCheck, parseSize, formatSize, matchesPattern, loadGitignore, generateTimestamp, getShortRepoName } from '../../utils/fileUtils.js';
|
|
9
|
+
import { detectProjectType, getProjectSpecificFiltering } from '../../utils/projectDetector.js';
|
|
10
|
+
import { execa } from 'execa';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import { pushTelemetry } from '../../utils/telemetry.js';
|
|
13
|
+
import { syncTokenWeights } from '../../utils/tokenEstimator.js';
|
|
13
14
|
|
|
14
15
|
// Mirror the same hidden-path guard used in createSnapshot.js
|
|
15
16
|
function isHiddenPath(filePath) {
|
|
@@ -42,52 +43,95 @@ async function generateSnapshotContent(repoPath, changedFiles, anchor, config, g
|
|
|
42
43
|
let includedCount = 0;
|
|
43
44
|
const fileList = [];
|
|
44
45
|
|
|
45
|
-
// Include Agent Report if it exists and hasn't been embedded yet
|
|
46
|
-
let agentReport = null;
|
|
47
|
-
const reportPath = path.join(repoPath, '.eck', 'lastsnapshot', 'AnswerToSA.md');
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
// Include Agent Report if it exists and hasn't been embedded yet
|
|
47
|
+
let agentReport = null;
|
|
48
|
+
const reportPath = path.join(repoPath, '.eck', 'lastsnapshot', 'AnswerToSA.md');
|
|
49
|
+
const lockPath = path.join(repoPath, '.eck', 'lastsnapshot', 'AnswerToSA.lock');
|
|
50
|
+
try {
|
|
51
|
+
// Use atomic directory creation as a lock to prevent race conditions
|
|
52
|
+
await fs.mkdir(lockPath);
|
|
53
|
+
const reportContent = await fs.readFile(reportPath, 'utf-8');
|
|
54
|
+
|
|
55
|
+
if (!reportContent.includes('[SYSTEM: EMBEDDED]')) {
|
|
56
|
+
agentReport = reportContent;
|
|
57
|
+
|
|
58
|
+
// Immediately mark as embedded to release the race window
|
|
59
|
+
await fs.appendFile(reportPath, '\n\n[SYSTEM: EMBEDDED]\n', 'utf-8');
|
|
60
|
+
|
|
61
|
+
// Auto-Journaling: prepend agent report to JOURNAL.md
|
|
62
|
+
const journalPath = path.join(repoPath, '.eck', 'JOURNAL.md');
|
|
63
|
+
try {
|
|
64
|
+
const dateStr = new Date().toISOString().split('T')[0];
|
|
65
|
+
const journalEntry = `## ${dateStr} — Agent Report\n\n${reportContent.trim()}\n`;
|
|
66
|
+
|
|
67
|
+
let existingJournal = '';
|
|
68
|
+
try {
|
|
69
|
+
existingJournal = await fs.readFile(journalPath, 'utf-8');
|
|
70
|
+
} catch (e) { /* might not exist */ }
|
|
71
|
+
|
|
72
|
+
const insertPos = existingJournal.indexOf('\n## ');
|
|
73
|
+
if (insertPos !== -1) {
|
|
74
|
+
const newJournal = existingJournal.slice(0, insertPos) + '\n\n' + journalEntry + existingJournal.slice(insertPos);
|
|
75
|
+
await fs.writeFile(journalPath, newJournal, 'utf-8');
|
|
76
|
+
} else {
|
|
77
|
+
await fs.writeFile(journalPath, (existingJournal ? existingJournal + '\n\n' : '') + journalEntry + '\n', 'utf-8');
|
|
78
|
+
}
|
|
79
|
+
} catch (je) {
|
|
80
|
+
console.warn('Could not auto-update JOURNAL.md', je.message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
await fs.rmdir(lockPath);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
// File not found or locked by another process
|
|
86
|
+
try { await fs.rmdir(lockPath); } catch (_) {}
|
|
87
|
+
}
|
|
55
88
|
|
|
56
89
|
const cleanDirsToIgnore = (config.dirsToIgnore || []).map(d => d.replace(/\/$/, ''));
|
|
57
90
|
|
|
58
|
-
for (const filePath of changedFiles) {
|
|
59
|
-
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
60
|
-
|
|
61
|
-
// Skip hidden paths (.idea/, .vscode/, etc.) — mirrors createSnapshot.js
|
|
62
|
-
if (isHiddenPath(normalizedPath)) continue;
|
|
63
|
-
|
|
64
|
-
// Skip binary files — mirrors createSnapshot.js
|
|
65
|
-
if (isBinaryPath(filePath)) continue;
|
|
66
|
-
|
|
67
|
-
const pathParts = normalizedPath.split('/');
|
|
68
|
-
let isIgnoredDir = false;
|
|
69
|
-
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
70
|
-
if (cleanDirsToIgnore.includes(pathParts[i])) {
|
|
71
|
-
isIgnoredDir = true;
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (isIgnoredDir) continue;
|
|
76
|
-
|
|
77
|
-
const fileExt = path.extname(filePath);
|
|
78
|
-
// Use matchesPattern (glob support) instead of exact includes() — mirrors createSnapshot.js
|
|
79
|
-
if (config.filesToIgnore && matchesPattern(normalizedPath, config.filesToIgnore)) continue;
|
|
80
|
-
if (fileExt && config.extensionsToIgnore?.includes(fileExt)) continue;
|
|
81
|
-
if (gitignore.ignores(normalizedPath)) continue;
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
const fullPath = path.join(repoPath, filePath);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
for (const filePath of changedFiles) {
|
|
92
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
93
|
+
|
|
94
|
+
// Skip hidden paths (.idea/, .vscode/, etc.) — mirrors createSnapshot.js
|
|
95
|
+
if (isHiddenPath(normalizedPath)) continue;
|
|
96
|
+
|
|
97
|
+
// Skip binary files — mirrors createSnapshot.js
|
|
98
|
+
if (isBinaryPath(filePath)) continue;
|
|
99
|
+
|
|
100
|
+
const pathParts = normalizedPath.split('/');
|
|
101
|
+
let isIgnoredDir = false;
|
|
102
|
+
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
103
|
+
if (cleanDirsToIgnore.includes(pathParts[i])) {
|
|
104
|
+
isIgnoredDir = true;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (isIgnoredDir) continue;
|
|
109
|
+
|
|
110
|
+
const fileExt = path.extname(filePath);
|
|
111
|
+
// Use matchesPattern (glob support) instead of exact includes() — mirrors createSnapshot.js
|
|
112
|
+
if (config.filesToIgnore && matchesPattern(normalizedPath, config.filesToIgnore)) continue;
|
|
113
|
+
if (fileExt && config.extensionsToIgnore?.includes(fileExt)) continue;
|
|
114
|
+
if (gitignore.ignores(normalizedPath)) continue;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const fullPath = path.join(repoPath, filePath);
|
|
118
|
+
|
|
119
|
+
// Explicitly check if file was deleted
|
|
120
|
+
try {
|
|
121
|
+
await fs.access(fullPath);
|
|
122
|
+
} catch (accessErr) {
|
|
123
|
+
contentOutput += `--- File: /${normalizedPath} ---\n\n[FILE DELETED]\n\n`;
|
|
124
|
+
fileList.push(`- ${normalizedPath} (Deleted)`);
|
|
125
|
+
includedCount++;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const content = await readFileWithSizeCheck(fullPath, parseSize(config.maxFileSize));
|
|
130
|
+
contentOutput += `--- File: /${normalizedPath} ---\n\n${content}\n\n`;
|
|
131
|
+
fileList.push(`- ${normalizedPath} (Modified/Added)`);
|
|
132
|
+
includedCount++;
|
|
133
|
+
} catch (e) { /* Skip */ }
|
|
134
|
+
}
|
|
91
135
|
|
|
92
136
|
// Load Template
|
|
93
137
|
const templatePath = path.join(__dirname, '../../templates/update-prompt.template.md');
|
|
@@ -122,17 +166,22 @@ export async function updateSnapshot(repoPath, options) {
|
|
|
122
166
|
}
|
|
123
167
|
|
|
124
168
|
// Auto-commit any uncommitted changes so they appear in the diff
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
169
|
+
let didCommit = false;
|
|
170
|
+
if (!options.fail) {
|
|
171
|
+
didCommit = await autoCommit(repoPath);
|
|
172
|
+
if (didCommit) {
|
|
173
|
+
spinner.info('Auto-committed uncommitted changes.');
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
spinner.info('Fail flag passed: skipping auto-commit.');
|
|
129
177
|
}
|
|
178
|
+
spinner.start('Generating update snapshot...');
|
|
130
179
|
|
|
131
|
-
const changedFiles = await getChangedFiles(repoPath, anchor);
|
|
132
|
-
if (changedFiles.length === 0) {
|
|
133
|
-
spinner.succeed('No changes detected since last full snapshot.');
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
180
|
+
const changedFiles = await getChangedFiles(repoPath, anchor, options.fail);
|
|
181
|
+
if (changedFiles.length === 0) {
|
|
182
|
+
spinner.succeed('No changes detected since last full snapshot.');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
136
185
|
|
|
137
186
|
const setupConfig = await loadSetupConfig();
|
|
138
187
|
let config = { ...setupConfig.fileFiltering, ...setupConfig.performance, ...options };
|
|
@@ -216,7 +265,7 @@ export async function updateSnapshot(repoPath, options) {
|
|
|
216
265
|
}
|
|
217
266
|
|
|
218
267
|
// New Silent/JSON command for Agents
|
|
219
|
-
export async function updateSnapshotJson(repoPath) {
|
|
268
|
+
export async function updateSnapshotJson(repoPath, options = {}) {
|
|
220
269
|
try {
|
|
221
270
|
const anchor = await getGitAnchor(repoPath);
|
|
222
271
|
if (!anchor) {
|
|
@@ -225,9 +274,11 @@ export async function updateSnapshotJson(repoPath) {
|
|
|
225
274
|
}
|
|
226
275
|
|
|
227
276
|
// Auto-commit any uncommitted changes
|
|
228
|
-
|
|
277
|
+
if (!options.fail) {
|
|
278
|
+
await autoCommit(repoPath);
|
|
279
|
+
}
|
|
229
280
|
|
|
230
|
-
const changedFiles = await getChangedFiles(repoPath, anchor);
|
|
281
|
+
const changedFiles = await getChangedFiles(repoPath, anchor, !!options.fail);
|
|
231
282
|
if (changedFiles.length === 0) {
|
|
232
283
|
console.log(JSON.stringify({ status: "no_changes", message: "No changes detected" }));
|
|
233
284
|
return;
|
|
@@ -294,15 +345,16 @@ export async function updateSnapshotJson(repoPath) {
|
|
|
294
345
|
}
|
|
295
346
|
// --------------------------------------------
|
|
296
347
|
|
|
297
|
-
console.log(JSON.stringify({
|
|
298
|
-
status: "success",
|
|
299
|
-
snapshot_file: `.eck/snapshots/${outputFilename}`,
|
|
300
|
-
files_count: includedCount,
|
|
301
|
-
timestamp: timestamp
|
|
302
|
-
}));
|
|
303
|
-
|
|
304
|
-
// Auto-push telemetry (fire and forget so it doesn't break JSON output)
|
|
305
|
-
pushTelemetry(repoPath, true).catch(() => {});
|
|
348
|
+
console.log(JSON.stringify({
|
|
349
|
+
status: "success",
|
|
350
|
+
snapshot_file: `.eck/snapshots/${outputFilename}`,
|
|
351
|
+
files_count: includedCount,
|
|
352
|
+
timestamp: timestamp
|
|
353
|
+
}));
|
|
354
|
+
|
|
355
|
+
// Auto-push telemetry and sync weights (fire and forget so it doesn't break JSON output)
|
|
356
|
+
pushTelemetry(repoPath, true).catch(() => {});
|
|
357
|
+
syncTokenWeights(true).catch(() => {});
|
|
306
358
|
|
|
307
359
|
} catch (error) {
|
|
308
360
|
console.log(JSON.stringify({ status: "error", message: error.message }));
|
|
@@ -4,18 +4,25 @@
|
|
|
4
4
|
You are an Expert Developer. The architecture is already decided. Your job is to **execute**, **fix**, and **polish**.
|
|
5
5
|
|
|
6
6
|
## DEFINITION OF DONE (CRITICAL)
|
|
7
|
-
When
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
When task is complete, you must report back and sync context.
|
|
8
|
+
|
|
9
|
+
**OPTION A: Using MCP Tool (Recommended)**
|
|
10
|
+
Call the \`eck_finish_task\` tool. Pass your detailed markdown report into the \`status\` argument.
|
|
11
|
+
- The tool will automatically write the report to \`AnswerToSA.md\`, commit, and generate a snapshot.
|
|
12
|
+
- **DO NOT** manually write to \`AnswerToSA.md\` with your file editing tools (it will fail safety checks).
|
|
13
|
+
- **WARNING: USE ONLY ONCE.** Do not use \`eck_finish_task\` for intermediate testing. It spams snapshot history.
|
|
14
|
+
|
|
15
|
+
**OPTION B: Manual CLI (Fallback)**
|
|
16
|
+
If the MCP tool is unavailable:
|
|
17
|
+
1. **READ** \`.eck/lastsnapshot/AnswerToSA.md\` using your \`Read\` tool (REQUIRED by safety rules before overwriting).
|
|
18
|
+
2. **WRITE** your report to that file.
|
|
19
|
+
3. Run \`eck-snapshot update\` in terminal.
|
|
20
|
+
|
|
21
|
+
## PROJECT CONTEXT (.eck DIRECTORY)
|
|
22
|
+
The `.eck/` directory contains critical project documentation. **Before starting your task, you MUST:**
|
|
23
|
+
1. List the files in the `.eck/` directory.
|
|
24
|
+
2. Read any files that might be relevant to your task based on their names (e.g., `CONTEXT.md`, `TECH_DEBT.md`, `OPERATIONS.md`).
|
|
25
|
+
3. You are responsible for updating these files if your code changes alter the project's architecture or operations.
|
|
19
26
|
|
|
20
27
|
## CONTEXT
|
|
21
28
|
- The GLM ZAI swarm might have struggled or produced code that needs refinement.
|
|
@@ -23,7 +30,9 @@ When the task is complete:
|
|
|
23
30
|
- You have full permission to edit files directly.
|
|
24
31
|
|
|
25
32
|
## WORKFLOW
|
|
26
|
-
1.
|
|
27
|
-
2.
|
|
28
|
-
3.
|
|
29
|
-
4.
|
|
33
|
+
1. Check the `.eck/RUNTIME_STATE.md` and verify actual running processes.
|
|
34
|
+
2. Read the code. If the Architect's hypothesis is wrong, discard it and find the real bug.
|
|
35
|
+
3. Fix the bugs / Implement the feature.
|
|
36
|
+
4. Verify functionality manually via browser/curl/logs/DB checks.
|
|
37
|
+
5. **Loop:** If verification fails, fix it immediately. Do not ask for permission.
|
|
38
|
+
6. **Blocked?** Use the `eck_fail_task` tool to abort safely without committing broken code.
|