@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.
Files changed (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -225
  3. package/index.js +14 -776
  4. package/package.json +25 -7
  5. package/setup.json +805 -0
  6. package/src/cli/cli.js +427 -0
  7. package/src/cli/commands/askGpt.js +29 -0
  8. package/src/cli/commands/autoDocs.js +150 -0
  9. package/src/cli/commands/consilium.js +86 -0
  10. package/src/cli/commands/createSnapshot.js +601 -0
  11. package/src/cli/commands/detectProfiles.js +98 -0
  12. package/src/cli/commands/detectProject.js +112 -0
  13. package/src/cli/commands/generateProfileGuide.js +91 -0
  14. package/src/cli/commands/pruneSnapshot.js +106 -0
  15. package/src/cli/commands/restoreSnapshot.js +173 -0
  16. package/src/cli/commands/setupGemini.js +149 -0
  17. package/src/cli/commands/setupGemini.test.js +115 -0
  18. package/src/cli/commands/trainTokens.js +38 -0
  19. package/src/config.js +81 -0
  20. package/src/services/authService.js +20 -0
  21. package/src/services/claudeCliService.js +621 -0
  22. package/src/services/claudeCliService.test.js +267 -0
  23. package/src/services/dispatcherService.js +33 -0
  24. package/src/services/gptService.js +302 -0
  25. package/src/services/gptService.test.js +120 -0
  26. package/src/templates/agent-prompt.template.md +29 -0
  27. package/src/templates/architect-prompt.template.md +50 -0
  28. package/src/templates/envScanRequest.md +4 -0
  29. package/src/templates/gitWorkflow.md +32 -0
  30. package/src/templates/multiAgent.md +164 -0
  31. package/src/templates/vectorMode.md +22 -0
  32. package/src/utils/aiHeader.js +303 -0
  33. package/src/utils/fileUtils.js +928 -0
  34. package/src/utils/projectDetector.js +704 -0
  35. package/src/utils/tokenEstimator.js +198 -0
  36. 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
+ }