@xelth/eck-snapshot 4.1.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.
@@ -6,6 +6,61 @@ import { detectProjectType, getProjectSpecificFiltering } from './projectDetecto
6
6
  import { executePrompt as askClaude } from '../services/claudeCliService.js';
7
7
  import { getProfile, loadSetupConfig } from '../config.js';
8
8
  import micromatch from 'micromatch';
9
+ import { minimatch } from 'minimatch';
10
+
11
+ /**
12
+ * Scanner for detecting and redacting secrets (API keys, tokens)
13
+ */
14
+ export const SecretScanner = {
15
+ patterns: [
16
+ // Service-specific patterns
17
+ { name: 'GitHub Token', regex: /gh[pous]_[a-zA-Z0-9]{36}/g },
18
+ { name: 'AWS Access Key', regex: /(?:AKIA|ASIA)[0-9A-Z]{16}/g },
19
+ { name: 'OpenAI API Key', regex: /sk-[a-zA-Z0-9]{32,}/g },
20
+ { name: 'Stripe Secret Key', regex: /sk_live_[0-9a-zA-Z]{24}/g },
21
+ { name: 'Google API Key', regex: /AIza[0-9A-Za-z\-_]{35}/g },
22
+ { name: 'Slack Token', regex: /xox[baprs]-[0-9a-zA-Z\-]{10,}/g },
23
+ { name: 'NPM Token', regex: /npm_[a-zA-Z0-9]{36}/g },
24
+ { name: 'Private Key', regex: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g },
25
+ // Generic high-entropy patterns near sensitive keywords
26
+ {
27
+ name: 'Generic Secret',
28
+ regex: /(?:api[_-]?key|secret|password|token|auth|pwd|credential)\s*[:=]\s*["']([a-zA-Z0-9\-_.]{16,})["']/gi
29
+ }
30
+ ],
31
+
32
+ /**
33
+ * Scans content and replaces detected secrets with a placeholder
34
+ * @param {string} content - File content to scan
35
+ * @param {string} filePath - Path for logging context
36
+ * @returns {{content: string, found: string[]}} Redacted content and list of found secret types
37
+ */
38
+ redact(content, filePath) {
39
+ let redactedContent = content;
40
+ const foundSecrets = [];
41
+
42
+ for (const pattern of this.patterns) {
43
+ // Reset regex lastIndex for global patterns
44
+ pattern.regex.lastIndex = 0;
45
+
46
+ const matches = [...content.matchAll(pattern.regex)];
47
+ if (matches.length > 0) {
48
+ for (const match of matches) {
49
+ // For generic pattern, use captured group; for specific patterns, use full match
50
+ const secretValue = match[1] || match[0];
51
+ const placeholder = `[REDACTED_${pattern.name.replace(/\s+/g, '_').toUpperCase()}]`;
52
+ redactedContent = redactedContent.replace(secretValue, placeholder);
53
+ foundSecrets.push(pattern.name);
54
+ }
55
+ }
56
+ }
57
+
58
+ return {
59
+ content: redactedContent,
60
+ found: [...new Set(foundSecrets)]
61
+ };
62
+ }
63
+ };
9
64
 
10
65
  export function parseSize(sizeStr) {
11
66
  const units = { B: 1, KB: 1024, MB: 1024 ** 2, GB: 1024 ** 3 };
@@ -40,40 +95,43 @@ export function matchesPattern(filePath, patterns) {
40
95
  });
41
96
  }
42
97
 
98
+ /**
99
+ * Checks if a file matches confidential patterns using minimatch
100
+ * @param {string} fileName - The file name to check
101
+ * @param {array} patterns - Array of glob patterns to match against
102
+ * @returns {boolean} True if the file matches any pattern
103
+ */
104
+ function matchesConfidentialPattern(fileName, patterns) {
105
+ return patterns.some(pattern => minimatch(fileName, pattern, { nocase: true }));
106
+ }
107
+
43
108
  /**
44
109
  * Applies smart filtering for files within the .eck directory.
45
110
  * Includes documentation files while excluding confidential files.
46
111
  * @param {string} fileName - The file name to check
47
112
  * @param {object} eckConfig - The eckDirectoryFiltering config object
48
- * @returns {boolean} True if the file should be included, false otherwise
113
+ * @returns {object} { include: boolean, isConfidential: boolean }
49
114
  */
