@xelth/eck-snapshot 2.2.0 → 4.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/LICENSE +21 -0
- package/README.md +119 -225
- package/index.js +14 -776
- package/package.json +25 -7
- package/setup.json +805 -0
- package/src/cli/cli.js +427 -0
- package/src/cli/commands/askGpt.js +29 -0
- package/src/cli/commands/autoDocs.js +150 -0
- package/src/cli/commands/consilium.js +86 -0
- package/src/cli/commands/createSnapshot.js +601 -0
- package/src/cli/commands/detectProfiles.js +98 -0
- package/src/cli/commands/detectProject.js +112 -0
- package/src/cli/commands/generateProfileGuide.js +91 -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/trainTokens.js +38 -0
- package/src/config.js +81 -0
- package/src/services/authService.js +20 -0
- package/src/services/claudeCliService.js +621 -0
- package/src/services/claudeCliService.test.js +267 -0
- package/src/services/dispatcherService.js +33 -0
- package/src/services/gptService.js +302 -0
- package/src/services/gptService.test.js +120 -0
- package/src/templates/agent-prompt.template.md +29 -0
- package/src/templates/architect-prompt.template.md +50 -0
- package/src/templates/envScanRequest.md +4 -0
- package/src/templates/gitWorkflow.md +32 -0
- package/src/templates/multiAgent.md +164 -0
- package/src/templates/vectorMode.md +22 -0
- package/src/utils/aiHeader.js +303 -0
- package/src/utils/fileUtils.js +928 -0
- package/src/utils/projectDetector.js +704 -0
- package/src/utils/tokenEstimator.js +198 -0
- package/.ecksnapshot.config.js +0 -35
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { loadSetupConfig, getAllProfiles } from '../config.js';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
// Simple template renderer for basic variable substitution
|
|
10
|
+
function render(template, data) {
|
|
11
|
+
let output = template;
|
|
12
|
+
for (const key in data) {
|
|
13
|
+
const value = data[key];
|
|
14
|
+
if (typeof value === 'object' && value !== null) {
|
|
15
|
+
for (const nestedKey in value) {
|
|
16
|
+
output = output.replace(new RegExp(`{{${key}.${nestedKey}}}`, 'g'), value[nestedKey]);
|
|
17
|
+
}
|
|
18
|
+
} else {
|
|
19
|
+
output = output.replace(new RegExp(`{{${key}}}`, 'g'), value);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return output;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildAgentDefinitions(executionAgents) {
|
|
26
|
+
let definitions = '';
|
|
27
|
+
for (const key in executionAgents) {
|
|
28
|
+
const agent = executionAgents[key];
|
|
29
|
+
if (agent.active) {
|
|
30
|
+
definitions += `
|
|
31
|
+
### ${agent.name} (ID: "${key}")
|
|
32
|
+
- **Description:** ${agent.description}
|
|
33
|
+
- **GUI Support:** ${agent.guiSupport ? 'Yes' : 'No (Headless)'}
|
|
34
|
+
- **Capabilities:** ${agent.capabilities.join(', ')}
|
|
35
|
+
- **Restrictions:** ${agent.restrictions.join(', ')}
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return definitions;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildEckManifestSection(eckManifest) {
|
|
43
|
+
if (!eckManifest) {
|
|
44
|
+
return '';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let section = '\n## Project-Specific Manifest (.eck Directory)\n\n';
|
|
48
|
+
section += 'This project includes a `.eck` directory with specific context and configuration:\n\n';
|
|
49
|
+
|
|
50
|
+
if (eckManifest.context) {
|
|
51
|
+
section += '### Project Context\n\n';
|
|
52
|
+
section += eckManifest.context + '\n\n';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (eckManifest.operations) {
|
|
56
|
+
section += '### Operations Guide\n\n';
|
|
57
|
+
section += eckManifest.operations + '\n\n';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (eckManifest.journal) {
|
|
61
|
+
section += '### Development Journal\n\n';
|
|
62
|
+
section += eckManifest.journal + '\n\n';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (Object.keys(eckManifest.environment).length > 0) {
|
|
66
|
+
section += '### Environment Overrides\n\n';
|
|
67
|
+
section += 'The following environment settings override auto-detected values:\n\n';
|
|
68
|
+
for (const [key, value] of Object.entries(eckManifest.environment)) {
|
|
69
|
+
section += `- **${key}**: ${value}\n`;
|
|
70
|
+
}
|
|
71
|
+
section += '\n';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
section += '**Important**: Use this manifest information when formulating technical plans and briefing execution agents. The context, operations guide, and journal provide crucial project-specific knowledge that should inform your decisions.\n\n';
|
|
75
|
+
section += '---\n\n';
|
|
76
|
+
|
|
77
|
+
return section;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function extractMeaningfulLine(block) {
|
|
81
|
+
if (!block || typeof block !== 'string') {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const lines = block.split(/\r?\n/);
|
|
86
|
+
for (const line of lines) {
|
|
87
|
+
const trimmed = line.trim();
|
|
88
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const withoutBullet = trimmed.replace(/^[-*]\s*/, '').trim();
|
|
92
|
+
if (withoutBullet) {
|
|
93
|
+
return withoutBullet.replace(/\s+/g, ' ');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function extractDescriptionFromManifest(eckManifest) {
|
|
100
|
+
if (!eckManifest) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (typeof eckManifest.description === 'string' && eckManifest.description.trim()) {
|
|
105
|
+
return eckManifest.description.trim();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (eckManifest.project && typeof eckManifest.project.description === 'string' && eckManifest.project.description.trim()) {
|
|
109
|
+
return eckManifest.project.description.trim();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (typeof eckManifest.context === 'string' && eckManifest.context.trim()) {
|
|
113
|
+
const sectionMatch = eckManifest.context.match(/##\s*Description\s*([\s\S]*?)(?=^##\s|^#\s|\Z)/im);
|
|
114
|
+
if (sectionMatch && sectionMatch[1]) {
|
|
115
|
+
const meaningful = extractMeaningfulLine(sectionMatch[1]);
|
|
116
|
+
if (meaningful) {
|
|
117
|
+
return meaningful;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const fallback = extractMeaningfulLine(eckManifest.context);
|
|
122
|
+
if (fallback) {
|
|
123
|
+
return fallback;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function resolveProjectDescription(context) {
|
|
131
|
+
const defaultDescription = 'Project description not provided.';
|
|
132
|
+
|
|
133
|
+
const manifestDescription = extractDescriptionFromManifest(context.eckManifest);
|
|
134
|
+
if (manifestDescription) {
|
|
135
|
+
const normalized = manifestDescription.trim();
|
|
136
|
+
const genericPatterns = [
|
|
137
|
+
/^brief description of what this project does/i,
|
|
138
|
+
/^no project context provided/i
|
|
139
|
+
];
|
|
140
|
+
const isGeneric = genericPatterns.some(pattern => pattern.test(normalized));
|
|
141
|
+
if (!isGeneric) {
|
|
142
|
+
return normalized;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (context.repoPath) {
|
|
147
|
+
try {
|
|
148
|
+
const packageJsonPath = path.join(context.repoPath, 'package.json');
|
|
149
|
+
const pkgRaw = await fs.readFile(packageJsonPath, 'utf-8');
|
|
150
|
+
const pkg = JSON.parse(pkgRaw);
|
|
151
|
+
if (typeof pkg.description === 'string' && pkg.description.trim()) {
|
|
152
|
+
return pkg.description.trim();
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
// Ignore errors - package.json may not exist or be readable
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return defaultDescription;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function generateEnhancedAIHeader(context, isGitRepo = false) {
|
|
163
|
+
try {
|
|
164
|
+
const setupConfig = await loadSetupConfig();
|
|
165
|
+
const { aiInstructions } = setupConfig;
|
|
166
|
+
const { architectPersona, executionAgents, promptTemplates } = aiInstructions;
|
|
167
|
+
|
|
168
|
+
// Helper function to read a template file or return the string if it's not a path
|
|
169
|
+
const loadTemplate = async (templatePathOrString) => {
|
|
170
|
+
if (templatePathOrString && (templatePathOrString.endsWith('.md') || templatePathOrString.endsWith('.txt'))) {
|
|
171
|
+
try {
|
|
172
|
+
// Resolve path relative to the project root. __dirname is src/utils.
|
|
173
|
+
const resolvedPath = path.join(__dirname, '..', '..', templatePathOrString);
|
|
174
|
+
return await fs.readFile(resolvedPath, 'utf-8');
|
|
175
|
+
} catch (e) {
|
|
176
|
+
return `ERROR: FAILED TO LOAD TEMPLATE ${templatePathOrString}: ${e.message}`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return templatePathOrString; // Fallback for old-style inline strings or errors
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// P1 Bug Fix: Normalize manifest structure as per Consilium report
|
|
183
|
+
function normalizeManifest(raw) {
|
|
184
|
+
if (!raw) return null;
|
|
185
|
+
const out = {};
|
|
186
|
+
// Handle `setup.json` structure (e.g., `projectContext.name`)
|
|
187
|
+
if (raw.projectContext) {
|
|
188
|
+
out.context = raw.projectContext.description || JSON.stringify(raw.projectContext, null, 2);
|
|
189
|
+
out.operations = raw.operations || raw.projectContext.operations || ''; // Assuming .eck/OPERATIONS.md is separate
|
|
190
|
+
out.journal = raw.journal || raw.projectContext.journal || ''; // Assuming .eck/JOURNAL.md is separate
|
|
191
|
+
out.environment = raw.environment || raw.projectContext.environment || {}; // Assuming .eck/ENVIRONMENT.md is separate
|
|
192
|
+
} else {
|
|
193
|
+
// Handle direct .eck file structure (e.g., raw.context from CONTEXT.md)
|
|
194
|
+
out.context = raw.context || '';
|
|
195
|
+
out.operations = raw.operations || '';
|
|
196
|
+
out.journal = raw.journal || '';
|
|
197
|
+
out.environment = raw.environment || {};
|
|
198
|
+
}
|
|
199
|
+
// Add fallback text if still empty
|
|
200
|
+
if (!out.context) out.context = 'No project context provided.';
|
|
201
|
+
if (!out.operations) out.operations = 'No operations guide provided.';
|
|
202
|
+
if (!out.journal) out.journal = 'No journal entries found.';
|
|
203
|
+
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// --- Build common context sections ---
|
|
208
|
+
const projectDescription = await resolveProjectDescription(context);
|
|
209
|
+
const projectOverview = `### PROJECT OVERVIEW
|
|
210
|
+
- **Project:** ${context.repoName || 'Unknown'}
|
|
211
|
+
- **Description:** ${projectDescription}
|
|
212
|
+
`;
|
|
213
|
+
const normalizedEck = normalizeManifest(context.eckManifest);
|
|
214
|
+
let eckManifestSection = '';
|
|
215
|
+
if (normalizedEck) {
|
|
216
|
+
eckManifestSection = buildEckManifestSection(normalizedEck);
|
|
217
|
+
} else {
|
|
218
|
+
eckManifestSection = '### PROJECT-SPECIFIC MANIFEST (.eck Directory)\n\nWARNING: .eck manifest was not found or was empty.\n';
|
|
219
|
+
}
|
|
220
|
+
// --- End context building ---
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
// Check if agent mode is enabled
|
|
224
|
+
if (context.options && context.options.agent) {
|
|
225
|
+
const agentPromptTemplate = await loadTemplate(promptTemplates.agent);
|
|
226
|
+
|
|
227
|
+
const agentHeader = `${agentPromptTemplate}
|
|
228
|
+
|
|
229
|
+
${projectOverview}
|
|
230
|
+
${eckManifestSection}
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Project Snapshot Information
|
|
234
|
+
|
|
235
|
+
- **Project**: ${context.repoName || 'Unknown'}
|
|
236
|
+
- **Timestamp**: ${new Date().toISOString()}
|
|
237
|
+
- **Files Included**: ${context.stats ? context.stats.includedFiles : 'Unknown'}
|
|
238
|
+
- **Total Files in Repo**: ${context.stats ? context.stats.totalFiles : 'Unknown'}
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
`;
|
|
243
|
+
return agentHeader;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// --- This is the main/Senior Architect prompt logic ---
|
|
247
|
+
let template;
|
|
248
|
+
if (context.mode === 'vector') {
|
|
249
|
+
template = await loadTemplate(promptTemplates.vectorMode);
|
|
250
|
+
// Inject context for vector mode
|
|
251
|
+
template = template.replace('{{multiAgentSection}}', `
|
|
252
|
+
${projectOverview}
|
|
253
|
+
${eckManifestSection}
|
|
254
|
+
`);
|
|
255
|
+
} else {
|
|
256
|
+
template = await loadTemplate(promptTemplates.multiAgent);
|
|
257
|
+
// --- INJECT DYNAMIC CONTEXT ---
|
|
258
|
+
template = template.replace('{{projectOverview}}', projectOverview);
|
|
259
|
+
template = template.replace('{{eckManifestSection}}', eckManifestSection);
|
|
260
|
+
// --- END INJECT ---
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const agentDefinitions = buildAgentDefinitions(executionAgents);
|
|
264
|
+
|
|
265
|
+
const data = {
|
|
266
|
+
...context,
|
|
267
|
+
timestamp: new Date().toISOString(),
|
|
268
|
+
architectPersona,
|
|
269
|
+
agentDefinitions
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
let renderedTemplate = render(template, data);
|
|
273
|
+
|
|
274
|
+
// Inject dynamic profile context if a profile is active
|
|
275
|
+
if (context.options && context.options.profile && context.repoPath) {
|
|
276
|
+
let metadataHeader = '\n\n## Partial Snapshot Context\n';
|
|
277
|
+
metadataHeader += `- **Profile(s) Active:** ${context.options.profile}\n`;
|
|
278
|
+
try {
|
|
279
|
+
const allProfiles = await getAllProfiles(context.repoPath);
|
|
280
|
+
const activeProfileNames = context.options.profile.split(',').map(p => p.trim().replace(/^-/, ''));
|
|
281
|
+
const allProfileNames = Object.keys(allProfiles).filter(p => !activeProfileNames.includes(p));
|
|
282
|
+
if (allProfileNames.length > 0) {
|
|
283
|
+
metadataHeader += `- **Other Available Profiles:** ${allProfileNames.join(', ')}\n`;
|
|
284
|
+
}
|
|
285
|
+
} catch (e) { /* fail silently on metadata generation */ }
|
|
286
|
+
|
|
287
|
+
const insertMarker = "### HIERARCHICAL AGENT WORKFLOW"; // Use our new marker
|
|
288
|
+
renderedTemplate = renderedTemplate.replace(insertMarker, metadataHeader + '\n' + insertMarker);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return renderedTemplate;
|
|
292
|
+
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.warn('Warning: Could not load setup.json, using minimal header', error.message);
|
|
295
|
+
return `# Snapshot for ${context.repoName || 'Project'}
|
|
296
|
+
|
|
297
|
+
Generated: ${new Date().toISOString()}
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
`;
|
|
302
|
+
}
|
|
303
|
+
}
|