@xelth/eck-snapshot 4.0.0 → 4.2.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/package.json +13 -3
- package/setup.json +57 -35
- package/src/cli/cli.js +81 -134
- package/src/cli/commands/autoDocs.js +1 -32
- package/src/cli/commands/createSnapshot.js +338 -198
- package/src/cli/commands/doctor.js +60 -0
- package/src/cli/commands/setupGemini.js +1 -1
- package/src/cli/commands/setupGemini.test.js +1 -1
- package/src/cli/commands/showFile.js +39 -0
- package/src/cli/commands/updateSnapshot.js +75 -0
- package/src/config.js +44 -0
- package/src/core/skeletonizer.js +201 -0
- package/src/services/claudeCliService.js +5 -0
- package/src/templates/agent-prompt.template.md +104 -7
- package/src/templates/architect-prompt.template.md +112 -23
- package/src/templates/multiAgent.md +40 -86
- package/src/templates/skeleton-instruction.md +16 -0
- package/src/templates/update-prompt.template.md +19 -0
- package/src/utils/aiHeader.js +373 -147
- package/src/utils/eckProtocolParser.js +221 -0
- package/src/utils/fileUtils.js +212 -175
- package/src/utils/gitUtils.js +44 -0
- package/src/utils/tokenEstimator.js +4 -1
- package/src/cli/commands/askGpt.js +0 -29
- package/src/services/authService.js +0 -20
- package/src/services/dispatcherService.js +0 -33
- package/src/services/gptService.js +0 -302
- package/src/services/gptService.test.js +0 -120
- package/src/templates/vectorMode.md +0 -22
|
@@ -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
|
+
};
|