@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.
- package/package.json +8 -4
- package/setup.json +10 -30
- package/src/cli/cli.js +11 -54
- package/src/cli/commands/autoDocs.js +1 -32
- package/src/cli/commands/createSnapshot.js +125 -6
- package/src/cli/commands/doctor.js +60 -0
- package/src/cli/commands/updateSnapshot.js +3 -3
- package/src/config.js +44 -0
- package/src/core/skeletonizer.js +77 -52
- 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 +9 -0
- package/src/utils/aiHeader.js +147 -95
- package/src/utils/eckProtocolParser.js +221 -0
- package/src/utils/fileUtils.js +181 -185
- 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
package/src/utils/fileUtils.js
CHANGED
|
@@ -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 {
|
|
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
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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
|
|
131
|
-
|
|
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
|
-
|
|
137
|
-
|
|
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
|
-
//
|
|
779
|
-
const
|
|
780
|
-
{
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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
|
-
|
|
869
|
+
(Placeholder: A ${staticFacts.type || 'project'} project)
|
|
792
870
|
|
|
793
871
|
## Architecture
|
|
794
|
-
|
|
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
|
-
|
|
850
|
-
|
|
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
|
-
##
|
|
853
|
-
|
|
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
|
-
|
|
886
|
-
|
|
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
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
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
|
-
|
|
905
|
-
|
|
906
|
-
|
|
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
|
-
##
|
|
909
|
-
|
|
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
|
|
916
|
-
const filePath = path.join(eckDir,
|
|
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(` ✅ ${
|
|
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 =
|
|
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 (
|
|
927
|
+
if (config.prompt && aiGenerationEnabled) {
|
|
932
928
|
try {
|
|
933
|
-
console.log(` 🧠 Attempting to auto-generate ${
|
|
934
|
-
const aiResponseObject = await askClaude(
|
|
935
|
-
const rawText = aiResponseObject.result;
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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
|
|
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
|
-
}
|