@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.
Files changed (37) hide show
  1. package/README.md +321 -190
  2. package/index.js +1 -1
  3. package/package.json +15 -2
  4. package/scripts/mcp-eck-core.js +143 -13
  5. package/setup.json +119 -81
  6. package/src/cli/cli.js +256 -385
  7. package/src/cli/commands/createSnapshot.js +391 -175
  8. package/src/cli/commands/recon.js +308 -0
  9. package/src/cli/commands/setupMcp.js +280 -19
  10. package/src/cli/commands/trainTokens.js +42 -32
  11. package/src/cli/commands/updateSnapshot.js +136 -43
  12. package/src/core/depthConfig.js +54 -0
  13. package/src/core/skeletonizer.js +280 -21
  14. package/src/templates/architect-prompt.template.md +34 -0
  15. package/src/templates/multiAgent.md +68 -15
  16. package/src/templates/opencode/coder.template.md +53 -17
  17. package/src/templates/opencode/junior-architect.template.md +54 -15
  18. package/src/templates/skeleton-instruction.md +1 -1
  19. package/src/templates/update-prompt.template.md +2 -0
  20. package/src/utils/aiHeader.js +57 -27
  21. package/src/utils/claudeMdGenerator.js +182 -88
  22. package/src/utils/fileUtils.js +217 -149
  23. package/src/utils/gitUtils.js +12 -8
  24. package/src/utils/opencodeAgentsGenerator.js +8 -2
  25. package/src/utils/projectDetector.js +66 -21
  26. package/src/utils/tokenEstimator.js +11 -7
  27. package/src/cli/commands/consilium.js +0 -86
  28. package/src/cli/commands/detectProfiles.js +0 -98
  29. package/src/cli/commands/envSync.js +0 -319
  30. package/src/cli/commands/generateProfileGuide.js +0 -144
  31. package/src/cli/commands/pruneSnapshot.js +0 -106
  32. package/src/cli/commands/restoreSnapshot.js +0 -173
  33. package/src/cli/commands/setupGemini.js +0 -149
  34. package/src/cli/commands/setupGemini.test.js +0 -115
  35. package/src/cli/commands/showFile.js +0 -39
  36. package/src/services/claudeCliService.js +0 -626
  37. package/src/services/claudeCliService.test.js +0 -267
@@ -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, projectType = null, trackConfidential = false) {
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 if not provided
151
- if (!projectType) {
211
+ // Get project-specific filtering for ALL detected types (polyglot monorepo support)
212
+ if (!projectTypes) {
152
213
  const detection = await detectProjectType(relativeTo);
153
- projectType = detection.type;
214
+ projectTypes = getAllDetectedTypes(detection);
154
215
  }
155
216
 
156
- const projectSpecific = await getProjectSpecificFiltering(projectType);
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
- // Explicitly skip heavy/system directories and lockfiles everywhere
175
- if (entry.isDirectory()) {
176
- if (entry.name === 'node_modules' ||
177
- entry.name === '.git' ||
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, projectType, trackConfidential);
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
- const ig = ignore().add(gitignoreContent);
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
- if (config.dirsToIgnore.some(d => entry.name.includes(d.replace('/', '')))) continue;
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
- console.log(` Other possibilities: ${detection.allDetections.slice(1).map(d => d.type).join(', ')}`);
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
- // Define the files to check
658
- const files = [
659
- { name: 'ENVIRONMENT.md', key: 'environment', parser: parseEnvironmentYaml },
660
- { name: 'CONTEXT.md', key: 'context', parser: content => content },
661
- { name: 'OPERATIONS.md', key: 'operations', parser: content => content },
662
- { name: 'JOURNAL.md', key: 'journal', parser: content => content },
663
- { name: 'ROADMAP.md', key: 'roadmap', parser: content => content },
664
- { name: 'TECH_DEBT.md', key: 'techDebt', parser: content => content }
665
- ];
666
-
667
- // Process each file
668
- for (const file of files) {
669
- const filePath = path.join(eckDir, file.name);
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
- manifest[file.key] = file.parser(content.trim());
673
- console.log(` ✅ Loaded ${file.name}`);
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
- // File doesn't exist or can't be read - that's okay, use default
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
- // Load setup configuration to check AI generation settings
842
- let aiGenerationEnabled = false;
943
+ let setupConfig = null;
843
944
  try {
844
- const setupConfig = await loadSetupConfig();
845
- aiGenerationEnabled = setupConfig?.aiInstructions?.manifestInitialization?.aiGenerationEnabled ?? false;
945
+ setupConfig = await loadSetupConfig();
846
946
  } catch (error) {
847
- // If setup config fails to load, default to disabled
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
- // --- NEW HYBRID LOGIC ---
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
- console.warn(` ⚠️ Static project detection failed: ${e.message}. Proceeding with generic prompts.`);
986
+ // Non-critical
890
987
  }
891
988
 
892
- // Prevent AI hallucination by removing low-confidence "other possibilities"
893
- if (staticFacts && staticFacts.allDetections) {
894
- delete staticFacts.allDetections;
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
- // 3. Define smarter templates and prompts with "STUB NOTICES"
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
- 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.`,
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
- 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'.`,
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
- 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'.`,
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
- prompt: `Based on the project type (${staticFacts.type}), propose a 3-step roadmap. Start with '# Project Roadmap'.`,
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
- prompt: `Given this is a ${staticFacts.type} project, list 2-3 common technical debt items. Start with '# Technical Debt'.`,
943
- fallback: `# [STUB: TECH_DEBT.MD]
944
-
945
- **CODER:** Scan for TODOs/FIXMEs or structural issues and list them here. Remove this stub marker.`
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
- fallback: `# Development Journal
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
- let fileContent = config.fallback; // Start with stub fallback
975
- let generatedByAI = false;
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
+ }
@@ -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 { stdout } = await execa('git', ['diff', '--name-only', anchorHash, 'HEAD'], { cwd: repoPath });
31
- return stdout.split('\n').filter(Boolean);
32
- } catch (e) {
33
- throw new Error(`Failed to get git diff: ${e.message}`);
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(repoPath, '..', '..', 'src', 'templates', 'opencode', 'junior-architect.template.md');
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(repoPath, '..', '..', 'src', 'templates', 'opencode', 'coder.template.md');
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
  }