ctxman 3.0.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/.ctxman/custom-profiles.example.json +20 -0
- package/.ctxman/llm-profiles.json +131 -0
- package/.ctxman/logs/ctxman-2026-07-02.log +156 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
- package/.ctxman/wizard-profiles/code-review/.contextignore +41 -0
- package/.ctxman/wizard-profiles/code-review/.contextinclude +30 -0
- package/.ctxman/wizard-profiles/code-review/.methodignore +26 -0
- package/.ctxman/wizard-profiles/code-review/.methodinclude +47 -0
- package/.ctxman/wizard-profiles/code-review/profile.json +26 -0
- package/.ctxman/wizard-profiles/documentation/.contextignore +21 -0
- package/.ctxman/wizard-profiles/documentation/.contextinclude +24 -0
- package/.ctxman/wizard-profiles/documentation/.methodignore +19 -0
- package/.ctxman/wizard-profiles/documentation/.methodinclude +44 -0
- package/.ctxman/wizard-profiles/documentation/profile.json +27 -0
- package/.ctxman/wizard-profiles/full/.contextignore +23 -0
- package/.ctxman/wizard-profiles/full/.contextinclude +38 -0
- package/.ctxman/wizard-profiles/full/.methodignore +5 -0
- package/.ctxman/wizard-profiles/full/.methodinclude +5 -0
- package/.ctxman/wizard-profiles/full/profile.json +28 -0
- package/.ctxman/wizard-profiles/llm-explain/.contextignore +25 -0
- package/.ctxman/wizard-profiles/llm-explain/.contextinclude +33 -0
- package/.ctxman/wizard-profiles/llm-explain/.methodignore +19 -0
- package/.ctxman/wizard-profiles/llm-explain/.methodinclude +48 -0
- package/.ctxman/wizard-profiles/llm-explain/profile.json +27 -0
- package/.ctxman/wizard-profiles/minimal/.contextignore +27 -0
- package/.ctxman/wizard-profiles/minimal/.contextinclude +14 -0
- package/.ctxman/wizard-profiles/minimal/.methodignore +22 -0
- package/.ctxman/wizard-profiles/minimal/.methodinclude +18 -0
- package/.ctxman/wizard-profiles/minimal/profile.json +26 -0
- package/.ctxman/wizard-profiles/security-audit/.contextignore +36 -0
- package/.ctxman/wizard-profiles/security-audit/.contextinclude +37 -0
- package/.ctxman/wizard-profiles/security-audit/.methodignore +29 -0
- package/.ctxman/wizard-profiles/security-audit/.methodinclude +60 -0
- package/.ctxman/wizard-profiles/security-audit/profile.json +34 -0
- package/README.md +1167 -0
- package/bin/cli.js +650 -0
- package/bin/cm-ask.js +95 -0
- package/bin/cm-gitingest.js +134 -0
- package/bin/cm-update.js +175 -0
- package/bin/mcp-server.js +31 -0
- package/ctxman.js +236 -0
- package/examples/README.md +298 -0
- package/examples/custom-llm-profiles.example.json +19 -0
- package/examples/wizard-profiles/code-review/.contextignore +41 -0
- package/examples/wizard-profiles/code-review/.contextinclude +30 -0
- package/examples/wizard-profiles/code-review/.methodignore +26 -0
- package/examples/wizard-profiles/code-review/.methodinclude +47 -0
- package/examples/wizard-profiles/code-review/profile.json +26 -0
- package/examples/wizard-profiles/documentation/.contextignore +21 -0
- package/examples/wizard-profiles/documentation/.contextinclude +24 -0
- package/examples/wizard-profiles/documentation/.methodignore +19 -0
- package/examples/wizard-profiles/documentation/.methodinclude +44 -0
- package/examples/wizard-profiles/documentation/profile.json +27 -0
- package/examples/wizard-profiles/full/.contextignore +23 -0
- package/examples/wizard-profiles/full/.contextinclude +38 -0
- package/examples/wizard-profiles/full/.methodignore +5 -0
- package/examples/wizard-profiles/full/.methodinclude +5 -0
- package/examples/wizard-profiles/full/profile.json +28 -0
- package/examples/wizard-profiles/llm-explain/.contextignore +25 -0
- package/examples/wizard-profiles/llm-explain/.contextinclude +33 -0
- package/examples/wizard-profiles/llm-explain/.methodignore +19 -0
- package/examples/wizard-profiles/llm-explain/.methodinclude +48 -0
- package/examples/wizard-profiles/llm-explain/profile.json +27 -0
- package/examples/wizard-profiles/minimal/.contextignore +27 -0
- package/examples/wizard-profiles/minimal/.contextinclude +14 -0
- package/examples/wizard-profiles/minimal/.methodignore +22 -0
- package/examples/wizard-profiles/minimal/.methodinclude +18 -0
- package/examples/wizard-profiles/minimal/profile.json +26 -0
- package/examples/wizard-profiles/security-audit/.contextignore +36 -0
- package/examples/wizard-profiles/security-audit/.contextinclude +37 -0
- package/examples/wizard-profiles/security-audit/.methodignore +29 -0
- package/examples/wizard-profiles/security-audit/.methodinclude +60 -0
- package/examples/wizard-profiles/security-audit/profile.json +34 -0
- package/index.js +68 -0
- package/lib/agents/pr-failure-agent.js +201 -0
- package/lib/analyzers/go-method-analyzer.js +126 -0
- package/lib/analyzers/method-analyzer.js +457 -0
- package/lib/analyzers/token-calculator.js +577 -0
- package/lib/api/mcp/prompts.js +319 -0
- package/lib/api/mcp/resources.js +269 -0
- package/lib/api/mcp/server.js +339 -0
- package/lib/api/mcp/transports/HttpSseTransport.js +63 -0
- package/lib/api/rest/server.js +405 -0
- package/lib/cache/CacheManager.js +388 -0
- package/lib/core/Analyzer.js +180 -0
- package/lib/core/ContextBuilder.js +325 -0
- package/lib/core/Reporter.js +270 -0
- package/lib/core/Scanner.js +129 -0
- package/lib/formatters/format-registry.js +419 -0
- package/lib/formatters/gitingest-formatter.js +746 -0
- package/lib/formatters/toon-formatter-v1.3.js +731 -0
- package/lib/formatters/toon-formatter.js +291 -0
- package/lib/integrations/git/BlameTracker.js +142 -0
- package/lib/integrations/git/DiffAnalyzer.js +260 -0
- package/lib/integrations/git/GitClient.js +336 -0
- package/lib/parsers/gitignore-parser.js +127 -0
- package/lib/parsers/method-filter-parser.js +50 -0
- package/lib/plugins/ExporterPlugin.js +102 -0
- package/lib/plugins/LanguagePlugin.js +100 -0
- package/lib/plugins/PluginManager.js +302 -0
- package/lib/rag/DataSourcePlugin.js +31 -0
- package/lib/rag/EmbeddingProvider.js +75 -0
- package/lib/rag/Indexer.js +83 -0
- package/lib/rag/VectorStoreFactory.js +24 -0
- package/lib/rag/sources/FileDataSource.js +70 -0
- package/lib/rag/stores/BaseVectorStore.js +43 -0
- package/lib/rag/stores/ChromaDBStore.js +85 -0
- package/lib/rag/stores/LanceDBStore.js +109 -0
- package/lib/rag/stores/PineconeStore.js +74 -0
- package/lib/ui/dashboard.js +109 -0
- package/lib/ui/index.js +15 -0
- package/lib/ui/progress-bar.js +110 -0
- package/lib/ui/select-input.js +51 -0
- package/lib/ui/wizard.js +359 -0
- package/lib/utils/clipboard-utils.js +66 -0
- package/lib/utils/config-utils.js +94 -0
- package/lib/utils/error-handler.js +145 -0
- package/lib/utils/file-utils.js +89 -0
- package/lib/utils/format-converter.js +325 -0
- package/lib/utils/git-utils.js +337 -0
- package/lib/utils/llm-detector.js +409 -0
- package/lib/utils/logger.js +278 -0
- package/lib/utils/token-utils.js +200 -0
- package/lib/utils/tokenizer-adapter.js +454 -0
- package/lib/utils/updater.js +565 -0
- package/lib/watch/FileWatcher.js +208 -0
- package/lib/watch/IncrementalAnalyzer.js +206 -0
- package/package.json +118 -0
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import MethodAnalyzer from '../analyzers/method-analyzer.js';
|
|
4
|
+
import MethodFilterParser from '../parsers/method-filter-parser.js';
|
|
5
|
+
import ConfigUtils from '../utils/config-utils.js';
|
|
6
|
+
import TokenUtils from '../utils/token-utils.js';
|
|
7
|
+
import FileUtils from '../utils/file-utils.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* GitIngest-style Digest Formatter
|
|
11
|
+
* Formats analyzed code into a single prompt-friendly text file
|
|
12
|
+
* Automatically applies method-level filtering if .methodinclude or .methodignore exists
|
|
13
|
+
*
|
|
14
|
+
* v2.3.0 Features:
|
|
15
|
+
* - Smart chunking for large repositories
|
|
16
|
+
* - Multiple chunking strategies (smart, size, file, directory, dependency)
|
|
17
|
+
* - Cross-chunk reference preservation
|
|
18
|
+
* - Chunk metadata and navigation
|
|
19
|
+
*/
|
|
20
|
+
class GitIngestFormatter {
|
|
21
|
+
constructor(projectRoot, stats, analysisResults, options = {}) {
|
|
22
|
+
this.projectRoot = projectRoot;
|
|
23
|
+
this.stats = stats;
|
|
24
|
+
this.analysisResults = analysisResults;
|
|
25
|
+
this.options = options;
|
|
26
|
+
|
|
27
|
+
// Chunking configuration (v2.3.3: Enhanced)
|
|
28
|
+
this.chunking = {
|
|
29
|
+
enabled: options.chunking?.enabled || false,
|
|
30
|
+
strategy: options.chunking?.strategy || 'smart',
|
|
31
|
+
maxTokensPerChunk: options.chunking?.maxTokensPerChunk || 100000,
|
|
32
|
+
overlap: options.chunking?.overlap || 500,
|
|
33
|
+
preserveContext: options.chunking?.preserveContext !== false,
|
|
34
|
+
includeMetadata: options.chunking?.includeMetadata !== false, // v2.3.3
|
|
35
|
+
crossReferences: options.chunking?.crossReferences !== false // v2.3.3
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Auto-detect method filtering configuration
|
|
39
|
+
this.methodFilterEnabled = this.detectMethodFilters();
|
|
40
|
+
if (this.methodFilterEnabled) {
|
|
41
|
+
this.methodAnalyzer = new MethodAnalyzer();
|
|
42
|
+
this.methodFilter = this.initMethodFilter();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
detectMethodFilters() {
|
|
47
|
+
return ConfigUtils.detectMethodFilters(this.projectRoot);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
initMethodFilter() {
|
|
51
|
+
return ConfigUtils.initMethodFilter(this.projectRoot);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
generateDigest() {
|
|
55
|
+
// Check if chunking is enabled
|
|
56
|
+
if (this.chunking.enabled) {
|
|
57
|
+
return this.generateChunkedDigest();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let digest = '';
|
|
61
|
+
|
|
62
|
+
// Header with project info
|
|
63
|
+
digest += this.generateSummary();
|
|
64
|
+
digest += '\n';
|
|
65
|
+
|
|
66
|
+
// Directory tree structure
|
|
67
|
+
digest += this.generateTree();
|
|
68
|
+
digest += '\n';
|
|
69
|
+
|
|
70
|
+
// File contents
|
|
71
|
+
digest += this.generateFileContents();
|
|
72
|
+
|
|
73
|
+
return digest;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Generate chunked digest for large repositories
|
|
78
|
+
* Returns array of chunk objects with metadata
|
|
79
|
+
* v2.3.3: Enhanced with overlap and cross-references
|
|
80
|
+
*/
|
|
81
|
+
generateChunkedDigest() {
|
|
82
|
+
const chunks = this.createChunks();
|
|
83
|
+
const chunkData = [];
|
|
84
|
+
|
|
85
|
+
// Add overlap between chunks (v2.3.3)
|
|
86
|
+
if (this.chunking.overlap > 0) {
|
|
87
|
+
this.addChunkOverlap(chunks);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
91
|
+
const chunk = chunks[i];
|
|
92
|
+
const chunkId = `chunk-${i + 1}`;
|
|
93
|
+
|
|
94
|
+
let digest = '';
|
|
95
|
+
|
|
96
|
+
// Chunk header
|
|
97
|
+
digest += this.generateChunkHeader(i + 1, chunks.length, chunk);
|
|
98
|
+
digest += '\n';
|
|
99
|
+
|
|
100
|
+
// Summary for this chunk
|
|
101
|
+
digest += this.generateChunkSummary(chunk);
|
|
102
|
+
digest += '\n';
|
|
103
|
+
|
|
104
|
+
// Directory tree for files in this chunk
|
|
105
|
+
digest += this.generateChunkTree(chunk);
|
|
106
|
+
digest += '\n';
|
|
107
|
+
|
|
108
|
+
// Chunk metadata (v2.3.3)
|
|
109
|
+
if (this.chunking.includeMetadata) {
|
|
110
|
+
digest += this.generateChunkMetadata(chunk, i, chunks);
|
|
111
|
+
digest += '\n';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// File contents
|
|
115
|
+
digest += this.generateChunkFileContents(chunk);
|
|
116
|
+
|
|
117
|
+
// Navigation info
|
|
118
|
+
digest += '\n';
|
|
119
|
+
digest += this.generateNavigationInfo(i, chunks.length);
|
|
120
|
+
|
|
121
|
+
chunkData.push({
|
|
122
|
+
id: chunkId,
|
|
123
|
+
index: i + 1,
|
|
124
|
+
total: chunks.length,
|
|
125
|
+
content: digest,
|
|
126
|
+
files: chunk.files.map(f => f.relativePath),
|
|
127
|
+
tokens: chunk.tokens,
|
|
128
|
+
metadata: chunk.metadata || {},
|
|
129
|
+
hasOverlap: chunk.hasOverlap || false, // v2.3.3
|
|
130
|
+
overlapFiles: chunk.overlapFiles || [] // v2.3.3
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return chunkData;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Add overlap between chunks
|
|
139
|
+
* v2.3.3: Ensures context continuity
|
|
140
|
+
*/
|
|
141
|
+
addChunkOverlap(chunks) {
|
|
142
|
+
for (let i = 0; i < chunks.length - 1; i++) {
|
|
143
|
+
const currentChunk = chunks[i];
|
|
144
|
+
const nextChunk = chunks[i + 1];
|
|
145
|
+
|
|
146
|
+
// Get last N tokens worth of files from current chunk
|
|
147
|
+
const overlapFiles = this.getOverlapFiles(currentChunk, this.chunking.overlap);
|
|
148
|
+
|
|
149
|
+
if (overlapFiles.length > 0) {
|
|
150
|
+
// Prepend to next chunk
|
|
151
|
+
nextChunk.files = [...overlapFiles, ...nextChunk.files];
|
|
152
|
+
nextChunk.hasOverlap = true;
|
|
153
|
+
nextChunk.overlapFiles = overlapFiles.map(f => f.relativePath);
|
|
154
|
+
|
|
155
|
+
// Update token count
|
|
156
|
+
const overlapTokens = overlapFiles.reduce((sum, f) => sum + f.tokens, 0);
|
|
157
|
+
nextChunk.tokens += overlapTokens;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get files for overlap based on token count
|
|
164
|
+
* v2.3.3
|
|
165
|
+
*/
|
|
166
|
+
getOverlapFiles(chunk, targetOverlapTokens) {
|
|
167
|
+
const files = [...chunk.files].reverse(); // Start from end
|
|
168
|
+
const overlapFiles = [];
|
|
169
|
+
let tokenCount = 0;
|
|
170
|
+
|
|
171
|
+
for (const file of files) {
|
|
172
|
+
if (tokenCount >= targetOverlapTokens) {
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
overlapFiles.unshift(file);
|
|
176
|
+
tokenCount += file.tokens;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return overlapFiles;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Generate chunk metadata
|
|
184
|
+
* v2.3.3: Language distribution, file types, etc.
|
|
185
|
+
*/
|
|
186
|
+
generateChunkMetadata(chunk, index, allChunks) {
|
|
187
|
+
let metadata = '\n';
|
|
188
|
+
metadata += 'CHUNK METADATA\n';
|
|
189
|
+
metadata += '='.repeat(60) + '\n';
|
|
190
|
+
|
|
191
|
+
// Language distribution
|
|
192
|
+
const languages = {};
|
|
193
|
+
for (const file of chunk.files) {
|
|
194
|
+
const ext = path.extname(file.relativePath) || 'no-ext';
|
|
195
|
+
languages[ext] = (languages[ext] || 0) + 1;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
metadata += 'Languages:\n';
|
|
199
|
+
for (const [ext, count] of Object.entries(languages).sort((a, b) => b[1] - a[1])) {
|
|
200
|
+
metadata += ` ${ext}: ${count} files\n`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Directories covered
|
|
204
|
+
const dirs = new Set(chunk.files.map(f => path.dirname(f.relativePath)));
|
|
205
|
+
metadata += `\nDirectories: ${dirs.size}\n`;
|
|
206
|
+
|
|
207
|
+
// Cross-references (v2.3.3)
|
|
208
|
+
if (this.chunking.crossReferences && index > 0) {
|
|
209
|
+
const prevChunk = allChunks[index - 1];
|
|
210
|
+
const sharedDirs = this.getSharedDirectories(chunk, prevChunk);
|
|
211
|
+
if (sharedDirs.length > 0) {
|
|
212
|
+
metadata += `\nShared with previous chunk: ${sharedDirs.join(', ')}\n`;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return metadata;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get shared directories between chunks
|
|
221
|
+
* v2.3.3
|
|
222
|
+
*/
|
|
223
|
+
getSharedDirectories(chunk1, chunk2) {
|
|
224
|
+
const dirs1 = new Set(chunk1.files.map(f => path.dirname(f.relativePath)));
|
|
225
|
+
const dirs2 = new Set(chunk2.files.map(f => path.dirname(f.relativePath)));
|
|
226
|
+
|
|
227
|
+
return [...dirs1].filter(dir => dirs2.has(dir));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Create chunks based on selected strategy
|
|
232
|
+
*/
|
|
233
|
+
createChunks() {
|
|
234
|
+
const strategy = this.chunking.strategy;
|
|
235
|
+
|
|
236
|
+
switch (strategy) {
|
|
237
|
+
case 'smart':
|
|
238
|
+
return this.createSmartChunks();
|
|
239
|
+
case 'size':
|
|
240
|
+
return this.createSizeBasedChunks();
|
|
241
|
+
case 'file':
|
|
242
|
+
return this.createFileBasedChunks();
|
|
243
|
+
case 'directory':
|
|
244
|
+
return this.createDirectoryBasedChunks();
|
|
245
|
+
case 'dependency':
|
|
246
|
+
return this.createDependencyBasedChunks();
|
|
247
|
+
default:
|
|
248
|
+
return this.createSizeBasedChunks();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Smart chunking - AI-based semantic grouping
|
|
254
|
+
* Groups related files together based on directory and imports
|
|
255
|
+
*/
|
|
256
|
+
createSmartChunks() {
|
|
257
|
+
const chunks = [];
|
|
258
|
+
const maxTokens = this.chunking.maxTokensPerChunk;
|
|
259
|
+
|
|
260
|
+
// Sort files by directory and then by token count
|
|
261
|
+
const sortedFiles = [...this.analysisResults].sort((a, b) => {
|
|
262
|
+
const dirA = path.dirname(a.relativePath);
|
|
263
|
+
const dirB = path.dirname(b.relativePath);
|
|
264
|
+
if (dirA !== dirB) {
|
|
265
|
+
return dirA.localeCompare(dirB);
|
|
266
|
+
}
|
|
267
|
+
return b.tokens - a.tokens;
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
let currentChunk = { files: [], tokens: 0, directories: new Set() };
|
|
271
|
+
|
|
272
|
+
for (const file of sortedFiles) {
|
|
273
|
+
const fileDir = path.dirname(file.relativePath);
|
|
274
|
+
const wouldExceedLimit = currentChunk.tokens + file.tokens > maxTokens;
|
|
275
|
+
|
|
276
|
+
// Start new chunk if:
|
|
277
|
+
// 1. Would exceed token limit
|
|
278
|
+
// 2. Different directory and chunk already has content
|
|
279
|
+
if (wouldExceedLimit ||
|
|
280
|
+
(currentChunk.files.length > 0 &&
|
|
281
|
+
!currentChunk.directories.has(fileDir) &&
|
|
282
|
+
currentChunk.tokens > maxTokens * 0.7)) {
|
|
283
|
+
|
|
284
|
+
if (currentChunk.files.length > 0) {
|
|
285
|
+
chunks.push(currentChunk);
|
|
286
|
+
}
|
|
287
|
+
currentChunk = { files: [], tokens: 0, directories: new Set() };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
currentChunk.files.push(file);
|
|
291
|
+
currentChunk.tokens += file.tokens;
|
|
292
|
+
currentChunk.directories.add(fileDir);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Add last chunk
|
|
296
|
+
if (currentChunk.files.length > 0) {
|
|
297
|
+
chunks.push(currentChunk);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return chunks;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Size-based chunking - Fixed token size chunks
|
|
305
|
+
*/
|
|
306
|
+
createSizeBasedChunks() {
|
|
307
|
+
const chunks = [];
|
|
308
|
+
const maxTokens = this.chunking.maxTokensPerChunk;
|
|
309
|
+
|
|
310
|
+
// Sort files by token count (largest first)
|
|
311
|
+
const sortedFiles = [...this.analysisResults].sort((a, b) => b.tokens - a.tokens);
|
|
312
|
+
|
|
313
|
+
let currentChunk = { files: [], tokens: 0 };
|
|
314
|
+
|
|
315
|
+
for (const file of sortedFiles) {
|
|
316
|
+
if (currentChunk.tokens + file.tokens > maxTokens && currentChunk.files.length > 0) {
|
|
317
|
+
chunks.push(currentChunk);
|
|
318
|
+
currentChunk = { files: [], tokens: 0 };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
currentChunk.files.push(file);
|
|
322
|
+
currentChunk.tokens += file.tokens;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (currentChunk.files.length > 0) {
|
|
326
|
+
chunks.push(currentChunk);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return chunks;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* File-based chunking - One file per chunk (with directory context)
|
|
334
|
+
*/
|
|
335
|
+
createFileBasedChunks() {
|
|
336
|
+
const chunks = [];
|
|
337
|
+
|
|
338
|
+
for (const file of this.analysisResults) {
|
|
339
|
+
chunks.push({
|
|
340
|
+
files: [file],
|
|
341
|
+
tokens: file.tokens,
|
|
342
|
+
metadata: {
|
|
343
|
+
directory: path.dirname(file.relativePath)
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return chunks;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Directory-based chunking - One directory per chunk
|
|
353
|
+
*/
|
|
354
|
+
createDirectoryBasedChunks() {
|
|
355
|
+
const chunks = [];
|
|
356
|
+
const filesByDirectory = new Map();
|
|
357
|
+
|
|
358
|
+
// Group files by directory
|
|
359
|
+
for (const file of this.analysisResults) {
|
|
360
|
+
const dir = path.dirname(file.relativePath);
|
|
361
|
+
if (!filesByDirectory.has(dir)) {
|
|
362
|
+
filesByDirectory.set(dir, []);
|
|
363
|
+
}
|
|
364
|
+
filesByDirectory.get(dir).push(file);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Create chunks from directories
|
|
368
|
+
for (const [dir, files] of filesByDirectory) {
|
|
369
|
+
const tokens = files.reduce((sum, f) => sum + f.tokens, 0);
|
|
370
|
+
chunks.push({
|
|
371
|
+
files,
|
|
372
|
+
tokens,
|
|
373
|
+
metadata: { directory: dir }
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return chunks;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Dependency-based chunking - Group by import/dependency graph
|
|
382
|
+
* Placeholder for future implementation with AST analysis
|
|
383
|
+
*/
|
|
384
|
+
createDependencyBasedChunks() {
|
|
385
|
+
// For now, fall back to smart chunking
|
|
386
|
+
// In Phase 2, we'll implement proper dependency analysis
|
|
387
|
+
return this.createSmartChunks();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Generate chunk header
|
|
392
|
+
*/
|
|
393
|
+
generateChunkHeader(index, total, chunk) {
|
|
394
|
+
let header = '='.repeat(60) + '\n';
|
|
395
|
+
header += `CHUNK ${index} of ${total}\n`;
|
|
396
|
+
header += '='.repeat(60);
|
|
397
|
+
return header;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Generate summary for a chunk
|
|
402
|
+
*/
|
|
403
|
+
generateChunkSummary(chunk) {
|
|
404
|
+
let summary = `Files in this chunk: ${chunk.files.length}\n`;
|
|
405
|
+
summary += `Estimated tokens: ${this.formatTokenCount(chunk.tokens)}`;
|
|
406
|
+
|
|
407
|
+
if (chunk.metadata?.directory) {
|
|
408
|
+
summary += `\nDirectory: ${chunk.metadata.directory}`;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return summary;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Generate directory tree for chunk
|
|
416
|
+
*/
|
|
417
|
+
generateChunkTree(chunk) {
|
|
418
|
+
let tree = 'Directory structure:\n';
|
|
419
|
+
|
|
420
|
+
// Build tree structure from chunk files
|
|
421
|
+
const fileTree = this.buildFileTreeFromFiles(chunk.files);
|
|
422
|
+
tree += this.formatTreeNode(fileTree, '', true);
|
|
423
|
+
|
|
424
|
+
return tree;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Build file tree from specific files
|
|
429
|
+
*/
|
|
430
|
+
buildFileTreeFromFiles(files) {
|
|
431
|
+
const root = {
|
|
432
|
+
name: path.basename(this.projectRoot),
|
|
433
|
+
type: 'directory',
|
|
434
|
+
children: {}
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const sortedFiles = [...files].sort((a, b) =>
|
|
438
|
+
a.relativePath.localeCompare(b.relativePath)
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
for (const fileInfo of sortedFiles) {
|
|
442
|
+
const parts = fileInfo.relativePath.split('/');
|
|
443
|
+
let current = root.children;
|
|
444
|
+
|
|
445
|
+
for (let i = 0; i < parts.length; i++) {
|
|
446
|
+
const part = parts[i];
|
|
447
|
+
const isLastPart = i === parts.length - 1;
|
|
448
|
+
|
|
449
|
+
if (isLastPart) {
|
|
450
|
+
current[part] = {
|
|
451
|
+
name: part,
|
|
452
|
+
type: 'file',
|
|
453
|
+
fileInfo: fileInfo
|
|
454
|
+
};
|
|
455
|
+
} else {
|
|
456
|
+
if (!current[part]) {
|
|
457
|
+
current[part] = {
|
|
458
|
+
name: part,
|
|
459
|
+
type: 'directory',
|
|
460
|
+
children: {}
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
current = current[part].children;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return root;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Generate file contents for chunk
|
|
473
|
+
*/
|
|
474
|
+
generateChunkFileContents(chunk) {
|
|
475
|
+
let content = '';
|
|
476
|
+
|
|
477
|
+
// Sort files by token count (largest first)
|
|
478
|
+
const sortedFiles = [...chunk.files].sort((a, b) => b.tokens - a.tokens);
|
|
479
|
+
|
|
480
|
+
for (const fileInfo of sortedFiles) {
|
|
481
|
+
if (fileInfo.error) continue;
|
|
482
|
+
|
|
483
|
+
content += '\n';
|
|
484
|
+
content += '='.repeat(48) + '\n';
|
|
485
|
+
content += `FILE: ${fileInfo.relativePath}\n`;
|
|
486
|
+
content += '='.repeat(48) + '\n';
|
|
487
|
+
|
|
488
|
+
try {
|
|
489
|
+
const fileContent = fs.readFileSync(fileInfo.path, 'utf8');
|
|
490
|
+
|
|
491
|
+
if (this.methodFilterEnabled && this.isCodeFile(fileInfo.path)) {
|
|
492
|
+
const filteredContent = this.generateFilteredFileContent(fileContent, fileInfo.path);
|
|
493
|
+
content += filteredContent;
|
|
494
|
+
} else {
|
|
495
|
+
content += fileContent;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (!content.endsWith('\n')) {
|
|
499
|
+
content += '\n';
|
|
500
|
+
}
|
|
501
|
+
} catch (error) {
|
|
502
|
+
content += `Error reading file: ${error.message}\n`;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return content;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Generate navigation info between chunks
|
|
511
|
+
*/
|
|
512
|
+
generateNavigationInfo(currentIndex, totalChunks) {
|
|
513
|
+
let nav = '\n';
|
|
514
|
+
nav += '='.repeat(60) + '\n';
|
|
515
|
+
nav += 'NAVIGATION\n';
|
|
516
|
+
nav += '='.repeat(60) + '\n';
|
|
517
|
+
|
|
518
|
+
if (currentIndex > 0) {
|
|
519
|
+
nav += `Previous: chunk-${currentIndex}.txt\n`;
|
|
520
|
+
}
|
|
521
|
+
if (currentIndex < totalChunks - 1) {
|
|
522
|
+
nav += `Next: chunk-${currentIndex + 2}.txt\n`;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
nav += `\nTotal Chunks: ${totalChunks}\n`;
|
|
526
|
+
nav += `Current Position: ${currentIndex + 1} / ${totalChunks}`;
|
|
527
|
+
|
|
528
|
+
return nav;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
generateSummary() {
|
|
532
|
+
const projectName = path.basename(this.projectRoot);
|
|
533
|
+
let summary = `Directory: ${projectName}\n`;
|
|
534
|
+
summary += `Files analyzed: ${this.stats.totalFiles}\n`;
|
|
535
|
+
|
|
536
|
+
if (this.methodFilterEnabled) {
|
|
537
|
+
const mode = this.methodFilter.hasIncludeFile ? 'INCLUDE' : 'EXCLUDE';
|
|
538
|
+
summary += `Method filtering: ${mode} mode active\n`;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (this.stats.totalTokens > 0) {
|
|
542
|
+
const tokenStr = this.formatTokenCount(this.stats.totalTokens);
|
|
543
|
+
summary += `\nEstimated tokens: ${tokenStr}`;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return summary;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
formatTokenCount(tokens) {
|
|
550
|
+
return TokenUtils.format(tokens);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
generateTree() {
|
|
554
|
+
let tree = 'Directory structure:\n';
|
|
555
|
+
|
|
556
|
+
// Build tree structure from analysis results
|
|
557
|
+
const fileTree = this.buildFileTree();
|
|
558
|
+
tree += this.formatTreeNode(fileTree, '', true);
|
|
559
|
+
|
|
560
|
+
return tree;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
buildFileTree() {
|
|
564
|
+
const root = {
|
|
565
|
+
name: path.basename(this.projectRoot),
|
|
566
|
+
type: 'directory',
|
|
567
|
+
children: {}
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
// Sort files by path for consistent tree structure
|
|
571
|
+
const sortedFiles = [...this.analysisResults].sort((a, b) =>
|
|
572
|
+
a.relativePath.localeCompare(b.relativePath)
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
for (const fileInfo of sortedFiles) {
|
|
576
|
+
const parts = fileInfo.relativePath.split('/');
|
|
577
|
+
let current = root.children;
|
|
578
|
+
|
|
579
|
+
for (let i = 0; i < parts.length; i++) {
|
|
580
|
+
const part = parts[i];
|
|
581
|
+
const isLastPart = i === parts.length - 1;
|
|
582
|
+
|
|
583
|
+
if (isLastPart) {
|
|
584
|
+
// It's a file
|
|
585
|
+
current[part] = {
|
|
586
|
+
name: part,
|
|
587
|
+
type: 'file',
|
|
588
|
+
fileInfo: fileInfo
|
|
589
|
+
};
|
|
590
|
+
} else {
|
|
591
|
+
// It's a directory
|
|
592
|
+
if (!current[part]) {
|
|
593
|
+
current[part] = {
|
|
594
|
+
name: part,
|
|
595
|
+
type: 'directory',
|
|
596
|
+
children: {}
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
current = current[part].children;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return root;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
formatTreeNode(node, prefix = '', isLast = true) {
|
|
608
|
+
let result = '';
|
|
609
|
+
|
|
610
|
+
if (node.name) {
|
|
611
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
612
|
+
const displayName = node.type === 'directory' ? `${node.name}/` : node.name;
|
|
613
|
+
result += `${prefix}${connector}${displayName}\n`;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (node.type === 'directory' && node.children) {
|
|
617
|
+
const newPrefix = node.name ? (prefix + (isLast ? ' ' : '│ ')) : '';
|
|
618
|
+
const childKeys = Object.keys(node.children).sort();
|
|
619
|
+
|
|
620
|
+
childKeys.forEach((key, index) => {
|
|
621
|
+
const child = node.children[key];
|
|
622
|
+
const childIsLast = index === childKeys.length - 1;
|
|
623
|
+
result += this.formatTreeNode(child, newPrefix, childIsLast);
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return result;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
generateFileContents() {
|
|
631
|
+
let content = '';
|
|
632
|
+
|
|
633
|
+
// Sort files by token count (largest first)
|
|
634
|
+
const sortedFiles = [...this.analysisResults].sort((a, b) =>
|
|
635
|
+
b.tokens - a.tokens
|
|
636
|
+
);
|
|
637
|
+
|
|
638
|
+
for (const fileInfo of sortedFiles) {
|
|
639
|
+
if (fileInfo.error) continue;
|
|
640
|
+
|
|
641
|
+
content += '\n';
|
|
642
|
+
content += '='.repeat(48) + '\n';
|
|
643
|
+
content += `FILE: ${fileInfo.relativePath}\n`;
|
|
644
|
+
content += '='.repeat(48) + '\n';
|
|
645
|
+
|
|
646
|
+
try {
|
|
647
|
+
const fileContent = fs.readFileSync(fileInfo.path, 'utf8');
|
|
648
|
+
|
|
649
|
+
// Apply method-level filtering if enabled and file is a code file
|
|
650
|
+
if (this.methodFilterEnabled && this.isCodeFile(fileInfo.path)) {
|
|
651
|
+
const filteredContent = this.generateFilteredFileContent(fileContent, fileInfo.path);
|
|
652
|
+
content += filteredContent;
|
|
653
|
+
} else {
|
|
654
|
+
// Include full file content
|
|
655
|
+
content += fileContent;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (!content.endsWith('\n')) {
|
|
659
|
+
content += '\n';
|
|
660
|
+
}
|
|
661
|
+
} catch (error) {
|
|
662
|
+
content += `Error reading file: ${error.message}\n`;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return content;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
isCodeFile(filePath) {
|
|
670
|
+
return FileUtils.isCode(filePath);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
generateFilteredFileContent(content, filePath) {
|
|
674
|
+
// Extract all methods from the file
|
|
675
|
+
const methods = this.methodAnalyzer.extractMethods(content, filePath);
|
|
676
|
+
const fileName = path.basename(filePath, path.extname(filePath));
|
|
677
|
+
|
|
678
|
+
// Filter methods
|
|
679
|
+
const filteredMethods = methods.filter(method =>
|
|
680
|
+
this.methodFilter.shouldIncludeMethod(method.name, fileName)
|
|
681
|
+
);
|
|
682
|
+
|
|
683
|
+
if (filteredMethods.length === 0) {
|
|
684
|
+
// No methods pass the filter, return a note
|
|
685
|
+
return `// No methods matched the filter criteria for this file\n`;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Build filtered content with only included methods
|
|
689
|
+
let filteredContent = `// File contains ${methods.length} methods, showing ${filteredMethods.length} filtered methods\n\n`;
|
|
690
|
+
|
|
691
|
+
const lines = content.split('\n');
|
|
692
|
+
|
|
693
|
+
for (const method of filteredMethods) {
|
|
694
|
+
// Extract method content using a simple approach
|
|
695
|
+
// Find the method start and try to get its content
|
|
696
|
+
const methodContent = this.extractMethodBlock(lines, method.line - 1);
|
|
697
|
+
|
|
698
|
+
filteredContent += `// Method: ${method.name} (line ${method.line})\n`;
|
|
699
|
+
filteredContent += methodContent + '\n\n';
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return filteredContent;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
extractMethodBlock(lines, startLine) {
|
|
706
|
+
// Simple method extraction: get the method and its body
|
|
707
|
+
let braceCount = 0;
|
|
708
|
+
let inMethod = false;
|
|
709
|
+
let methodLines = [];
|
|
710
|
+
|
|
711
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
712
|
+
const line = lines[i];
|
|
713
|
+
methodLines.push(line);
|
|
714
|
+
|
|
715
|
+
// Count braces to find method end
|
|
716
|
+
for (const char of line) {
|
|
717
|
+
if (char === '{') {
|
|
718
|
+
braceCount++;
|
|
719
|
+
inMethod = true;
|
|
720
|
+
} else if (char === '}') {
|
|
721
|
+
braceCount--;
|
|
722
|
+
if (inMethod && braceCount === 0) {
|
|
723
|
+
return methodLines.join('\n');
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Safety: limit to 100 lines per method
|
|
729
|
+
if (methodLines.length > 100) {
|
|
730
|
+
methodLines.push('// ... (method too long, truncated)');
|
|
731
|
+
break;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return methodLines.join('\n');
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
saveToFile(outputPath) {
|
|
739
|
+
const digest = this.generateDigest();
|
|
740
|
+
fs.writeFileSync(outputPath, digest, 'utf8');
|
|
741
|
+
return digest.length;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
export default GitIngestFormatter;
|