@xelth/eck-snapshot 4.2.4 → 5.4.1
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.
Potentially problematic release.
This version of @xelth/eck-snapshot might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/index.js +14 -0
- package/package.json +64 -9
- package/scripts/mcp-eck-core.js +101 -0
- package/scripts/mcp-glm-zai-worker.mjs +243 -0
- package/scripts/verify_changes.js +68 -0
- package/setup.json +845 -0
- package/src/cli/cli.js +369 -0
- package/src/cli/commands/claudeSettings.js +93 -0
- package/src/cli/commands/consilium.js +86 -0
- package/src/cli/commands/createSnapshot.js +917 -0
- package/src/cli/commands/detectProfiles.js +98 -0
- package/src/cli/commands/detectProject.js +112 -0
- package/src/cli/commands/doctor.js +60 -0
- package/src/cli/commands/envSync.js +319 -0
- package/src/cli/commands/generateProfileGuide.js +144 -0
- package/src/cli/commands/pruneSnapshot.js +106 -0
- package/src/cli/commands/restoreSnapshot.js +173 -0
- package/src/cli/commands/setupGemini.js +149 -0
- package/src/cli/commands/setupGemini.test.js +115 -0
- package/src/cli/commands/setupMcp.js +269 -0
- package/src/cli/commands/showFile.js +39 -0
- package/src/cli/commands/trainTokens.js +38 -0
- package/src/cli/commands/updateSnapshot.js +247 -0
- package/src/config.js +115 -0
- package/src/core/skeletonizer.js +201 -0
- package/src/mcp-server/index.js +211 -0
- package/src/services/claudeCliService.js +626 -0
- package/src/services/claudeCliService.test.js +267 -0
- package/src/templates/agent-prompt.template.md +43 -0
- package/src/templates/architect-prompt.template.md +164 -0
- package/src/templates/claude-code/README.md +105 -0
- package/src/templates/claude-code/mcp-config-template.json +11 -0
- package/src/templates/claude-code/mcp-server-template.js +206 -0
- package/src/templates/claude-code/settings-claude.json +1 -0
- package/src/templates/envScanRequest.md +4 -0
- package/src/templates/gitWorkflow.md +32 -0
- package/src/templates/multiAgent.md +118 -0
- package/src/templates/opencode/coder.template.md +22 -0
- package/src/templates/opencode/junior-architect.template.md +85 -0
- package/src/templates/skeleton-instruction.md +16 -0
- package/src/templates/update-prompt.template.md +19 -0
- package/src/utils/aiHeader.js +678 -0
- package/src/utils/claudeMdGenerator.js +148 -0
- package/src/utils/eckProtocolParser.js +221 -0
- package/src/utils/fileUtils.js +1017 -0
- package/src/utils/gitUtils.js +51 -0
- package/src/utils/opencodeAgentsGenerator.js +271 -0
- package/src/utils/projectDetector.js +704 -0
- package/src/utils/tokenEstimator.js +201 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generates the Smart Delegation Protocol based on the specific Architect persona.
|
|
6
|
+
*/
|
|
7
|
+
function getArchitectInstructions(modelName, tree) {
|
|
8
|
+
const isOpus = modelName.includes('Opus');
|
|
9
|
+
|
|
10
|
+
return `# 🧠 ROLE: Junior Architect (${modelName})
|
|
11
|
+
|
|
12
|
+
## 1. PROJECT CONTEXT & MEMORY
|
|
13
|
+
You are working inside the repository.
|
|
14
|
+
- **Source of Truth:** The file system is your source of truth.
|
|
15
|
+
- **Documentation:** The \`.eck/\` directory contains project context. READ filenames to understand what is available.
|
|
16
|
+
- **Directory Structure:**
|
|
17
|
+
\`\`\`
|
|
18
|
+
${tree}
|
|
19
|
+
\`\`\`
|
|
20
|
+
|
|
21
|
+
## 2. SMART DELEGATION PROTOCOL (TOKEN ECONOMY)
|
|
22
|
+
|
|
23
|
+
### A. Token Efficiency: When NOT to Delegate
|
|
24
|
+
**DO NOT delegate tasks where explanation costs more tokens than execution.**
|
|
25
|
+
* *Examples:*
|
|
26
|
+
- Reading a config file → Just use \`Read\` tool (1 tool call vs explaining to GLM Z.AI)
|
|
27
|
+
- Checking if file exists → Use \`Bash test -f\` or \`Read\`
|
|
28
|
+
- Fixing a typo → Use \`Edit\` tool directly
|
|
29
|
+
- Writing < 50 lines of glue code
|
|
30
|
+
* **Rule:** If you can solve it in 1-2 tool calls, DO IT YOURSELF.
|
|
31
|
+
|
|
32
|
+
### B. Heavy Lifting (DELEGATE TO GLM Z.AI)
|
|
33
|
+
For bulk work where delegation saves YOUR expensive context:
|
|
34
|
+
* *Examples:*
|
|
35
|
+
- Implementing complex business logic (> 100 lines)
|
|
36
|
+
- Refactoring entire modules
|
|
37
|
+
- Writing comprehensive test suites
|
|
38
|
+
- Generating boilerplate code
|
|
39
|
+
* **Action:** Use \`glm_zai_backend\`, \`glm_zai_frontend\`, \`glm_zai_qa\`, or \`glm_zai_refactor\`.
|
|
40
|
+
|
|
41
|
+
## 3. DEFINITION OF DONE (CRITICAL)
|
|
42
|
+
When you have completed your coding task and verified it works:
|
|
43
|
+
1. **DO NOT** run \`git commit\` manually.
|
|
44
|
+
2. **DO NOT** just say "I'm done".
|
|
45
|
+
3. **YOU MUST use the tool \`eck_finish_task\`**.
|
|
46
|
+
- This tool handles the commit AND automatically updates your context snapshot.
|
|
47
|
+
- Using this tool is the ONLY way to successfully close a task.
|
|
48
|
+
|
|
49
|
+
## 4. THE RALPH WIGGUM PROTOCOL (AUTONOMOUS LOOPS)
|
|
50
|
+
**Core Directive:** You are "deterministically persistent". Failures are expected, giving up is not.
|
|
51
|
+
|
|
52
|
+
1. **Iterative Correction:**
|
|
53
|
+
* If a build fails or tests turn red: **DO NOT STOP**.
|
|
54
|
+
* **Read** the error message.
|
|
55
|
+
* **Think** about the cause.
|
|
56
|
+
* **Fix** the code.
|
|
57
|
+
* **Retry** the verification command.
|
|
58
|
+
* *Repeat this loop up to 3-4 times.*
|
|
59
|
+
|
|
60
|
+
2. **Intelligent Retry (GLM Z.AI Supervision):**
|
|
61
|
+
* If a GLM Z.AI worker produces bad code:
|
|
62
|
+
* **DON'T** repeat the same prompt.
|
|
63
|
+
* **Analyze WHY** it failed (missing context? wrong import?).
|
|
64
|
+
* **Guide** the worker: "Previous attempt failed because X. Try again using pattern Y."
|
|
65
|
+
* **Takeover:** If GLM Z.AI fails twice, **DO IT YOURSELF**.
|
|
66
|
+
|
|
67
|
+
3. **Definition of Done:**
|
|
68
|
+
* A task is ONLY done when the verification command (e.g., \`npm test\`) exits with code 0.
|
|
69
|
+
* If you cannot achieve green tests after max retries, produce a detailed report of *why* it is blocked.
|
|
70
|
+
|
|
71
|
+
## 5. REPORTING PROTOCOL
|
|
72
|
+
At the end of your task, you **MUST** create or overwrite the file \`.eck/lastsnapshot/AnswerToSA.md\` BEFORE calling \`eck_finish_task\`.
|
|
73
|
+
This file communicates your results back to the Senior Architect (Gemini).
|
|
74
|
+
|
|
75
|
+
**Format for .eck/lastsnapshot/AnswerToSA.md:**
|
|
76
|
+
\`\`\`markdown
|
|
77
|
+
# Report: [Task Name]
|
|
78
|
+
**Status:** [SUCCESS / BLOCKED / FAILED]
|
|
79
|
+
**Changes:**
|
|
80
|
+
- Modified X
|
|
81
|
+
- Created Y
|
|
82
|
+
**Verification:**
|
|
83
|
+
- Ran test Z -> Passed
|
|
84
|
+
**Next Steps / Questions:**
|
|
85
|
+
- [What should the Architect do next?]
|
|
86
|
+
\`\`\`
|
|
87
|
+
|
|
88
|
+
## 6. OPERATIONAL RULES
|
|
89
|
+
- **Commits:** Use the \`eck_finish_task\` tool for committing and updating context.
|
|
90
|
+
- **Manifests:** If you see [STUB] in .eck/ files, update them.
|
|
91
|
+
- **Reporting:** NEVER finish a session without writing \`.eck/lastsnapshot/AnswerToSA.md\` and calling \`eck_finish_task\`.
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const CODER_INSTRUCTIONS = `# 🛠️ ROLE: Expert Developer (The Fixer)
|
|
96
|
+
|
|
97
|
+
## CORE DIRECTIVE
|
|
98
|
+
You are an Expert Developer. The architecture is already decided. Your job is to **execute**, **fix**, and **polish**.
|
|
99
|
+
|
|
100
|
+
## DEFINITION OF DONE (CRITICAL)
|
|
101
|
+
When the task is complete:
|
|
102
|
+
1. **UPDATE** the \`.eck/lastsnapshot/AnswerToSA.md\` file with your status.
|
|
103
|
+
2. **Use the \`eck_finish_task\` tool** to commit and sync context.
|
|
104
|
+
3. **DO NOT** use raw git commands for the final commit.
|
|
105
|
+
|
|
106
|
+
## CONTEXT
|
|
107
|
+
- The GLM Z.AI worker might have struggled or produced code that needs refinement.
|
|
108
|
+
- You are here to solve the hard problems manually.
|
|
109
|
+
- You have full permission to edit files directly.
|
|
110
|
+
|
|
111
|
+
## WORKFLOW
|
|
112
|
+
1. Read the code.
|
|
113
|
+
2. Fix the bugs / Implement the feature.
|
|
114
|
+
3. Verify functionality (Run tests!).
|
|
115
|
+
4. **Loop:** If verification fails, fix it immediately. Do not ask for permission.
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Generates and writes the CLAUDE.md file based on the selected mode.
|
|
120
|
+
*/
|
|
121
|
+
export async function updateClaudeMd(repoPath, mode, tree, confidentialFiles = []) {
|
|
122
|
+
let content = '';
|
|
123
|
+
|
|
124
|
+
if (mode === 'jas') {
|
|
125
|
+
content = getArchitectInstructions('Sonnet 4.5', tree);
|
|
126
|
+
} else if (mode === 'jao') {
|
|
127
|
+
content = getArchitectInstructions('Opus 4.5', tree);
|
|
128
|
+
} else if (mode === 'jag') {
|
|
129
|
+
content = getArchitectInstructions('Gemini 3 Pro', tree);
|
|
130
|
+
} else {
|
|
131
|
+
// Default coder mode (or if flags are missing)
|
|
132
|
+
content = CODER_INSTRUCTIONS;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Append Confidential Files Reference
|
|
136
|
+
if (confidentialFiles.length > 0) {
|
|
137
|
+
content += '\n\n## 🔐 Access & Credentials\n';
|
|
138
|
+
content += 'The following confidential files are available locally but excluded from snapshots/tree:\n';
|
|
139
|
+
for (const file of confidentialFiles) {
|
|
140
|
+
content += `- \`${file}\`\n`;
|
|
141
|
+
}
|
|
142
|
+
content += '> **Note:** Read these files only when strictly necessary.\n';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const claudeMdPath = path.join(repoPath, 'CLAUDE.md');
|
|
146
|
+
await fs.writeFile(claudeMdPath, content, 'utf-8');
|
|
147
|
+
console.log(`📝 Updated CLAUDE.md for role: **${mode.toUpperCase()}** (Ralph Loop + GLM Z.AI Protocol Active)`);
|
|
148
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eck-Protocol v2 Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses hybrid Markdown/XML/JSON format for agent communication.
|
|
5
|
+
* This is a pure text parser - no shell commands are involved.
|
|
6
|
+
*
|
|
7
|
+
* Format specification:
|
|
8
|
+
* - Markdown for human-readable analysis/thinking
|
|
9
|
+
* - XML-like <file> tags for code changes (with code in standard fences)
|
|
10
|
+
* - JSON in fenced blocks for structured metadata
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parses Eck-Protocol v2 response from an agent.
|
|
15
|
+
* @param {string} text - Raw text response from the agent
|
|
16
|
+
* @returns {object} Parsed structure with thought, files, and metadata
|
|
17
|
+
*/
|
|
18
|
+
export function parseEckResponse(text) {
|
|
19
|
+
const result = {
|
|
20
|
+
thought: '',
|
|
21
|
+
files: [],
|
|
22
|
+
metadata: {},
|
|
23
|
+
raw: text
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (!text || typeof text !== 'string') {
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
result.files = extractFiles(text);
|
|
31
|
+
result.metadata = extractMetadata(text);
|
|
32
|
+
result.thought = extractThought(text);
|
|
33
|
+
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Extracts file changes from <file> tags.
|
|
39
|
+
* @param {string} text - Raw text
|
|
40
|
+
* @returns {Array<{path: string, action: string, content: string}>}
|
|
41
|
+
*/
|
|
42
|
+
function extractFiles(text) {
|
|
43
|
+
const files = [];
|
|
44
|
+
|
|
45
|
+
// Pattern: <file path="..." action="..."> content </file>
|
|
46
|
+
const fileRegex = /<file\s+path=["']([^"']+)["']\s+action=["']([^"']+)["']>\s*([\s\S]*?)\s*<\/file>/gi;
|
|
47
|
+
|
|
48
|
+
let match;
|
|
49
|
+
while ((match = fileRegex.exec(text)) !== null) {
|
|
50
|
+
const path = match[1];
|
|
51
|
+
const action = match[2];
|
|
52
|
+
let content = match[3].trim();
|
|
53
|
+
|
|
54
|
+
// Strip markdown code fences if present
|
|
55
|
+
const fenceMatch = content.match(/^```[\w]*\s*\n?([\s\S]*?)\n?\s*```$/);
|
|
56
|
+
if (fenceMatch) {
|
|
57
|
+
content = fenceMatch[1];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
content = content.replace(/\r\n/g, '\n');
|
|
61
|
+
files.push({ path, action, content });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return files;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Extracts metadata from JSON fenced blocks.
|
|
69
|
+
* @param {string} text - Raw text
|
|
70
|
+
* @returns {object} Parsed metadata object
|
|
71
|
+
*/
|
|
72
|
+
function extractMetadata(text) {
|
|
73
|
+
let metadata = {};
|
|
74
|
+
|
|
75
|
+
// Try: ## Metadata section with JSON block
|
|
76
|
+
const metadataSectionMatch = text.match(/##\s*Metadata\s*\n+```json\s*\n([\s\S]*?)\n```/i);
|
|
77
|
+
if (metadataSectionMatch) {
|
|
78
|
+
try {
|
|
79
|
+
metadata = JSON.parse(metadataSectionMatch[1].trim());
|
|
80
|
+
return metadata;
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.warn('Failed to parse Metadata JSON:', e.message);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Try: <journal> tag with JSON
|
|
87
|
+
const journalMatch = text.match(/<journal>\s*```json\s*\n?([\s\S]*?)\n?\s*```\s*<\/journal>/i);
|
|
88
|
+
if (journalMatch) {
|
|
89
|
+
try {
|
|
90
|
+
metadata.journal = JSON.parse(journalMatch[1].trim());
|
|
91
|
+
return metadata;
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.warn('Failed to parse journal JSON:', e.message);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Try: simple <journal type="..." scope="...">summary</journal>
|
|
98
|
+
const simpleJournalMatch = text.match(/<journal\s+type=["']([^"']+)["']\s+scope=["']([^"']+)["']>([^<]+)<\/journal>/i);
|
|
99
|
+
if (simpleJournalMatch) {
|
|
100
|
+
metadata.journal = {
|
|
101
|
+
type: simpleJournalMatch[1],
|
|
102
|
+
scope: simpleJournalMatch[2],
|
|
103
|
+
summary: simpleJournalMatch[3].trim()
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return metadata;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Extracts the thought/analysis section.
|
|
112
|
+
* @param {string} text - Raw text
|
|
113
|
+
* @returns {string} The thought/analysis content
|
|
114
|
+
*/
|
|
115
|
+
function extractThought(text) {
|
|
116
|
+
const changesIndex = text.search(/##\s*Changes/i);
|
|
117
|
+
const fileIndex = text.search(/<file\s+path=/i);
|
|
118
|
+
|
|
119
|
+
let endIndex = text.length;
|
|
120
|
+
if (changesIndex !== -1 && fileIndex !== -1) {
|
|
121
|
+
endIndex = Math.min(changesIndex, fileIndex);
|
|
122
|
+
} else if (changesIndex !== -1) {
|
|
123
|
+
endIndex = changesIndex;
|
|
124
|
+
} else if (fileIndex !== -1) {
|
|
125
|
+
endIndex = fileIndex;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let thought = text.substring(0, endIndex).trim();
|
|
129
|
+
thought = thought.replace(/^#\s*(Analysis|Thinking|Plan)\s*\n*/i, '').trim();
|
|
130
|
+
return thought;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Validates if a response contains valid Eck-Protocol v2 structure.
|
|
135
|
+
* @param {string} text - Raw text to validate
|
|
136
|
+
* @returns {{valid: boolean, hasFiles: boolean, hasMetadata: boolean, errors: string[]}}
|
|
137
|
+
*/
|
|
138
|
+
export function validateEckResponse(text) {
|
|
139
|
+
const result = {
|
|
140
|
+
valid: true,
|
|
141
|
+
hasFiles: false,
|
|
142
|
+
hasMetadata: false,
|
|
143
|
+
errors: []
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
if (!text || typeof text !== 'string') {
|
|
147
|
+
result.valid = false;
|
|
148
|
+
result.errors.push('Response is empty or not a string');
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const hasFileTags = /<file\s+path=/.test(text);
|
|
153
|
+
if (hasFileTags) {
|
|
154
|
+
result.hasFiles = true;
|
|
155
|
+
const openTags = (text.match(/<file\s+/g) || []).length;
|
|
156
|
+
const closeTags = (text.match(/<\/file>/g) || []).length;
|
|
157
|
+
if (openTags !== closeTags) {
|
|
158
|
+
result.valid = false;
|
|
159
|
+
result.errors.push(`Mismatched file tags: ${openTags} opening, ${closeTags} closing`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const hasMetadata = /##\s*Metadata|<journal/i.test(text);
|
|
164
|
+
if (hasMetadata) {
|
|
165
|
+
result.hasMetadata = true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Parses response with fallback to legacy JSON format.
|
|
173
|
+
* @param {string} text - Raw response text
|
|
174
|
+
* @returns {object} Parsed result with format indicator
|
|
175
|
+
*/
|
|
176
|
+
export function parseWithFallback(text) {
|
|
177
|
+
const validation = validateEckResponse(text);
|
|
178
|
+
|
|
179
|
+
if (validation.hasFiles || validation.hasMetadata) {
|
|
180
|
+
const parsed = parseEckResponse(text);
|
|
181
|
+
return { format: 'eck-v2', ...parsed };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Fallback: try to parse as JSON
|
|
185
|
+
try {
|
|
186
|
+
const jsonMatch = text.match(/```json\s*\n([\s\S]*?)\n```/);
|
|
187
|
+
if (jsonMatch) {
|
|
188
|
+
const jsonData = JSON.parse(jsonMatch[1]);
|
|
189
|
+
return {
|
|
190
|
+
format: 'legacy-json',
|
|
191
|
+
thought: text.split('```json')[0].trim(),
|
|
192
|
+
data: jsonData,
|
|
193
|
+
files: [],
|
|
194
|
+
metadata: {}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const jsonData = JSON.parse(text.trim());
|
|
199
|
+
return {
|
|
200
|
+
format: 'pure-json',
|
|
201
|
+
thought: '',
|
|
202
|
+
data: jsonData,
|
|
203
|
+
files: [],
|
|
204
|
+
metadata: {}
|
|
205
|
+
};
|
|
206
|
+
} catch (e) {
|
|
207
|
+
return {
|
|
208
|
+
format: 'plain-text',
|
|
209
|
+
thought: text,
|
|
210
|
+
files: [],
|
|
211
|
+
metadata: {},
|
|
212
|
+
raw: text
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export default {
|
|
218
|
+
parseEckResponse,
|
|
219
|
+
validateEckResponse,
|
|
220
|
+
parseWithFallback
|
|
221
|
+
};
|