@xelth/eck-snapshot 5.9.0 → 6.6.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/README.md +321 -190
- package/index.js +1 -1
- package/package.json +15 -2
- package/scripts/mcp-eck-core.js +143 -13
- package/setup.json +119 -81
- package/src/cli/cli.js +256 -385
- package/src/cli/commands/createSnapshot.js +391 -175
- package/src/cli/commands/recon.js +308 -0
- package/src/cli/commands/setupMcp.js +280 -19
- package/src/cli/commands/trainTokens.js +42 -32
- package/src/cli/commands/updateSnapshot.js +136 -43
- package/src/core/depthConfig.js +54 -0
- package/src/core/skeletonizer.js +280 -21
- package/src/templates/architect-prompt.template.md +34 -0
- package/src/templates/multiAgent.md +68 -15
- package/src/templates/opencode/coder.template.md +53 -17
- package/src/templates/opencode/junior-architect.template.md +54 -15
- package/src/templates/skeleton-instruction.md +1 -1
- package/src/templates/update-prompt.template.md +2 -0
- package/src/utils/aiHeader.js +57 -27
- package/src/utils/claudeMdGenerator.js +182 -88
- package/src/utils/fileUtils.js +217 -149
- package/src/utils/gitUtils.js +12 -8
- package/src/utils/opencodeAgentsGenerator.js +8 -2
- package/src/utils/projectDetector.js +66 -21
- package/src/utils/tokenEstimator.js +11 -7
- package/src/cli/commands/consilium.js +0 -86
- package/src/cli/commands/detectProfiles.js +0 -98
- package/src/cli/commands/envSync.js +0 -319
- package/src/cli/commands/generateProfileGuide.js +0 -144
- package/src/cli/commands/pruneSnapshot.js +0 -106
- package/src/cli/commands/restoreSnapshot.js +0 -173
- package/src/cli/commands/setupGemini.js +0 -149
- package/src/cli/commands/setupGemini.test.js +0 -115
- package/src/cli/commands/showFile.js +0 -39
- package/src/services/claudeCliService.js +0 -626
- package/src/services/claudeCliService.test.js +0 -267
package/src/utils/fileUtils.js
CHANGED
|
@@ -1,13 +1,44 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { execa } from 'execa';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Safely extracts metadata headers from Large ML models without loading them into memory.
|
|
7
|
+
*/
|
|
8
|
+
export async function readMlModelMetadata(filePath) {
|
|
9
|
+
let fileHandle;
|
|
10
|
+
try {
|
|
11
|
+
fileHandle = await fs.open(filePath, 'r');
|
|
12
|
+
const stats = await fileHandle.stat();
|
|
13
|
+
const readSize = Math.min(stats.size, 4096); // Read only the first 4KB
|
|
14
|
+
const buffer = Buffer.alloc(readSize);
|
|
15
|
+
await fileHandle.read(buffer, 0, readSize, 0);
|
|
16
|
+
|
|
17
|
+
let text = buffer.toString('utf8');
|
|
18
|
+
// Extract printable characters, keeping JSON structure intact, remove binary gibberish
|
|
19
|
+
let cleanText = text.replace(/[^\x20-\x7E\n\r\t"{}\[\]:,]/g, '').trim();
|
|
20
|
+
|
|
21
|
+
if (cleanText.length > 2000) {
|
|
22
|
+
cleanText = cleanText.substring(0, 2000) + '\n...';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return `[ML MODEL METADATA EXTRACTED]\nSize: ${formatSize(stats.size)}\n\nHeader Preview:\n${cleanText}\n\n[... BINARY DATA TRUNCATED ...]`;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
return `[ML MODEL - Could not extract metadata: ${error.message}]`;
|
|
28
|
+
} finally {
|
|
29
|
+
if (fileHandle) await fileHandle.close();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
4
32
|
import ignore from 'ignore';
|
|
5
|
-
import { detectProjectType, getProjectSpecificFiltering } from './projectDetector.js';
|
|
6
|
-
import { executePrompt as askClaude } from '../services/claudeCliService.js';
|
|
33
|
+
import { detectProjectType, getProjectSpecificFiltering, getAllDetectedTypes } from './projectDetector.js';
|
|
7
34
|
import { getProfile, loadSetupConfig } from '../config.js';
|
|
8
35
|
import micromatch from 'micromatch';
|
|
9
36
|
import { minimatch } from 'minimatch';
|
|
10
37
|
|
|
38
|
+
// Global hard-ignore patterns (shared between git-based and scan-based file collection)
|
|
39
|
+
const GLOBAL_HARD_IGNORE_DIRS = ['node_modules', '.git', '.idea', '.vscode', '.gradle', 'build', '__pycache__'];
|
|
40
|
+
const GLOBAL_HARD_IGNORE_FILES = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'go.sum'];
|
|
41
|
+
|
|
11
42
|
/**
|
|
12
43
|
* Scanner for detecting and redacting secrets (API keys, tokens)
|
|
13
44
|
*/
|
|
@@ -29,6 +60,21 @@ export const SecretScanner = {
|
|
|
29
60
|
}
|
|
30
61
|
],
|
|
31
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Calculates Shannon Entropy of a string
|
|
65
|
+
*/
|
|
66
|
+
calculateEntropy(str) {
|
|
67
|
+
const len = str.length;
|
|
68
|
+
const frequencies = Array.from(str).reduce((freq, c) => {
|
|
69
|
+
freq[c] = (freq[c] || 0) + 1;
|
|
70
|
+
return freq;
|
|
71
|
+
}, {});
|
|
72
|
+
return Object.values(frequencies).reduce((sum, f) => {
|
|
73
|
+
const p = f / len;
|
|
74
|
+
return sum - (p * Math.log2(p));
|
|
75
|
+
}, 0);
|
|
76
|
+
},
|
|
77
|
+
|
|
32
78
|
/**
|
|
33
79
|
* Scans content and replaces detected secrets with a placeholder
|
|
34
80
|
* @param {string} content - File content to scan
|
|
@@ -55,6 +101,21 @@ export const SecretScanner = {
|
|
|
55
101
|
}
|
|
56
102
|
}
|
|
57
103
|
|
|
104
|
+
// Second pass: Shannon Entropy check for arbitrary hardcoded secrets
|
|
105
|
+
// Look for long strings assigned to variable names that might be keys
|
|
106
|
+
const entropyRegex = /(?:const|let|var|set|export|define)\s+([A-Za-z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD)[A-Za-z0-9_]*)\s*=\s*["']([a-zA-Z0-9+/=_-]{20,128})["']/gi;
|
|
107
|
+
const entropyMatches = [...redactedContent.matchAll(entropyRegex)];
|
|
108
|
+
|
|
109
|
+
for (const match of entropyMatches) {
|
|
110
|
+
const secretValue = match[2];
|
|
111
|
+
// Check entropy - random base64 usually has entropy > 4.5
|
|
112
|
+
if (this.calculateEntropy(secretValue) > 4.5 && !secretValue.includes('REDACTED')) {
|
|
113
|
+
const placeholder = `[REDACTED_HIGH_ENTROPY_SECRET]`;
|
|
114
|
+
redactedContent = redactedContent.replace(secretValue, placeholder);
|
|
115
|
+
foundSecrets.push('High Entropy Secret');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
58
119
|
return {
|
|
59
120
|
content: redactedContent,
|
|
60
121
|
found: [...new Set(foundSecrets)]
|
|
@@ -143,17 +204,17 @@ export async function checkGitRepository(repoPath) {
|
|
|
143
204
|
}
|
|
144
205
|
}
|
|
145
206
|
|
|
146
|
-
export async function scanDirectoryRecursively(dirPath, config, relativeTo = dirPath,
|
|
207
|
+
export async function scanDirectoryRecursively(dirPath, config, relativeTo = dirPath, projectTypes = null, trackConfidential = false) {
|
|
147
208
|
const files = [];
|
|
148
209
|
const confidentialFiles = [];
|
|
149
210
|
|
|
150
|
-
// Get project-specific filtering
|
|
151
|
-
if (!
|
|
211
|
+
// Get project-specific filtering for ALL detected types (polyglot monorepo support)
|
|
212
|
+
if (!projectTypes) {
|
|
152
213
|
const detection = await detectProjectType(relativeTo);
|
|
153
|
-
|
|
214
|
+
projectTypes = getAllDetectedTypes(detection);
|
|
154
215
|
}
|
|
155
216
|
|
|
156
|
-
const projectSpecific = await getProjectSpecificFiltering(
|
|
217
|
+
const projectSpecific = await getProjectSpecificFiltering(projectTypes);
|
|
157
218
|
|
|
158
219
|
// Merge project-specific filters with global config
|
|
159
220
|
const effectiveConfig = {
|
|
@@ -171,21 +232,10 @@ export async function scanDirectoryRecursively(dirPath, config, relativeTo = dir
|
|
|
171
232
|
const relativePath = path.relative(relativeTo, fullPath).replace(/\\/g, '/');
|
|
172
233
|
|
|
173
234
|
// --- GLOBAL HARD IGNORES (Zero-Config Safety) ---
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
entry.name === '.idea' ||
|
|
179
|
-
entry.name === '.vscode') {
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
} else {
|
|
183
|
-
if (entry.name === 'package-lock.json' ||
|
|
184
|
-
entry.name === 'yarn.lock' ||
|
|
185
|
-
entry.name === 'pnpm-lock.yaml' ||
|
|
186
|
-
entry.name === 'go.sum') {
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
235
|
+
if (entry.isDirectory() && GLOBAL_HARD_IGNORE_DIRS.includes(entry.name)) {
|
|
236
|
+
continue;
|
|
237
|
+
} else if (!entry.isDirectory() && GLOBAL_HARD_IGNORE_FILES.includes(entry.name)) {
|
|
238
|
+
continue;
|
|
189
239
|
}
|
|
190
240
|
// -----------------------------------------------
|
|
191
241
|
|
|
@@ -205,7 +255,7 @@ export async function scanDirectoryRecursively(dirPath, config, relativeTo = dir
|
|
|
205
255
|
}
|
|
206
256
|
|
|
207
257
|
if (entry.isDirectory()) {
|
|
208
|
-
const subResult = await scanDirectoryRecursively(fullPath, effectiveConfig, relativeTo,
|
|
258
|
+
const subResult = await scanDirectoryRecursively(fullPath, effectiveConfig, relativeTo, projectTypes, trackConfidential);
|
|
209
259
|
if (trackConfidential) {
|
|
210
260
|
files.push(...subResult.files);
|
|
211
261
|
confidentialFiles.push(...subResult.confidentialFiles);
|
|
@@ -241,14 +291,23 @@ export async function scanDirectoryRecursively(dirPath, config, relativeTo = dir
|
|
|
241
291
|
}
|
|
242
292
|
|
|
243
293
|
export async function loadGitignore(repoPath) {
|
|
294
|
+
const ig = ignore();
|
|
295
|
+
|
|
244
296
|
try {
|
|
245
297
|
const gitignoreContent = await fs.readFile(path.join(repoPath, '.gitignore'), 'utf-8');
|
|
246
|
-
|
|
247
|
-
return ig;
|
|
298
|
+
ig.add(gitignoreContent);
|
|
248
299
|
} catch {
|
|
249
300
|
console.log('ℹ️ No .gitignore file found or could not be read');
|
|
250
|
-
return ignore();
|
|
251
301
|
}
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
const eckignoreContent = await fs.readFile(path.join(repoPath, '.eckignore'), 'utf-8');
|
|
305
|
+
ig.add(eckignoreContent);
|
|
306
|
+
} catch {
|
|
307
|
+
// .eckignore is optional, silently skip if missing
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return ig;
|
|
252
311
|
}
|
|
253
312
|
|
|
254
313
|
export async function readFileWithSizeCheck(filePath, maxFileSize) {
|
|
@@ -279,12 +338,19 @@ export async function generateDirectoryTree(dir, prefix = '', allFiles, depth =
|
|
|
279
338
|
const validEntries = [];
|
|
280
339
|
|
|
281
340
|
for (const entry of sortedEntries) {
|
|
341
|
+
// --- GLOBAL HARD IGNORES ---
|
|
342
|
+
if (entry.isDirectory() && GLOBAL_HARD_IGNORE_DIRS.includes(entry.name)) continue;
|
|
343
|
+
if (!entry.isDirectory() && GLOBAL_HARD_IGNORE_FILES.includes(entry.name)) continue;
|
|
344
|
+
// ---------------------------
|
|
345
|
+
|
|
282
346
|
// Skip hidden directories and files (starting with '.')
|
|
283
347
|
// EXCEPT: Allow .eck to be visible
|
|
284
348
|
if (entry.name.startsWith('.') && entry.name !== '.eck') {
|
|
285
349
|
continue;
|
|
286
350
|
}
|
|
287
|
-
|
|
351
|
+
// Only skip directories (not files) and use exact name match to avoid
|
|
352
|
+
// substring false positives (e.g., "build/" hiding "build.gradle.kts")
|
|
353
|
+
if (entry.isDirectory() && config.dirsToIgnore.some(d => entry.name === d.replace('/', ''))) continue;
|
|
288
354
|
const fullPath = path.join(dir, entry.name);
|
|
289
355
|
const relativePath = path.relative(process.cwd(), fullPath).replace(/\\/g, '/');
|
|
290
356
|
|
|
@@ -598,7 +664,8 @@ export function displayProjectInfo(detection) {
|
|
|
598
664
|
}
|
|
599
665
|
|
|
600
666
|
if (detection.allDetections && detection.allDetections.length > 1) {
|
|
601
|
-
|
|
667
|
+
const otherTypes = detection.allDetections.slice(1).map(d => d.type).join(', ');
|
|
668
|
+
console.log(` Polyglot filtering: applying rules for [${detection.type}, ${otherTypes}]`);
|
|
602
669
|
}
|
|
603
670
|
|
|
604
671
|
console.log('');
|
|
@@ -635,48 +702,83 @@ function parseEnvironmentYaml(content) {
|
|
|
635
702
|
*/
|
|
636
703
|
export async function loadProjectEckManifest(repoPath) {
|
|
637
704
|
const eckDir = path.join(repoPath, '.eck');
|
|
638
|
-
|
|
705
|
+
|
|
639
706
|
try {
|
|
640
|
-
// Check if .eck directory exists
|
|
641
707
|
const eckStats = await fs.stat(eckDir);
|
|
642
708
|
if (!eckStats.isDirectory()) {
|
|
643
709
|
return null;
|
|
644
710
|
}
|
|
645
|
-
|
|
646
|
-
console.log('📋 Found .eck directory - loading project manifest...');
|
|
647
|
-
|
|
711
|
+
|
|
712
|
+
console.log('📋 Found .eck directory - dynamically loading project manifest...');
|
|
713
|
+
|
|
648
714
|
const manifest = {
|
|
649
715
|
environment: {},
|
|
650
716
|
context: '',
|
|
651
717
|
operations: '',
|
|
652
718
|
journal: '',
|
|
653
719
|
roadmap: '',
|
|
654
|
-
techDebt: ''
|
|
720
|
+
techDebt: '',
|
|
721
|
+
dynamicFiles: {}
|
|
655
722
|
};
|
|
656
723
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
724
|
+
const entries = await fs.readdir(eckDir, { withFileTypes: true });
|
|
725
|
+
|
|
726
|
+
for (const entry of entries) {
|
|
727
|
+
if (!entry.isFile() || !entry.name.endsWith('.md')) {
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// Ignore secret/credential files AND internal tool files
|
|
732
|
+
const lower = entry.name.toLowerCase();
|
|
733
|
+
if (
|
|
734
|
+
lower.includes('secret') ||
|
|
735
|
+
lower.includes('credential') ||
|
|
736
|
+
lower.includes('server_access') ||
|
|
737
|
+
entry.name === 'profile_generation_guide.md'
|
|
738
|
+
) {
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const filePath = path.join(eckDir, entry.name);
|
|
670
743
|
try {
|
|
671
744
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
672
|
-
|
|
673
|
-
|
|
745
|
+
const cleanContent = content.trim();
|
|
746
|
+
|
|
747
|
+
// Map well-known files to their dedicated manifest keys
|
|
748
|
+
switch (entry.name) {
|
|
749
|
+
case 'ENVIRONMENT.md':
|
|
750
|
+
try {
|
|
751
|
+
manifest.environment = parseEnvironmentYaml(cleanContent);
|
|
752
|
+
} catch (e) {
|
|
753
|
+
manifest.dynamicFiles[entry.name] = cleanContent;
|
|
754
|
+
}
|
|
755
|
+
break;
|
|
756
|
+
case 'CONTEXT.md':
|
|
757
|
+
manifest.context = cleanContent;
|
|
758
|
+
break;
|
|
759
|
+
case 'OPERATIONS.md':
|
|
760
|
+
manifest.operations = cleanContent;
|
|
761
|
+
break;
|
|
762
|
+
case 'JOURNAL.md':
|
|
763
|
+
manifest.journal = cleanContent;
|
|
764
|
+
break;
|
|
765
|
+
case 'ROADMAP.md':
|
|
766
|
+
manifest.roadmap = cleanContent;
|
|
767
|
+
break;
|
|
768
|
+
case 'TECH_DEBT.md':
|
|
769
|
+
manifest.techDebt = cleanContent;
|
|
770
|
+
break;
|
|
771
|
+
default:
|
|
772
|
+
// All other .md files (ARCHITECTURE, RUNTIME_STATE, DEPLOY_CHECKLIST, etc.)
|
|
773
|
+
manifest.dynamicFiles[entry.name] = cleanContent;
|
|
774
|
+
break;
|
|
775
|
+
}
|
|
776
|
+
console.log(` ✅ Loaded ${entry.name}`);
|
|
674
777
|
} catch (error) {
|
|
675
|
-
|
|
676
|
-
console.log(` ⚠️ ${file.name} not found or unreadable`);
|
|
778
|
+
console.log(` ⚠️ ${entry.name} not found or unreadable`);
|
|
677
779
|
}
|
|
678
780
|
}
|
|
679
|
-
|
|
781
|
+
|
|
680
782
|
return manifest;
|
|
681
783
|
} catch (error) {
|
|
682
784
|
// .eck directory doesn't exist - that's normal
|
|
@@ -838,14 +940,11 @@ export async function applyProfileFilter(allFiles, profileString, repoPath) {
|
|
|
838
940
|
export async function initializeEckManifest(projectPath) {
|
|
839
941
|
const eckDir = path.join(projectPath, '.eck');
|
|
840
942
|
|
|
841
|
-
|
|
842
|
-
let aiGenerationEnabled = false;
|
|
943
|
+
let setupConfig = null;
|
|
843
944
|
try {
|
|
844
|
-
|
|
845
|
-
aiGenerationEnabled = setupConfig?.aiInstructions?.manifestInitialization?.aiGenerationEnabled ?? false;
|
|
945
|
+
setupConfig = await loadSetupConfig();
|
|
846
946
|
} catch (error) {
|
|
847
|
-
// If setup config fails to load,
|
|
848
|
-
console.warn(` ⚠️ Could not load setup config: ${error.message}. AI generation disabled.`);
|
|
947
|
+
// If setup config fails to load, continue with defaults
|
|
849
948
|
}
|
|
850
949
|
|
|
851
950
|
try {
|
|
@@ -879,82 +978,45 @@ export async function initializeEckManifest(projectPath) {
|
|
|
879
978
|
await fs.mkdir(eckDir, { recursive: true });
|
|
880
979
|
console.log('📋 Initializing .eck manifest directory...');
|
|
881
980
|
|
|
882
|
-
//
|
|
883
|
-
// 1. Run static analysis first to gather facts.
|
|
981
|
+
// Gather basic project info for stub templates
|
|
884
982
|
let staticFacts = {};
|
|
885
983
|
try {
|
|
886
984
|
staticFacts = await detectProjectType(projectPath);
|
|
887
|
-
console.log(` 🔍 Static analysis complete. Detected type: ${staticFacts.type}`);
|
|
888
985
|
} catch (e) {
|
|
889
|
-
|
|
986
|
+
// Non-critical
|
|
890
987
|
}
|
|
891
988
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
const staticFactsJson = JSON.stringify(staticFacts, null, 2);
|
|
898
|
-
// --- END NEW LOGIC ---
|
|
989
|
+
const projName = setupConfig?.projectContext?.name || path.basename(projectPath) || 'project';
|
|
990
|
+
const projType = setupConfig?.projectContext?.type || staticFacts.type || 'unknown';
|
|
991
|
+
const projStack = (setupConfig?.projectContext?.architecture?.stack?.length > 0)
|
|
992
|
+
? setupConfig.projectContext.architecture.stack.join(', ')
|
|
993
|
+
: 'TBD';
|
|
899
994
|
|
|
900
|
-
//
|
|
995
|
+
// Static stub templates — AI agents fill these in via their AGENTS.md/CLAUDE.md instructions
|
|
901
996
|
const templateConfigs = {
|
|
902
997
|
'ENVIRONMENT.md': {
|
|
903
|
-
|
|
904
|
-
fallback: `project_type: ${staticFacts.type || 'unknown'}
|
|
905
|
-
# [STUB: ENVIRONMENT.MD]
|
|
906
|
-
# ARCHITECT: Task Coder to verify environment variables.
|
|
907
|
-
# CODER: Scan project for .env files or config files and fill this. Remove this stub notice.
|
|
908
|
-
agent_id: local_dev
|
|
909
|
-
`
|
|
998
|
+
content: `project_type: ${projType}\nagent_id: local_dev\n`
|
|
910
999
|
},
|
|
911
1000
|
'CONTEXT.md': {
|
|
912
|
-
|
|
913
|
-
fallback: `# [STUB: CONTEXT.MD]
|
|
914
|
-
|
|
915
|
-
## 🚨 ATTENTION ARCHITECT & CODER
|
|
916
|
-
**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.
|
|
917
|
-
**CODER:** Use your tools to read the code, write the real content, and DELETE this entire stub header.
|
|
918
|
-
|
|
919
|
-
## Description
|
|
920
|
-
(Placeholder: A ${staticFacts.type || 'project'} project)
|
|
921
|
-
|
|
922
|
-
## Architecture
|
|
923
|
-
(Placeholder: TBD)`
|
|
1001
|
+
content: `# [STUB: CONTEXT.md]\n\nProject: ${projName} | Type: ${projType} | Stack: ${projStack}\n\nAI Agent: please update this file based on your analysis of the codebase.\n`
|
|
924
1002
|
},
|
|
925
1003
|
'OPERATIONS.md': {
|
|
926
|
-
|
|
927
|
-
fallback: `# [STUB: OPERATIONS.MD]
|
|
928
|
-
|
|
929
|
-
## 🚨 ATTENTION
|
|
930
|
-
**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.
|
|
931
|
-
|
|
932
|
-
## Setup
|
|
933
|
-
${staticFacts.type === 'nodejs' ? 'npm install' : 'TBD'}`
|
|
1004
|
+
content: `# [STUB: OPERATIONS.md]\n\nAI Agent: please update with actual setup, run, and test commands.\n\n## Setup\n${staticFacts.type === 'nodejs' ? '\\`\\`\\`bash\\nnpm install\\n\\`\\`\\`' : 'TBD'}\n`
|
|
934
1005
|
},
|
|
935
1006
|
'ROADMAP.md': {
|
|
936
|
-
|
|
937
|
-
fallback: `# [STUB: ROADMAP.MD]
|
|
938
|
-
|
|
939
|
-
**ARCHITECT:** Set a real roadmap based on user goals. **CODER:** Remove this stub marker once a real goal is added.`
|
|
1007
|
+
content: `# [STUB: ROADMAP.md]\n\nAI Agent: please set a real roadmap based on project goals.\n`
|
|
940
1008
|
},
|
|
941
1009
|
'TECH_DEBT.md': {
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1010
|
+
content: `# [STUB: TECH_DEBT.md]\n\nAI Agent: please scan for TODOs/FIXMEs or structural issues and list them here.\n`
|
|
1011
|
+
},
|
|
1012
|
+
'DEPLOY_CHECKLIST.md': {
|
|
1013
|
+
content: `# [STUB: DEPLOY_CHECKLIST.md]\n\n- [ ] Verify all tests pass\n- [ ] Build assets\n- [ ] Check environment variables\n\nAI Agent: please update with actual deploy steps.\n`
|
|
1014
|
+
},
|
|
1015
|
+
'RUNTIME_STATE.md': {
|
|
1016
|
+
content: `# [STUB: RUNTIME_STATE.md]\n\nAI Agent: check actual runtime state (ports, processes, env vars) and update this file.\n\n- **Server:** TBD\n- **Services:** TBD\n`
|
|
946
1017
|
},
|
|
947
1018
|
'JOURNAL.md': {
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
## Recent Changes
|
|
951
|
-
---
|
|
952
|
-
type: feat
|
|
953
|
-
scope: project
|
|
954
|
-
summary: Initial manifest generated (PENDING REVIEW)
|
|
955
|
-
date: ${new Date().toISOString().split('T')[0]}
|
|
956
|
-
---
|
|
957
|
-
- NOTICE: Some .eck files are STUBS. They need manual or AI-assisted verification.`
|
|
1019
|
+
content: `# Development Journal\n\n## Recent Changes\n---\ntype: feat\nscope: project\nsummary: Initial manifest generated (PENDING REVIEW)\ndate: ${new Date().toISOString().split('T')[0]}\n---\n- NOTICE: Some .eck files are STUBS. They need manual or AI-assisted verification.\n`
|
|
958
1020
|
}
|
|
959
1021
|
};
|
|
960
1022
|
|
|
@@ -971,40 +1033,8 @@ date: ${new Date().toISOString().split('T')[0]}
|
|
|
971
1033
|
// File doesn't exist, create it
|
|
972
1034
|
}
|
|
973
1035
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
// For files with a prompt, try to dynamically generate (only if enabled)
|
|
978
|
-
if (config.prompt && aiGenerationEnabled) {
|
|
979
|
-
try {
|
|
980
|
-
console.log(` 🧠 Attempting to auto-generate ${fileName} via Claude...`);
|
|
981
|
-
const aiResponseObject = await askClaude(config.prompt);
|
|
982
|
-
const rawText = aiResponseObject.result;
|
|
983
|
-
|
|
984
|
-
if (!rawText || typeof rawText.replace !== 'function') {
|
|
985
|
-
throw new Error(`AI returned invalid content type: ${typeof rawText}`);
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
// Basic cleanup of potential markdown code blocks from Claude
|
|
989
|
-
const cleanedResponse = rawText.replace(/^```(markdown|yaml)?\n|```$/g, '').trim();
|
|
990
|
-
|
|
991
|
-
if (cleanedResponse) {
|
|
992
|
-
fileContent = cleanedResponse;
|
|
993
|
-
generatedByAI = true;
|
|
994
|
-
console.log(` ✨ AI successfully generated ${fileName}`);
|
|
995
|
-
} else {
|
|
996
|
-
throw new Error('AI returned empty content.');
|
|
997
|
-
}
|
|
998
|
-
} catch (error) {
|
|
999
|
-
console.warn(` ⚠️ AI generation failed for ${fileName}: ${error.message}. Using stub template.`);
|
|
1000
|
-
// fileContent is already set to the stub fallback
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
await fs.writeFile(filePath, fileContent);
|
|
1005
|
-
if (!generatedByAI) {
|
|
1006
|
-
console.log(` ✅ Created ${fileName} (stub template)`);
|
|
1007
|
-
}
|
|
1036
|
+
await fs.writeFile(filePath, config.content);
|
|
1037
|
+
console.log(` ✅ Created ${fileName} (stub template)`);
|
|
1008
1038
|
}
|
|
1009
1039
|
|
|
1010
1040
|
console.log('📋 .eck manifest initialized! Edit the files to provide project-specific context.');
|
|
@@ -1014,3 +1044,41 @@ date: ${new Date().toISOString().split('T')[0]}
|
|
|
1014
1044
|
console.warn(`⚠️ Warning: Could not initialize .eck manifest: ${error.message}`);
|
|
1015
1045
|
}
|
|
1016
1046
|
}
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
function isHiddenPath(filePath) {
|
|
1051
|
+
const parts = filePath.split('/');
|
|
1052
|
+
return parts.some(part => part.startsWith('.') && part !== '.eck');
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
* Gets project files using git ls-files (preferred) or scanDirectoryRecursively as fallback.
|
|
1057
|
+
* Applies global hard-ignore filters and project-specific filtering.
|
|
1058
|
+
*/
|
|
1059
|
+
export async function getProjectFiles(projectPath, config) {
|
|
1060
|
+
const isGitRepo = await checkGitRepository(projectPath);
|
|
1061
|
+
if (isGitRepo) {
|
|
1062
|
+
const { stdout } = await execa('git', ['ls-files'], { cwd: projectPath });
|
|
1063
|
+
const gitFiles = stdout.split('\n').filter(Boolean);
|
|
1064
|
+
|
|
1065
|
+
const dirsToIgnore = [...GLOBAL_HARD_IGNORE_DIRS, ...(config.dirsToIgnore || []).map(d => d.replace(/\/$/, ''))];
|
|
1066
|
+
const filesToIgnore = [...GLOBAL_HARD_IGNORE_FILES, ...(config.filesToIgnore || [])];
|
|
1067
|
+
const extensionsToIgnore = config.extensionsToIgnore || [];
|
|
1068
|
+
|
|
1069
|
+
const filteredFiles = gitFiles.filter(file => {
|
|
1070
|
+
if (isHiddenPath(file)) return false;
|
|
1071
|
+
const fileName = file.split('/').pop();
|
|
1072
|
+
const fileExt = path.extname(fileName);
|
|
1073
|
+
const pathParts = file.split('/');
|
|
1074
|
+
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
1075
|
+
if (dirsToIgnore.includes(pathParts[i])) return false;
|
|
1076
|
+
}
|
|
1077
|
+
if (filesToIgnore.includes(fileName)) return false;
|
|
1078
|
+
if (fileExt && extensionsToIgnore.includes(fileExt)) return false;
|
|
1079
|
+
return true;
|
|
1080
|
+
});
|
|
1081
|
+
return filteredFiles;
|
|
1082
|
+
}
|
|
1083
|
+
return scanDirectoryRecursively(projectPath, config);
|
|
1084
|
+
}
|
package/src/utils/gitUtils.js
CHANGED
|
@@ -25,14 +25,18 @@ export async function getGitAnchor(repoPath) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export async function getChangedFiles(repoPath, anchorHash) {
|
|
29
|
-
try {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
export async function getChangedFiles(repoPath, anchorHash, includeWorkingTree = false) {
|
|
29
|
+
try {
|
|
30
|
+
const args = ['diff', '--name-only', anchorHash];
|
|
31
|
+
if (!includeWorkingTree) {
|
|
32
|
+
args.push('HEAD');
|
|
33
|
+
}
|
|
34
|
+
const { stdout } = await execa('git', args, { cwd: repoPath });
|
|
35
|
+
return stdout.split('\n').filter(Boolean);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
throw new Error(`Failed to get git diff: ${e.message}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
36
40
|
|
|
37
41
|
export async function getGitDiffOutput(repoPath, anchorHash, excludeFiles = []) {
|
|
38
42
|
try {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
9
|
* Generates AGENTS.md for OpenCode integration (GLM Z.AI ecosystem only)
|
|
@@ -30,11 +34,12 @@ export async function generateOpenCodeAgents(repoPath, mode, tree, confidentialF
|
|
|
30
34
|
color: '#10a37f'
|
|
31
35
|
};
|
|
32
36
|
|
|
33
|
-
const templatePath = path.join(
|
|
37
|
+
const templatePath = path.join(__dirname, '..', 'templates', 'opencode', 'junior-architect.template.md');
|
|
34
38
|
try {
|
|
35
39
|
let templateContent = await fs.readFile(templatePath, 'utf-8');
|
|
36
40
|
body = templateContent.replace('{{tree}}', tree);
|
|
37
41
|
} catch (error) {
|
|
42
|
+
console.warn(`⚠️ Could not load JAZ template from ${templatePath}: ${error.message}`);
|
|
38
43
|
body = `# 🧠 ROLE: Swarm Orchestrator (GLM-4.7)\n\nDirectory:\n\`\`\`\n${tree}\n\`\`\``;
|
|
39
44
|
}
|
|
40
45
|
} else {
|
|
@@ -48,10 +53,11 @@ export async function generateOpenCodeAgents(repoPath, mode, tree, confidentialF
|
|
|
48
53
|
color: '#44BA81'
|
|
49
54
|
};
|
|
50
55
|
|
|
51
|
-
const templatePath = path.join(
|
|
56
|
+
const templatePath = path.join(__dirname, '..', 'templates', 'opencode', 'coder.template.md');
|
|
52
57
|
try {
|
|
53
58
|
body = await fs.readFile(templatePath, 'utf-8');
|
|
54
59
|
} catch (error) {
|
|
60
|
+
console.warn(`⚠️ Could not load Coder template from ${templatePath}: ${error.message}`);
|
|
55
61
|
body = `# 🛠️ ROLE: Expert Developer`;
|
|
56
62
|
}
|
|
57
63
|
}
|