@xelth/eck-snapshot 5.9.0 ā 6.4.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.
Potentially problematic release.
This version of @xelth/eck-snapshot might be problematic. Click here for more details.
- package/README.md +267 -190
- package/package.json +15 -2
- package/scripts/mcp-eck-core.js +61 -13
- package/setup.json +114 -80
- package/src/cli/cli.js +235 -385
- package/src/cli/commands/createSnapshot.js +336 -122
- package/src/cli/commands/recon.js +244 -0
- package/src/cli/commands/setupMcp.js +278 -19
- package/src/cli/commands/trainTokens.js +42 -32
- package/src/cli/commands/updateSnapshot.js +128 -76
- package/src/core/depthConfig.js +54 -0
- package/src/core/skeletonizer.js +71 -18
- package/src/templates/architect-prompt.template.md +34 -0
- package/src/templates/multiAgent.md +18 -10
- package/src/templates/opencode/coder.template.md +44 -17
- package/src/templates/opencode/junior-architect.template.md +45 -15
- package/src/templates/skeleton-instruction.md +1 -1
- package/src/utils/aiHeader.js +57 -27
- package/src/utils/claudeMdGenerator.js +136 -78
- package/src/utils/fileUtils.js +1011 -1016
- 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
|
@@ -17,14 +17,16 @@ import {
|
|
|
17
17
|
ensureSnapshotsInGitignore, initializeEckManifest, generateTimestamp,
|
|
18
18
|
getShortRepoName, SecretScanner
|
|
19
19
|
} from '../../utils/fileUtils.js';
|
|
20
|
-
import { detectProjectType, getProjectSpecificFiltering } from '../../utils/projectDetector.js';
|
|
20
|
+
import { detectProjectType, getProjectSpecificFiltering, getAllDetectedTypes } from '../../utils/projectDetector.js';
|
|
21
21
|
import { estimateTokensWithPolynomial, generateTrainingCommand } from '../../utils/tokenEstimator.js';
|
|
22
22
|
import { loadSetupConfig, getProfile } from '../../config.js';
|
|
23
23
|
import { applyProfileFilter } from '../../utils/fileUtils.js';
|
|
24
24
|
import { saveGitAnchor } from '../../utils/gitUtils.js';
|
|
25
25
|
import { skeletonize } from '../../core/skeletonizer.js';
|
|
26
|
+
import { getDepthConfig } from '../../core/depthConfig.js';
|
|
26
27
|
import { updateClaudeMd } from '../../utils/claudeMdGenerator.js';
|
|
27
28
|
import { generateOpenCodeAgents } from '../../utils/opencodeAgentsGenerator.js';
|
|
29
|
+
import { ensureProjectMcpConfig, ensureProjectOpenCodeConfig, ensureProjectCodexConfig } from './setupMcp.js';
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
32
|
* Creates dynamic project context based on detection results
|
|
@@ -254,14 +256,14 @@ async function getGitCommitHash(projectPath) {
|
|
|
254
256
|
return null;
|
|
255
257
|
}
|
|
256
258
|
|
|
257
|
-
async function estimateProjectTokens(projectPath, config,
|
|
259
|
+
async function estimateProjectTokens(projectPath, config, projectTypes = null) {
|
|
258
260
|
// Get project-specific filtering if not provided
|
|
259
|
-
if (!
|
|
261
|
+
if (!projectTypes) {
|
|
260
262
|
const detection = await detectProjectType(projectPath);
|
|
261
|
-
|
|
263
|
+
projectTypes = getAllDetectedTypes(detection);
|
|
262
264
|
}
|
|
263
265
|
|
|
264
|
-
const projectSpecific = await getProjectSpecificFiltering(
|
|
266
|
+
const projectSpecific = await getProjectSpecificFiltering(projectTypes);
|
|
265
267
|
|
|
266
268
|
// Merge project-specific filters with global config (same as in scanDirectoryRecursively)
|
|
267
269
|
const effectiveConfig = {
|
|
@@ -314,15 +316,17 @@ async function estimateProjectTokens(projectPath, config, projectType = null) {
|
|
|
314
316
|
}
|
|
315
317
|
|
|
316
318
|
// Use adaptive polynomial estimation
|
|
317
|
-
|
|
319
|
+
// Token estimation uses the primary type for coefficient lookup
|
|
320
|
+
const primaryType = Array.isArray(projectTypes) ? projectTypes[0] : projectTypes;
|
|
321
|
+
const estimatedTokens = await estimateTokensWithPolynomial(primaryType, totalSize);
|
|
318
322
|
|
|
319
323
|
return { estimatedTokens, totalSize, includedFiles };
|
|
320
324
|
}
|
|
321
325
|
|
|
322
|
-
async function processProjectFiles(repoPath, options, config,
|
|
323
|
-
// Merge project-specific filtering rules (
|
|
324
|
-
if (
|
|
325
|
-
const projectSpecific = await getProjectSpecificFiltering(
|
|
326
|
+
async function processProjectFiles(repoPath, options, config, projectTypes = null) {
|
|
327
|
+
// Merge project-specific filtering rules for ALL detected types (polyglot monorepo support)
|
|
328
|
+
if (projectTypes) {
|
|
329
|
+
const projectSpecific = await getProjectSpecificFiltering(projectTypes);
|
|
326
330
|
config = {
|
|
327
331
|
...config,
|
|
328
332
|
dirsToIgnore: [...(config.dirsToIgnore || []), ...(projectSpecific.dirsToIgnore || [])],
|
|
@@ -371,7 +375,7 @@ async function processProjectFiles(repoPath, options, config, projectType = null
|
|
|
371
375
|
|
|
372
376
|
if (filterResult.notFoundProfiles.length > 0) {
|
|
373
377
|
errorMsg += `\n\nā Profile(s) not found: ${filterResult.notFoundProfiles.join(', ')}`;
|
|
374
|
-
errorMsg += `\n\nš” Run 'eck-snapshot --profile' to see available profiles.`;
|
|
378
|
+
errorMsg += `\n\nš” Run 'eck-snapshot snapshot --profile' to see available profiles.`;
|
|
375
379
|
errorMsg += `\n Or run 'eck-snapshot generate-profile-guide' to create profiles.`;
|
|
376
380
|
} else if (filterResult.foundProfiles.length > 0) {
|
|
377
381
|
errorMsg += `\n\nā Profile(s) found: ${filterResult.foundProfiles.join(', ')}`;
|
|
@@ -483,7 +487,7 @@ async function processProjectFiles(repoPath, options, config, projectType = null
|
|
|
483
487
|
// Check if file should be focused (kept full)
|
|
484
488
|
const isFocused = options.focus && micromatch.isMatch(normalizedPath, options.focus);
|
|
485
489
|
if (!isFocused) {
|
|
486
|
-
content = await skeletonize(content, normalizedPath);
|
|
490
|
+
content = await skeletonize(content, normalizedPath, { preserveDocs: options.preserveDocs !== false });
|
|
487
491
|
}
|
|
488
492
|
}
|
|
489
493
|
|
|
@@ -531,7 +535,65 @@ async function processProjectFiles(repoPath, options, config, projectType = null
|
|
|
531
535
|
}
|
|
532
536
|
}
|
|
533
537
|
|
|
538
|
+
/**
|
|
539
|
+
* Groups files by directory and packs them into chunks of a given maximum size.
|
|
540
|
+
* Preserves directory locality for better RAG retrieval in NotebookLM.
|
|
541
|
+
*/
|
|
542
|
+
function packFilesForNotebookLM(successfulFileObjects, maxChunkSizeBytes = 2.5 * 1024 * 1024) {
|
|
543
|
+
const dirGroups = new Map();
|
|
544
|
+
|
|
545
|
+
for (const fileObj of successfulFileObjects) {
|
|
546
|
+
const dir = fileObj.path.includes('/') ? fileObj.path.substring(0, fileObj.path.lastIndexOf('/')) : './';
|
|
547
|
+
if (!dirGroups.has(dir)) dirGroups.set(dir, { size: 0, files: [] });
|
|
548
|
+
const group = dirGroups.get(dir);
|
|
549
|
+
group.files.push(fileObj);
|
|
550
|
+
group.size += fileObj.size;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const sortedDirs = Array.from(dirGroups.keys()).sort();
|
|
554
|
+
const chunks = [];
|
|
555
|
+
let currentChunk = { size: 0, contentArray: [] };
|
|
556
|
+
|
|
557
|
+
for (const dir of sortedDirs) {
|
|
558
|
+
const group = dirGroups.get(dir);
|
|
559
|
+
|
|
560
|
+
if (group.size > maxChunkSizeBytes) {
|
|
561
|
+
// Directory too large ā pack files individually
|
|
562
|
+
for (const fileObj of group.files) {
|
|
563
|
+
if (currentChunk.size + fileObj.size > maxChunkSizeBytes && currentChunk.contentArray.length > 0) {
|
|
564
|
+
chunks.push(currentChunk);
|
|
565
|
+
currentChunk = { size: 0, contentArray: [] };
|
|
566
|
+
}
|
|
567
|
+
currentChunk.contentArray.push(fileObj.content);
|
|
568
|
+
currentChunk.size += fileObj.size;
|
|
569
|
+
}
|
|
570
|
+
} else if (currentChunk.size + group.size <= maxChunkSizeBytes) {
|
|
571
|
+
// Directory fits in current chunk
|
|
572
|
+
for (const f of group.files) currentChunk.contentArray.push(f.content);
|
|
573
|
+
currentChunk.size += group.size;
|
|
574
|
+
} else {
|
|
575
|
+
// Start a new chunk
|
|
576
|
+
chunks.push(currentChunk);
|
|
577
|
+
currentChunk = { size: 0, contentArray: [] };
|
|
578
|
+
for (const f of group.files) currentChunk.contentArray.push(f.content);
|
|
579
|
+
currentChunk.size += group.size;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (currentChunk.contentArray.length > 0) chunks.push(currentChunk);
|
|
584
|
+
return chunks;
|
|
585
|
+
}
|
|
586
|
+
|
|
534
587
|
export async function createRepoSnapshot(repoPath, options) {
|
|
588
|
+
// Handle linked project depth settings before processing
|
|
589
|
+
if (options.isLinkedProject) {
|
|
590
|
+
const depthCfg = getDepthConfig(options.linkDepth !== undefined ? options.linkDepth : 0);
|
|
591
|
+
if (depthCfg.skipContent) options.skipContent = true;
|
|
592
|
+
if (depthCfg.skeleton !== undefined) options.skeleton = depthCfg.skeleton;
|
|
593
|
+
if (depthCfg.preserveDocs !== undefined) options.preserveDocs = depthCfg.preserveDocs;
|
|
594
|
+
if (depthCfg.maxLinesPerFile !== undefined) options.maxLinesPerFile = depthCfg.maxLinesPerFile;
|
|
595
|
+
}
|
|
596
|
+
|
|
535
597
|
const spinner = ora('Analyzing project...').start();
|
|
536
598
|
try {
|
|
537
599
|
// Ensure snapshots/ is in .gitignore to prevent accidental commits
|
|
@@ -570,7 +632,7 @@ export async function createRepoSnapshot(repoPath, options) {
|
|
|
570
632
|
// Generate ready-to-copy command with all profiles
|
|
571
633
|
const allProfilesString = profileNames.join(',');
|
|
572
634
|
console.log(chalk.cyan('š Ready-to-Copy Command (all profiles):'));
|
|
573
|
-
console.log(chalk.bold(`\neck-snapshot
|
|
635
|
+
console.log(chalk.bold(`\neck-snapshot '{"name": "eck_snapshot", "arguments": {"profile": "${allProfilesString}"}}'\n`));
|
|
574
636
|
console.log(chalk.gray('š” Tip: Copy the command above and remove profiles you don\'t need'));
|
|
575
637
|
process.exit(0);
|
|
576
638
|
} catch (error) {
|
|
@@ -629,19 +691,19 @@ export async function createRepoSnapshot(repoPath, options) {
|
|
|
629
691
|
...options // Command-line options have the final say
|
|
630
692
|
};
|
|
631
693
|
|
|
632
|
-
// Detect architect modes
|
|
633
|
-
const isJas = options.jas;
|
|
634
|
-
const isJao = options.jao;
|
|
635
|
-
const isJaz = options.jaz;
|
|
636
|
-
|
|
637
|
-
// If NOT in Junior Architect mode, hide JA-specific documentation to prevent context pollution
|
|
638
|
-
if (!options.withJa && !isJas && !isJao && !isJaz) {
|
|
639
|
-
if (!config.filesToIgnore) config.filesToIgnore = [];
|
|
640
|
-
config.filesToIgnore.push(
|
|
641
|
-
'COMMANDS_REFERENCE.md',
|
|
642
|
-
'codex_delegation_snapshot.md'
|
|
643
|
-
);
|
|
644
|
-
}
|
|
694
|
+
// Detect architect modes
|
|
695
|
+
const isJas = options.jas;
|
|
696
|
+
const isJao = options.jao;
|
|
697
|
+
const isJaz = options.jaz;
|
|
698
|
+
|
|
699
|
+
// If NOT in Junior Architect mode, hide JA-specific documentation to prevent context pollution
|
|
700
|
+
if (!options.withJa && !isJas && !isJao && !isJaz) {
|
|
701
|
+
if (!config.filesToIgnore) config.filesToIgnore = [];
|
|
702
|
+
config.filesToIgnore.push(
|
|
703
|
+
'COMMANDS_REFERENCE.md',
|
|
704
|
+
'codex_delegation_snapshot.md'
|
|
705
|
+
);
|
|
706
|
+
}
|
|
645
707
|
|
|
646
708
|
// Apply defaults for options that may not be provided via command line
|
|
647
709
|
if (!config.output) {
|
|
@@ -658,7 +720,8 @@ export async function createRepoSnapshot(repoPath, options) {
|
|
|
658
720
|
config.includeHidden = setupConfig.fileFiltering?.includeHidden ?? false;
|
|
659
721
|
}
|
|
660
722
|
|
|
661
|
-
const
|
|
723
|
+
const allTypesForEstimation = getAllDetectedTypes(projectDetection);
|
|
724
|
+
const estimation = await estimateProjectTokens(repoPath, config, allTypesForEstimation);
|
|
662
725
|
spinner.info(`Estimated project size: ~${Math.round(estimation.estimatedTokens).toLocaleString()} tokens.`);
|
|
663
726
|
|
|
664
727
|
spinner.succeed('Creating snapshots...');
|
|
@@ -669,7 +732,8 @@ export async function createRepoSnapshot(repoPath, options) {
|
|
|
669
732
|
|
|
670
733
|
let stats, contentArray, successfulFileObjects, allFiles, processedRepoPath;
|
|
671
734
|
|
|
672
|
-
const
|
|
735
|
+
const allTypes = getAllDetectedTypes(projectDetection);
|
|
736
|
+
const result = await processProjectFiles(repoPath, options, config, allTypes);
|
|
673
737
|
stats = result.stats;
|
|
674
738
|
contentArray = result.contentArray;
|
|
675
739
|
successfulFileObjects = result.successfulFileObjects;
|
|
@@ -717,86 +781,200 @@ export async function createRepoSnapshot(repoPath, options) {
|
|
|
717
781
|
let architectFilePath = null;
|
|
718
782
|
let jaFilePath = null;
|
|
719
783
|
|
|
720
|
-
//
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
const
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
//
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
784
|
+
// --- NotebookLM Chunked Export (Brain + Body) ---
|
|
785
|
+
if (options.notebooklm) {
|
|
786
|
+
const isArchitectMode = options.notebooklm === 'architect';
|
|
787
|
+
const modeName = isArchitectMode ? 'Architect' : 'Scout';
|
|
788
|
+
console.log(chalk.blue(`\nš Packing project for NotebookLM (${modeName} Mode)...`));
|
|
789
|
+
|
|
790
|
+
const chunks = packFilesForNotebookLM(successfulFileObjects);
|
|
791
|
+
const shortRepoName = getShortRepoName(repoName);
|
|
792
|
+
const absPath = processedRepoPath.replace(/\\/g, '/');
|
|
793
|
+
const filePrefix = isArchitectMode ? 'notelm' : 'booklm';
|
|
794
|
+
|
|
795
|
+
// Clean up old notebooklm chunks
|
|
796
|
+
try {
|
|
797
|
+
const existingFiles = await fs.readdir(outputPath);
|
|
798
|
+
for (const file of existingFiles) {
|
|
799
|
+
if (file.includes('_booklm_part') || file.includes('_notelm_part')) {
|
|
800
|
+
await fs.unlink(path.join(outputPath, file));
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
} catch (e) { /* ignore */ }
|
|
804
|
+
|
|
805
|
+
// --- Part 0: The Brain (Instructions + Manifests + Tree) ---
|
|
806
|
+
let part0 = `# š§ NOTEBOOKLM KNOWLEDGE BASE ā PART 0 (THE BRAIN)\n`;
|
|
807
|
+
part0 += `**Project:** ${repoName}\n\n`;
|
|
808
|
+
|
|
809
|
+
if (isArchitectMode) {
|
|
810
|
+
part0 += `## YOUR ROLE: THE ARCHITECT\n`;
|
|
811
|
+
part0 += `You are the Senior Software Architect for this project. You have access to the entire codebase across the source documents (Parts 1-${chunks.length}).\n`;
|
|
812
|
+
part0 += `Analyze the codebase to solve complex structural problems, design new features, and propose refactoring strategies. Provide high-level guidance and precise code modifications.\n\n`;
|
|
813
|
+
} else {
|
|
814
|
+
part0 += `## YOUR ROLE: THE SCOUT\n`;
|
|
815
|
+
part0 += `You are an expert code analyst and retrieval specialist. You have access to the entire codebase across the source documents (Parts 1-${chunks.length}). Your goal is NOT to write code, but to help the primary Architect find the exact files they need.\n`;
|
|
816
|
+
part0 += `When asked about a feature, bug, or module ā analyze the project structure and codebase, then output precise bash commands using \`eck-snapshot fetch\` so the user can extract the relevant files for their Architect agent.\n\n`;
|
|
817
|
+
part0 += `**RULES FOR FETCH COMMANDS:**\n`;
|
|
818
|
+
part0 += `1. Always start with \`cd ${absPath}\`\n`;
|
|
819
|
+
part0 += `2. Use relative glob patterns: \`eck-snapshot fetch "**/auth.js" "**/userController.js"\`\n`;
|
|
820
|
+
part0 += `3. Output the commands in a bash code block, accompanied by a brief explanation of why you selected those files.\n`;
|
|
821
|
+
part0 += `4. Include both directly relevant files AND adjacent files that provide context (imports, shared types, config).\n\n`;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Add .eck manifests
|
|
825
|
+
if (eckManifest) {
|
|
826
|
+
part0 += `## š Project Context & Manifests\n\n`;
|
|
827
|
+
if (eckManifest.context) part0 += `### CONTEXT\n${eckManifest.context}\n\n`;
|
|
828
|
+
if (eckManifest.techDebt) part0 += `### TECH DEBT\n${eckManifest.techDebt}\n\n`;
|
|
829
|
+
if (eckManifest.roadmap) part0 += `### ROADMAP\n${eckManifest.roadmap}\n\n`;
|
|
830
|
+
if (eckManifest.operations) part0 += `### OPERATIONS\n${eckManifest.operations}\n\n`;
|
|
831
|
+
// Include any dynamic .eck files
|
|
832
|
+
if (eckManifest.dynamicFiles) {
|
|
833
|
+
for (const [name, content] of Object.entries(eckManifest.dynamicFiles)) {
|
|
834
|
+
part0 += `### ${name.replace('.md', '').toUpperCase()}\n${content}\n\n`;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Add full directory tree
|
|
840
|
+
if (directoryTree) {
|
|
841
|
+
part0 += `## š³ Global Directory Structure\n\`\`\`text\n${directoryTree}\n\`\`\`\n`;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
const part0Name = `eck_${shortRepoName}_${filePrefix}_part0_BRAIN.md`;
|
|
845
|
+
await fs.writeFile(path.join(outputPath, part0Name), part0);
|
|
846
|
+
console.log(chalk.magenta(` š§ Part 0 (Brain): ${part0Name}`));
|
|
847
|
+
|
|
848
|
+
// --- Parts 1-N: The Body (Source Code Only) ---
|
|
849
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
850
|
+
const chunk = chunks[i];
|
|
851
|
+
const header = `--- NOTEBOOKLM SOURCE CODE ā PART ${i + 1} OF ${chunks.length} ---\n\n`;
|
|
852
|
+
const body = header + chunk.contentArray.join('');
|
|
853
|
+
|
|
854
|
+
const fname = `eck_${shortRepoName}_${filePrefix}_part${i + 1}.md`;
|
|
855
|
+
await fs.writeFile(path.join(outputPath, fname), body);
|
|
856
|
+
console.log(chalk.cyan(` š Part ${i + 1}/${chunks.length}: ${fname} (${formatSize(chunk.size)})`));
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
console.log(chalk.green(`\nā
NotebookLM export complete: 1 Brain + ${chunks.length} Source chunk(s) in ${outputPath}`));
|
|
860
|
+
console.log(chalk.gray(` Upload all files as separate sources in NotebookLM (max 50 sources).`));
|
|
861
|
+
|
|
862
|
+
// Starter prompt for NotebookLM chat
|
|
863
|
+
console.log('\nš¤ STARTER PROMPT (paste as your FIRST message in NotebookLM):');
|
|
864
|
+
console.log('---------------------------------------------------');
|
|
865
|
+
console.log(chalk.cyan.bold('Read the source document ending in "_part0_BRAIN.md" completely before answering any questions. This document contains your core instructions, the project context, and the global directory tree. Acknowledge that you understand your role.\n'));
|
|
866
|
+
|
|
867
|
+
await saveGitAnchor(processedRepoPath);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// --- Standard Snapshot Mode ---
|
|
872
|
+
let fileBody = '';
|
|
873
|
+
if (directoryTree) {
|
|
874
|
+
fileBody += `\n## Directory Structure\n\n\`\`\`\n${directoryTree}\`\`\`\n\n`;
|
|
875
|
+
}
|
|
876
|
+
if (!options.skipContent) {
|
|
877
|
+
fileBody += contentArray.join('');
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Helper to write snapshot file
|
|
881
|
+
const writeSnapshot = async (suffix, isAgentMode) => {
|
|
882
|
+
let header = '';
|
|
883
|
+
if (options.isLinkedProject) {
|
|
884
|
+
const absPath = processedRepoPath.replace(/\\/g, '/');
|
|
885
|
+
header = `# š LINKED PROJECT: [${repoName}]\n\n`;
|
|
886
|
+
header += `**ABSOLUTE PATH:** \`${absPath}\`\n`;
|
|
887
|
+
header += `**CROSS-CONTEXT MODE:** This is a linked companion project provided for reference. DO NOT generate code for it directly in your response unless explicitly asked. To inspect files inside this project, use your tool (or ask the user) to run ONE of the following commands:\n\n`;
|
|
888
|
+
header += `**Option A: Short format (Best for Windows PowerShell / CMD)**\n`;
|
|
889
|
+
header += `\`eck-snapshot fetch "${absPath}/src/example.js"\`\n\n`;
|
|
890
|
+
header += `**Option B: Pure JSON format (Best for Linux/Mac Bash/Zsh)**\n`;
|
|
891
|
+
header += `\`eck-snapshot '{"name": "eck_fetch", "arguments": {"patterns": ["${absPath}/src/example.js"]}}'\`\n\n`;
|
|
892
|
+
if (options.skipContent) {
|
|
893
|
+
header += `*(Source code omitted due to linkDepth=0. Directory structure only.)*\n\n`;
|
|
894
|
+
}
|
|
895
|
+
} else {
|
|
896
|
+
const opts = { ...options, agent: false, jas: isJas, jao: isJao, jaz: isJaz };
|
|
897
|
+
header = await generateEnhancedAIHeader({ stats, repoName, mode: 'file', eckManifest, options: opts, repoPath: processedRepoPath }, isGitRepo);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Compact filename format
|
|
901
|
+
const shortHash = gitHash ? gitHash.substring(0, 7) : '';
|
|
902
|
+
const shortRepoName = getShortRepoName(repoName);
|
|
903
|
+
|
|
904
|
+
let fname = options.isLinkedProject ? `link_${shortRepoName}${timestamp}` : `eck${shortRepoName}${timestamp}`;
|
|
905
|
+
if (shortHash) fname += `_${shortHash}`;
|
|
906
|
+
|
|
907
|
+
// Add mode suffix
|
|
908
|
+
if (options.skeleton) {
|
|
909
|
+
fname += '_sk';
|
|
910
|
+
} else if (suffix) {
|
|
911
|
+
fname += suffix;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
const fullContent = header + fileBody;
|
|
915
|
+
const sizeKB = Math.max(1, Math.round(Buffer.byteLength(fullContent, 'utf-8') / 1024));
|
|
916
|
+
fname += `_${sizeKB}kb.${fileExtension}`;
|
|
917
|
+
const fpath = path.join(outputPath, fname);
|
|
918
|
+
await fs.writeFile(fpath, fullContent);
|
|
919
|
+
const approxTokens = Math.round(fullContent.length / 4);
|
|
920
|
+
const tokensStr = approxTokens < 1000 ? `${approxTokens}` : `${(approxTokens / 1000).toFixed(1)}k`;
|
|
921
|
+
console.log(`š Generated Snapshot: ${fname} (${sizeKB} KB | ~${tokensStr} tokens)`);
|
|
922
|
+
|
|
923
|
+
// --- FEATURE: Active Snapshot ---
|
|
924
|
+
if (!isAgentMode) {
|
|
925
|
+
try {
|
|
926
|
+
if (options.isLinkedProject) {
|
|
927
|
+
// Link snapshots go to .eck/links/
|
|
928
|
+
const linksDir = path.join(originalCwd, '.eck', 'links');
|
|
929
|
+
await fs.mkdir(linksDir, { recursive: true });
|
|
930
|
+
await fs.writeFile(path.join(linksDir, fname), fullContent);
|
|
931
|
+
const approxTokens = Math.round(fullContent.length / 4);
|
|
932
|
+
const tokensStr = approxTokens < 1000 ? `${approxTokens}` : `${(approxTokens / 1000).toFixed(1)}k`;
|
|
933
|
+
console.log(chalk.cyan(`š Link saved to .eck/links/${fname}`));
|
|
934
|
+
console.log(chalk.gray(` Size: ${sizeKB} KB | ~${tokensStr} tokens`));
|
|
935
|
+
} else {
|
|
936
|
+
// Main snapshots go to .eck/lastsnapshot/
|
|
937
|
+
const snapDir = path.join(originalCwd, '.eck', 'lastsnapshot');
|
|
938
|
+
await fs.mkdir(snapDir, { recursive: true });
|
|
939
|
+
|
|
940
|
+
// Clean up OLD snapshots (keep AnswerToSA.md)
|
|
941
|
+
const existingFiles = await fs.readdir(snapDir);
|
|
942
|
+
for (const file of existingFiles) {
|
|
943
|
+
if ((file.startsWith('eck') && file.endsWith('.md')) || file === 'answer.md') {
|
|
944
|
+
await fs.unlink(path.join(snapDir, file));
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
await fs.writeFile(path.join(snapDir, fname), fullContent);
|
|
949
|
+
console.log(chalk.cyan(`š Active snapshot updated in .eck/lastsnapshot/: ${fname}`));
|
|
950
|
+
}
|
|
951
|
+
} catch (e) {
|
|
952
|
+
// Non-critical failure
|
|
953
|
+
console.warn(chalk.yellow(`ā ļø Could not update active snapshot: ${e.message}`));
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
// --------------------------------------------
|
|
957
|
+
|
|
958
|
+
return fpath;
|
|
959
|
+
};
|
|
960
|
+
|
|
961
|
+
// Generate snapshot file for ALL modes
|
|
962
|
+
if (isJas) {
|
|
963
|
+
architectFilePath = await writeSnapshot('_jas', true);
|
|
964
|
+
} else if (isJao) {
|
|
965
|
+
architectFilePath = await writeSnapshot('_jao', true);
|
|
966
|
+
} else if (isJaz) {
|
|
967
|
+
architectFilePath = await writeSnapshot('_jaz', true);
|
|
968
|
+
} else {
|
|
969
|
+
// Standard snapshot behavior
|
|
970
|
+
architectFilePath = await writeSnapshot('', false);
|
|
971
|
+
|
|
972
|
+
// --- File 2: Junior Architect Snapshot (legacy --with-ja support) ---
|
|
973
|
+
if (options.withJa && fileExtension === 'md') {
|
|
974
|
+
console.log('šļø Generating Junior Architect (_ja) snapshot...');
|
|
975
|
+
jaFilePath = await writeSnapshot('_ja', true);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
800
978
|
|
|
801
979
|
// Save git anchor for future delta updates
|
|
802
980
|
await saveGitAnchor(processedRepoPath);
|
|
@@ -816,20 +994,48 @@ export async function createRepoSnapshot(repoPath, options) {
|
|
|
816
994
|
console.log('š Scanning for confidential files...');
|
|
817
995
|
const confidentialFiles = await scanEckForConfidentialFiles(processedRepoPath, config);
|
|
818
996
|
|
|
819
|
-
let claudeMode = 'coder';
|
|
820
|
-
if (isJas) claudeMode = 'jas';
|
|
821
|
-
if (isJao) claudeMode = 'jao';
|
|
822
|
-
if (isJaz) claudeMode = 'jaz';
|
|
823
|
-
|
|
824
|
-
// Claude Code exclusively uses CLAUDE.md
|
|
825
|
-
if (isJas || isJao || (!isJaz && !options.withJa)) {
|
|
826
|
-
await updateClaudeMd(processedRepoPath, claudeMode, directoryTree, confidentialFiles, { zh: options.zh });
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
997
|
+
let claudeMode = 'coder';
|
|
998
|
+
if (isJas) claudeMode = 'jas';
|
|
999
|
+
if (isJao) claudeMode = 'jao';
|
|
1000
|
+
if (isJaz) claudeMode = 'jaz';
|
|
1001
|
+
|
|
1002
|
+
// Claude Code exclusively uses CLAUDE.md
|
|
1003
|
+
if (isJas || isJao || (!isJaz && !options.withJa)) {
|
|
1004
|
+
await updateClaudeMd(processedRepoPath, claudeMode, directoryTree, confidentialFiles, { zh: options.zh });
|
|
1005
|
+
// Ensure .mcp.json with eck-core is present so Claude Code agents have MCP tools
|
|
1006
|
+
try {
|
|
1007
|
+
const mcpCreated = await ensureProjectMcpConfig(processedRepoPath);
|
|
1008
|
+
if (mcpCreated) {
|
|
1009
|
+
console.log(chalk.green('š Created .mcp.json with eck-core MCP server'));
|
|
1010
|
+
}
|
|
1011
|
+
} catch (e) {
|
|
1012
|
+
// Non-critical ā agent can still use manual fallback
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// OpenCode exclusively uses AGENTS.md
|
|
1017
|
+
if (isJaz || (!isJas && !isJao && !options.withJa)) {
|
|
1018
|
+
await generateOpenCodeAgents(processedRepoPath, claudeMode, directoryTree, confidentialFiles, { zh: options.zh });
|
|
1019
|
+
// Ensure local opencode.json has eck-core MCP server
|
|
1020
|
+
try {
|
|
1021
|
+
const mcpCreated = await ensureProjectOpenCodeConfig(processedRepoPath);
|
|
1022
|
+
if (mcpCreated) {
|
|
1023
|
+
console.log(chalk.green('š Added eck-core to local opencode.json'));
|
|
1024
|
+
}
|
|
1025
|
+
} catch (e) {
|
|
1026
|
+
// Non-critical ā agent can still use manual fallback
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// Ensure Codex config if the directory exists
|
|
1030
|
+
try {
|
|
1031
|
+
const codexMcpCreated = await ensureProjectCodexConfig(processedRepoPath);
|
|
1032
|
+
if (codexMcpCreated) {
|
|
1033
|
+
console.log(chalk.green('š Added eck-core to .codex/config.toml'));
|
|
1034
|
+
}
|
|
1035
|
+
} catch (e) {
|
|
1036
|
+
// Non-critical
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
833
1039
|
|
|
834
1040
|
// --- Combined Report ---
|
|
835
1041
|
console.log('\nā
Snapshot generation complete!');
|
|
@@ -924,6 +1130,14 @@ export async function createRepoSnapshot(repoPath, options) {
|
|
|
924
1130
|
console.log(' Replace [ACTUAL_TOKENS_HERE] with the real token count from your LLM');
|
|
925
1131
|
}
|
|
926
1132
|
|
|
1133
|
+
// Output AI Prompt Suggestion for stubborn LLMs
|
|
1134
|
+
console.log('\nš¤ AI PROMPT SUGGESTION (Crucial for ChatGPT, helpful for others):');
|
|
1135
|
+
console.log('---------------------------------------------------');
|
|
1136
|
+
console.log(chalk.yellow('š” Tip: Gemini and Grok handle large files best. ChatGPT works but can be slow.'));
|
|
1137
|
+
console.log('If your AI ignores the file instructions and acts as an external reviewer,');
|
|
1138
|
+
console.log('copy and paste this exact text as your FIRST prompt along with the snapshot file:\n');
|
|
1139
|
+
console.log(chalk.cyan.bold('Read the SYSTEM DIRECTIVE at the very beginning of the attached file. Immediately assume the role of Senior Architect as instructed, then await my first task.\n'));
|
|
1140
|
+
|
|
927
1141
|
} finally {
|
|
928
1142
|
process.chdir(originalCwd); // Final reset back to original CWD
|
|
929
1143
|
}
|