50
115
  export function applyEckDirectoryFiltering(fileName, eckConfig) {
51
116
  if (!eckConfig || !eckConfig.enabled) {
52
- return false; // .eck filtering disabled, exclude all
117
+ return { include: false, isConfidential: false }; // .eck filtering disabled, exclude all
53
118
  }
54
119
 
55
120
  const { confidentialPatterns = [], alwaysIncludePatterns = [] } = eckConfig;
56
121
 
57
- // First check if file matches confidential patterns (always exclude)
58
- if (matchesPattern(fileName, confidentialPatterns)) {
59
- return false;
122
+ // First check if file matches confidential patterns
123
+ const isConfidential = matchesConfidentialPattern(fileName, confidentialPatterns);
124
+ if (isConfidential) {
125
+ return { include: false, isConfidential: true };
60
126
  }
61
127
 
62
128
  // Check if file matches always-include patterns
63
129
  if (matchesPattern(fileName, alwaysIncludePatterns)) {
64
- return true;
130
+ return { include: true, isConfidential: false };
65
131
  }
66
132
 
67
133
  // Default: exclude files not in the include list
68
- return false;
69
- }
70
-
71
- export async function checkGitAvailability() {
72
- try {
73
- await execa('git', ['--version']);
74
- } catch (error) {
75
- throw new Error('Git is not installed or not available in PATH');
76
- }
134
+ return { include: false, isConfidential: false };
77
135
  }
78
136
 
79
137
  export async function checkGitRepository(repoPath) {
@@ -85,17 +143,18 @@ export async function checkGitRepository(repoPath) {
85
143
  }
86
144
  }
87
145
 
88
- export async function scanDirectoryRecursively(dirPath, config, relativeTo = dirPath, projectType = null) {
146
+ export async function scanDirectoryRecursively(dirPath, config, relativeTo = dirPath, projectType = null, trackConfidential = false) {
89
147
  const files = [];
90
-
148
+ const confidentialFiles = [];
149
+
91
150
  // Get project-specific filtering if not provided
92
151
  if (!projectType) {
93
152
  const detection = await detectProjectType(relativeTo);
94
153
  projectType = detection.type;
95
154
  }
96
-
155
+
97
156
  const projectSpecific = await getProjectSpecificFiltering(projectType);
98
-
157
+
99
158
  // Merge project-specific filters with global config
100
159
  const effectiveConfig = {
101
160
  ...config,
@@ -103,38 +162,47 @@ export async function scanDirectoryRecursively(dirPath, config, relativeTo = dir
103
162
  filesToIgnore: [...(config.filesToIgnore || []), ...(projectSpecific.filesToIgnore || [])],
104
163
  extensionsToIgnore: [...(config.extensionsToIgnore || []), ...(projectSpecific.extensionsToIgnore || [])]
105
164
  };
106
-
165
+
107
166
  try {
108
167
  const entries = await fs.readdir(dirPath, { withFileTypes: true });
109
-
168
+
110
169
  for (const entry of entries) {
111
170
  const fullPath = path.join(dirPath, entry.name);
112
171
  const relativePath = path.relative(relativeTo, fullPath).replace(/\\/g, '/');
113
-
114
- if (effectiveConfig.dirsToIgnore.some(dir =>
115
- entry.name === dir.replace('/', '') ||
172
+
173
+ // Special handling for .eck directory - never ignore it when tracking confidential files
174
+ const isEckDirectory = entry.name === '.eck' && entry.isDirectory();
175
+ const isInsideEck = relativePath.startsWith('.eck/');
176
+
177
+ if (effectiveConfig.dirsToIgnore.some(dir =>
178
+ entry.name === dir.replace('/', '') ||
116
179
  relativePath.startsWith(dir)
117
- )) {
180
+ ) && !isEckDirectory && !isInsideEck) {
118
181
  continue;
119
182
  }
120
-
121
- // Special handling for .eck directory - allow it even when hidden files are excluded
122
- const isEckDirectory = entry.name === '.eck' && entry.isDirectory();
123
- const isInsideEck = relativePath.startsWith('.eck/');
124
183
 
125
184
  if (!effectiveConfig.includeHidden && entry.name.startsWith('.') && !isEckDirectory && !isInsideEck) {
126
185
  continue;
127
186
  }
128
187
 
129
188
  if (entry.isDirectory()) {
130
- const subFiles = await scanDirectoryRecursively(fullPath, effectiveConfig, relativeTo, projectType);
131
- files.push(...subFiles);
189
+ const subResult = await scanDirectoryRecursively(fullPath, effectiveConfig, relativeTo, projectType, trackConfidential);
190
+ if (trackConfidential) {
191
+ files.push(...subResult.files);
192
+ confidentialFiles.push(...subResult.confidentialFiles);
193
+ } else {
194
+ files.push(...subResult);
195
+ }
132
196
  } else {
133
197
  // Apply smart filtering for files inside .eck directory
134
198
  if (isInsideEck) {
135
199
  const eckConfig = effectiveConfig.eckDirectoryFiltering;
136
- if (!applyEckDirectoryFiltering(entry.name, eckConfig)) {
137
- continue; // File doesn't pass .eck smart filtering
200
+ const filterResult = applyEckDirectoryFiltering(entry.name, eckConfig);
201
+
202
+ if (trackConfidential && filterResult.isConfidential) {
203
+ confidentialFiles.push(relativePath);
204
+ } else if (filterResult.include) {
205
+ files.push(relativePath);
138
206
  }
139
207
  } else {
140
208
  // Normal filtering for non-.eck files
@@ -142,16 +210,15 @@ export async function scanDirectoryRecursively(dirPath, config, relativeTo = dir
142
210
  matchesPattern(relativePath, effectiveConfig.filesToIgnore)) {
143
211
  continue;
144
212
  }
213
+ files.push(relativePath);
145
214
  }
146
-
147
- files.push(relativePath);
148
215
  }
149
216
  }
150
217
  } catch (error) {
151
218
  console.warn(`⚠️ Warning: Could not read directory: ${dirPath} - ${error.message}`);
152
219
  }
153
-
154
- return files;
220
+
221
+ return trackConfidential ? { files, confidentialFiles } : files;
155
222
  }
156
223
 
157
224
  export async function loadGitignore(repoPath) {
@@ -194,6 +261,11 @@ export async function generateDirectoryTree(dir, prefix = '', allFiles, depth =
194
261
  const validEntries = [];
195
262
 
196
263
  for (const entry of sortedEntries) {
264
+ // Skip hidden directories and files (starting with '.')
265
+ // EXCEPT: show .eck as a placeholder at the first level
266
+ if (entry.name.startsWith('.')) {
267
+ continue;
268
+ }
197
269
  if (config.dirsToIgnore.some(d => entry.name.includes(d.replace('/', '')))) continue;
198
270
  const fullPath = path.join(dir, entry.name);
199
271
  const relativePath = path.relative(process.cwd(), fullPath).replace(/\\/g, '/');
@@ -205,10 +277,10 @@ export async function generateDirectoryTree(dir, prefix = '', allFiles, depth =
205
277
  for (let i = 0; i < validEntries.length; i++) {
206
278
  const { entry, fullPath, relativePath } = validEntries[i];
207
279
  const isLast = i === validEntries.length - 1;
208
-
280
+
209
281
  const connector = isLast ? '└── ' : '├── ';
210
282
  const nextPrefix = prefix + (isLast ? ' ' : '│ ');
211
-
283
+
212
284
  if (entry.isDirectory()) {
213
285
  tree += `${prefix}${connector}${entry.name}/\n`;
214
286
  tree += await generateDirectoryTree(fullPath, nextPrefix, allFiles, depth + 1, maxDepth, config);
@@ -216,7 +288,14 @@ export async function generateDirectoryTree(dir, prefix = '', allFiles, depth =
216
288
  tree += `${prefix}${connector}${entry.name}\n`;
217
289
  }
218
290
  }
219
-
291
+
292
+ // Add .eck placeholder at root level
293
+ if (depth === 0) {
294
+ const isLast = validEntries.length === 0;
295
+ const connector = isLast ? '└── ' : '├── ';
296
+ tree += `${prefix}${connector}.eck/\n`;
297
+ }
298
+
220
299
  return tree;
221
300
  } catch (error) {
222
301
  console.warn(`⚠️ Warning: Could not read directory: ${dir}`);
@@ -336,14 +415,6 @@ export function generateTimestamp() {
336
415
  return `${YYYY}-${MM}-${DD}_${hh}-${mm}-${ss}`;
337
416
  }
338
417
 
339
- export function sanitizeForFilename(text) {
340
- return text
341
- .toLowerCase()
342
- .replace(/\s+/g, '-') // Replace spaces with hyphens
343
- .replace(/[^a-z0-9-]/g, '') // Remove invalid characters
344
- .substring(0, 50); // Truncate to a reasonable length
345
- }
346
-
347
418
  /**
348
419
  * Displays project detection information in a user-friendly format
349
420
  * @param {object} detection - Project detection result
@@ -775,164 +846,89 @@ export async function initializeEckManifest(projectPath) {
775
846
  const staticFactsJson = JSON.stringify(staticFacts, null, 2);
776
847
  // --- END NEW LOGIC ---
777
848
 
778
- // Template files with their content
779
- const templateFiles = [
780
- {
781
- name: 'ENVIRONMENT.md',
782
- prompt: `Given these static project analysis facts:\n${staticFactsJson}\n\nGenerate the raw YAML key-value content for an .eck/ENVIRONMENT.md file. Only include detected facts. DO NOT add any keys that are not present in the facts. DO NOT add conversational text or markdown wrappers. Your response MUST start directly with a YAML key (e.g., 'project_type: ...').`,
783
- content: `# This file is for environment overrides. Add agent-specific settings here.\nagent_id: local_dev\n` // Simple static fallback
849
+ // 3. Define smarter templates and prompts with "STUB NOTICES"
850
+ const templateConfigs = {
851
+ 'ENVIRONMENT.md': {
852
+ prompt: `Generate raw YAML for .eck/ENVIRONMENT.md based on these project facts:\n${staticFactsJson}\nInclude project_type, runtime, and agent_id: local_dev. NO markdown fences.`,
853
+ fallback: `project_type: ${staticFacts.type || 'unknown'}
854
+ # [STUB: ENVIRONMENT.MD]
855
+ # ARCHITECT: Task Coder to verify environment variables.
856
+ # CODER: Scan project for .env files or config files and fill this. Remove this stub notice.
857
+ agent_id: local_dev
858
+ `
784
859
  },
785
- {
786
- name: 'CONTEXT.md',
787
- prompt: `Given these static project analysis facts:\n${staticFactsJson}\n\nGenerate the raw Markdown content ONLY for a .eck/CONTEXT.md file. Use the facts to write ## Description, ## Architecture, and ## Key Technologies. DO NOT add conversational text (like "Here is the file..."). Your response MUST start *directly* with the '# Project Overview' heading.`,
788
- content: `# Project Overview
860
+ 'CONTEXT.md': {
861
+ prompt: `Analyze these project files and dependencies:\n${staticFactsJson}\nGenerate a professional # Project Overview in Markdown. Describe the actual architecture and purpose of this specific project. Be technical and concise. Start with '# Project Overview'.`,
862
+ fallback: `# [STUB: CONTEXT.MD]
863
+
864
+ ## 🚨 ATTENTION ARCHITECT & CODER
865
+ **ARCHITECT:** This file is EMPTY. You MUST prioritize a task for the Coder to analyze the source code and define the Project Overview, Architecture, and Key Technologies.
866
+ **CODER:** Use your tools to read the code, write the real content, and DELETE this entire stub header.
789
867
 
790
868
  ## Description
791
- Brief description of what this project does and its main purpose.
869
+ (Placeholder: A ${staticFacts.type || 'project'} project)
792
870
 
793
871
  ## Architecture
794
- High-level overview of the system architecture, key components, and how they interact.
795
-
796
- ## Key Technologies
797
- - Technology 1
798
- - Technology 2
799
- - Technology 3
800
-
801
- ## Important Notes
802
- Any crucial information that developers should know when working on this project.
803
- `
804
- },
805
- {
806
- name: 'OPERATIONS.md',
807
- prompt: `Given these static project analysis facts (especially package.json scripts):
808
- ${staticFactsJson}
809
-
810
- Generate the raw Markdown content ONLY for a .eck/OPERATIONS.md file. DO NOT add conversational text. Your response MUST start *directly* with the '# Common Operations' heading. List commands for ## Development Setup, ## Running the Project, and ## Testing.`,
811
- content: `# Common Operations
812
-
813
- ## Development Setup
814
- \`\`\`bash
815
- # Setup commands
816
- npm install
817
- # or yarn install
818
- \`\`\`
819
-
820
- ## Running the Project
821
- \`\`\`bash
822
- # Development mode
823
- npm run dev
824
-
825
- # Production build
826
- npm run build
827
- \`\`\`
828
-
829
- ## Testing
830
- \`\`\`bash
831
- # Run tests
832
- npm test
833
-
834
- # Run tests in watch mode
835
- npm run test:watch
836
- \`\`\`
837
-
838
- ## Deployment
839
- \`\`\`bash
840
- # Deployment commands
841
- npm run deploy
842
- \`\`\`
843
-
844
- ## Troubleshooting
845
- Common issues and their solutions.
846
- `
872
+ (Placeholder: TBD)`
847
873
  },
848
- {
849
- name: 'JOURNAL.md',
850
- content: `# Development Journal
874
+ 'OPERATIONS.md': {
875
+ prompt: `Look at the dependencies and files:\n${staticFactsJson}\nGenerate a Markdown guide for common operations (Setup, Run, Test, Build) using the correct commands for this tech stack. Start with '# Common Operations'.`,
876
+ fallback: `# [STUB: OPERATIONS.MD]
851
877
 
852
- ## Recent Changes
853
- Track significant changes, decisions, and progress here.
878
+ ## 🚨 ATTENTION
879
+ **CODER:** Run \`npm run\`, check Makefile, or build files to identify REAL commands for Setup, Running, and Testing. Replace this stub with actual commands. Remove this notice.
854
880
 
855
- ---
856
-
857
- ### YYYY-MM-DD - Project Started
858
- - Initial project setup
859
- - Added basic structure
860
- `
861
- },
862
- {
863
- name: 'ROADMAP.md',
864
- prompt: `Given these static project analysis facts:\n${staticFactsJson}\n\nGenerate the raw Markdown content ONLY for a .eck/ROADMAP.md file. DO NOT add conversational text. Start *directly* with '# Project Roadmap'. Propose 1-2 *plausible* placeholder items for ## Current Sprint/Phase and ## Next Phase based on the project type.`,
865
- content: `# Project Roadmap
866
-
867
- ## Current Sprint/Phase
868
- - [ ] Feature 1
869
- - [ ] Feature 2
870
- - [ ] Bug fix 1
871
-
872
- ## Next Phase
873
- - [ ] Future feature 1
874
- - [ ] Future feature 2
875
-
876
- ## Long-term Goals
877
- - [ ] Major milestone 1
878
- - [ ] Major milestone 2
879
-
880
- ## Completed
881
- - [x] Project initialization
882
- `
881
+ ## Setup
882
+ ${staticFacts.type === 'nodejs' ? 'npm install' : 'TBD'}`
883
883
  },
884
- {
885
- name: 'TECH_DEBT.md',
886
- prompt: `Generate the raw Markdown content ONLY for a .eck/TECH_DEBT.md file. DO NOT add conversational text. Start *directly* with '# Technical Debt'. Propose 1-2 *common* placeholder items for ## Code Quality Issues and ## Refactoring Opportunities.`,
887
- content: `# Technical Debt
888
-
889
- ## Current Technical Debt
890
- Track technical debt, refactoring needs, and code quality issues.
891
-
892
- ### Code Quality Issues
893
- - Issue 1: Description and priority
894
- - Issue 2: Description and priority
884
+ 'ROADMAP.md': {
885
+ prompt: `Based on the project type (${staticFacts.type}), propose a 3-step roadmap. Start with '# Project Roadmap'.`,
886
+ fallback: `# [STUB: ROADMAP.MD]
895
887
 
896
- ### Refactoring Opportunities
897
- - Opportunity 1: Description and impact
898
- - Opportunity 2: Description and impact
899
-
900
- ### Performance Issues
901
- - Performance issue 1: Description and impact
902
- - Performance issue 2: Description and impact
888
+ **ARCHITECT:** Set a real roadmap based on user goals. **CODER:** Remove this stub marker once a real goal is added.`
889
+ },
890
+ 'TECH_DEBT.md': {
891
+ prompt: `Given this is a ${staticFacts.type} project, list 2-3 common technical debt items. Start with '# Technical Debt'.`,
892
+ fallback: `# [STUB: TECH_DEBT.MD]
903
893
 
904
- ### Security Concerns
905
- - Security concern 1: Description and priority
906
- - Security concern 2: Description and priority
894
+ **CODER:** Scan for TODOs/FIXMEs or structural issues and list them here. Remove this stub marker.`
895
+ },
896
+ 'JOURNAL.md': {
897
+ fallback: `# Development Journal
907
898
 
908
- ## Resolved
909
- - [x] Resolved issue 1
910
- `
899
+ ## Recent Changes
900
+ ---
901
+ type: feat
902
+ scope: project
903
+ summary: Initial manifest generated (PENDING REVIEW)
904
+ date: ${new Date().toISOString().split('T')[0]}
905
+ ---
906
+ - NOTICE: Some .eck files are STUBS. They need manual or AI-assisted verification.`
911
907
  }
912
- ];
908
+ };
913
909
 
914
910
  // Create each template file (only if it doesn't exist)
915
- for (const file of templateFiles) {
916
- const filePath = path.join(eckDir, file.name);
917
-
911
+ for (const [fileName, config] of Object.entries(templateConfigs)) {
912
+ const filePath = path.join(eckDir, fileName);
913
+
918
914
  // Skip if file already exists
919
915
  try {
920
916
  await fs.stat(filePath);
921
- console.log(` ✅ ${file.name} already exists, skipping`);
917
+ console.log(` ✅ ${fileName} already exists, skipping`);
922
918
  continue;
923
919
  } catch (error) {
924
920
  // File doesn't exist, create it
925
921
  }
926
-
927
- let fileContent = file.content; // Start with fallback
922
+
923
+ let fileContent = config.fallback; // Start with stub fallback
928
924
  let generatedByAI = false;
929
925
 
930
926
  // For files with a prompt, try to dynamically generate (only if enabled)
931
- if (file.prompt && aiGenerationEnabled) {
927
+ if (config.prompt && aiGenerationEnabled) {
932
928
  try {
933
- console.log(` 🧠 Attempting to auto-generate ${file.name} via Claude...`);
934
- const aiResponseObject = await askClaude(file.prompt); // Use the prompt
935
- const rawText = aiResponseObject.result; // Handle Claude response
929
+ console.log(` 🧠 Attempting to auto-generate ${fileName} via Claude...`);
930
+ const aiResponseObject = await askClaude(config.prompt);
931
+ const rawText = aiResponseObject.result;
936
932
 
937
933
  if (!rawText || typeof rawText.replace !== 'function') {
938
934
  throw new Error(`AI returned invalid content type: ${typeof rawText}`);
@@ -944,19 +940,19 @@ Track technical debt, refactoring needs, and code quality issues.
944
940
  if (cleanedResponse) {
945
941
  fileContent = cleanedResponse;
946
942
  generatedByAI = true;
947
- console.log(` ✨ AI successfully generated ${file.name}`);
943
+ console.log(` ✨ AI successfully generated ${fileName}`);
948
944
  } else {
949
945
  throw new Error('AI returned empty content.');
950
946
  }
951
947
  } catch (error) {
952
- console.warn(` ⚠️ AI generation failed for ${file.name}: ${error.message}. Using static template.`);
953
- // fileContent is already set to the fallback
948
+ console.warn(` ⚠️ AI generation failed for ${fileName}: ${error.message}. Using stub template.`);
949
+ // fileContent is already set to the stub fallback
954
950
  }
955
951
  }
956
-
952
+
957
953
  await fs.writeFile(filePath, fileContent);
958
954
  if (!generatedByAI) {
959
- console.log(` ✅ Created ${file.name} (static template)`);
955
+ console.log(` ✅ Created ${fileName} (stub template)`);
960
956
  }
961
957
  }
962
958
 
@@ -8,7 +8,8 @@ import { fileURLToPath } from 'url';
8
8
 
9
9
  const __filename = fileURLToPath(import.meta.url);
10
10
  const __dirname = path.dirname(__filename);
11
- const ESTIMATION_DATA_FILE = path.join(__dirname, '..', '..', '.eck-token-training.json');
11
+ const ECK_DIR = path.join(__dirname, '..', '..', '.eck');
12
+ const ESTIMATION_DATA_FILE = path.join(ECK_DIR, 'token-training.json');
12
13
 
13
14
  /**
14
15
  * Default coefficients for different project types (bytes to tokens ratio)
@@ -49,6 +50,8 @@ async function loadTrainingData() {
49
50
  * Save training data to file
50
51
  */
51
52
  async function saveTrainingData(data) {
53
+ // Ensure .eck directory exists
54
+ await fs.mkdir(ECK_DIR, { recursive: true });
52
55
  await fs.writeFile(ESTIMATION_DATA_FILE, JSON.stringify(data, null, 2));
53
56
  }
54
57
 
@@ -1,29 +0,0 @@
1
- import { ask } from '../../services/gptService.js';
2
-
3
- /**
4
- * CLI entry point for ask-gpt command.
5
- * @param {string} payload - JSON payload string.
6
- * @param {{ verbose?: boolean, model?: string, reasoning?: string }} options - CLI options.
7
- */
8
- export async function askGpt(payload, options = {}) {
9
- const verbose = Boolean(options.verbose);
10
- const model = options.model || 'gpt-5-codex';
11
- const reasoning = options.reasoning || 'high';
12
-
13
- if (!payload) {
14
- console.error('ask-gpt requires a JSON payload argument.');
15
- process.exitCode = 1;
16
- return;
17
- }
18
-
19
- try {
20
- const result = await ask(payload, { verbose, model, reasoning });
21
- console.log(JSON.stringify(result, null, 2));
22
- } catch (error) {
23
- console.error(error.message);
24
- if (verbose && error?.stack) {
25
- console.error(error.stack);
26
- }
27
- process.exitCode = 1;
28
- }
29
- }
@@ -1,20 +0,0 @@
1
- import ora from 'ora';
2
- import { execa } from 'execa';
3
-
4
- /**
5
- * Initiates the interactive login flow by spawning 'codex login'.
6
- * This will open a browser and wait for the user to complete authentication.
7
- * @returns {Promise<void>}
8
- */
9
- export async function initiateLogin() {
10
- const spinner = ora('Authentication required. Please follow the browser instructions.').start();
11
- try {
12
- // Run `codex login` interactively, inheriting stdio to show user instructions.
13
- await execa('codex', ['login'], { stdio: 'inherit' });
14
- spinner.succeed('Login successful. Retrying original command...');
15
- } catch (e) {
16
- spinner.fail('Login process failed or was cancelled.');
17
- // Re-throw to notify p-retry that the attempt failed.
18
- throw new Error(`Login failed: ${e.message}`);
19
- }
20
- }
@@ -1,33 +0,0 @@
1
- import { ask as askGpt } from './gptService.js';
2
- import { executePrompt as askClaude } from './claudeCliService.js';
3
-
4
- /**
5
- * Dispatches an analytical task to the most efficient AI model with a fallback.
6
- * Priority 1: Codex (GPT) with low reasoning for speed and cost.
7
- * Priority 2: Claude as a reliable fallback.
8
- * @param {string} prompt The JSON payload or prompt string for the task.
9
- * @returns {Promise<object>} The result from the successful AI agent.
10
- */
11
- export async function dispatchAnalysisTask(prompt) {
12
- try {
13
- console.log('🧠 Dispatcher: Attempting analysis with Codex (low reasoning)...');
14
- const gptOptions = {
15
- model: 'gpt-5-codex',
16
- reasoning: 'low'
17
- };
18
- // The 'ask' function expects payload as first arg, and options as second.
19
- // Since prompt is a string here, we wrap it in an object for consistency if needed,
20
- // but for simple prompts it can often be passed directly.
21
- const payload = (typeof prompt === 'string' && prompt.startsWith('{')) ? prompt : JSON.stringify({ objective: prompt });
22
- return await askGpt(payload, { verbose: false, ...gptOptions });
23
- } catch (gptError) {
24
- console.warn(`⚠️ Codex (low reasoning) failed: ${gptError.message}`);
25
- console.log('🔄 Failing over to Claude for analysis...');
26
- try {
27
- return await askClaude(prompt);
28
- } catch (claudeError) {
29
- console.error(`❌ Critical Failure: Both Codex and Claude failed for analysis task.`);
30
- throw new Error(`Primary (Codex) Error: ${gptError.message}\nFallback (Claude) Error: ${claudeError.message}`);
31
- }
32
- }
33
- }