ctxman 3.0.0 → 3.1.1
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/llm-profiles.json +1067 -39
- package/.ctxman/profiles/backend-team.json +13 -0
- package/.ctxman/profiles/default.json +12 -0
- package/.ctxman/profiles/devops-team.json +21 -0
- package/.ctxman/profiles/frontend-team.json +20 -0
- package/.ctxman/templates/bug-fix.json +17 -0
- package/.ctxman/templates/code-review.json +9 -0
- package/.ctxman/templates/documentation.json +9 -0
- package/.ctxman/templates/feature.json +9 -0
- package/.ctxman/templates/full-context.json +9 -0
- package/.ctxman/templates/refactoring.json +18 -0
- package/.ctxman/templates/testing.json +9 -0
- package/.ctxman/wizard-profiles/security-audit/profile.json +7 -1
- package/LICENSE +21 -0
- package/README.md +381 -136
- package/bin/cli.js +1880 -529
- package/bin/cm-ask.js +18 -67
- package/bin/cm-audit.js +121 -0
- package/bin/cm-count.js +151 -0
- package/bin/cm-gitingest.js +128 -115
- package/bin/cm-map.js +242 -0
- package/bin/cm-mcp.js +293 -0
- package/bin/cm-pack.js +405 -0
- package/bin/cm-policy.js +170 -0
- package/bin/cm-pr.js +248 -0
- package/bin/cm-search.js +178 -0
- package/bin/cm-update.js +128 -128
- package/bin/cm-why.js +197 -0
- package/bin/mcp-server.js +8 -24
- package/ctxman.js +237 -183
- package/examples/README.md +19 -4
- package/examples/github-actions/ctxman-check.yml +49 -0
- package/examples/wizard-profiles/security-audit/profile.json +7 -1
- package/index.d.ts +1422 -0
- package/index.js +55 -31
- package/lib/agents/pr-failure-agent.js +48 -45
- package/lib/analyzers/ai-suggester.js +451 -0
- package/lib/analyzers/dependency-scanner.js +505 -0
- package/lib/analyzers/duplicate-detector.js +264 -0
- package/lib/analyzers/go-method-analyzer.js +151 -110
- package/lib/analyzers/import-tracker.js +431 -0
- package/lib/analyzers/method-analyzer.js +1055 -434
- package/lib/analyzers/performance-dashboard.js +687 -0
- package/lib/analyzers/token-calculator.js +895 -504
- package/lib/analyzers/unused-exports-detector.js +336 -0
- package/lib/api/mcp/install.js +424 -0
- package/lib/api/mcp/prompts.js +281 -281
- package/lib/api/mcp/resources.js +300 -241
- package/lib/api/mcp/sandbox.js +89 -0
- package/lib/api/mcp/server.js +772 -312
- package/lib/api/mcp/start.js +125 -0
- package/lib/api/mcp/transports/HttpSseTransport.js +60 -52
- package/lib/api/rest/server.js +105 -28
- package/lib/cache/CacheManager.js +9 -17
- package/lib/core/Analyzer.js +16 -10
- package/lib/core/ContextBuilder.js +19 -28
- package/lib/core/Reporter.js +15 -14
- package/lib/core/Scanner.js +81 -12
- package/lib/count/index.js +100 -0
- package/lib/formatters/format-registry.js +394 -372
- package/lib/formatters/gitingest-formatter.js +656 -621
- package/lib/formatters/toon-formatter-v1.3.js +637 -632
- package/lib/formatters/toon-formatter.js +239 -240
- package/lib/integrations/git/BlameTracker.js +4 -4
- package/lib/integrations/git/DiffAnalyzer.js +38 -31
- package/lib/integrations/git/GitClient.js +71 -53
- package/lib/lsp/server.js +311 -0
- package/lib/map/graph.js +299 -0
- package/lib/map/imports.js +233 -0
- package/lib/map/index.js +33 -0
- package/lib/map/rank.js +253 -0
- package/lib/map/render.js +346 -0
- package/lib/map/resolver.js +574 -0
- package/lib/pack/budget.js +111 -0
- package/lib/pack/code-lexer.js +360 -0
- package/lib/pack/compress.js +414 -0
- package/lib/pack/index.js +354 -0
- package/lib/pack/renderers/common.js +134 -0
- package/lib/pack/renderers/index.js +24 -0
- package/lib/pack/renderers/json.js +47 -0
- package/lib/pack/renderers/markdown.js +54 -0
- package/lib/pack/renderers/plain.js +38 -0
- package/lib/pack/renderers/xml.js +85 -0
- package/lib/pack/select.js +177 -0
- package/lib/pack/split.js +182 -0
- package/lib/pack/tree.js +35 -0
- package/lib/parsers/gitignore-parser.js +116 -107
- package/lib/parsers/method-filter-parser.js +50 -33
- package/lib/plugins/ExporterPlugin.js +3 -3
- package/lib/plugins/LanguagePlugin.js +3 -3
- package/lib/plugins/PluginManager.js +7 -13
- package/lib/policy/audit.js +312 -0
- package/lib/policy/enforce.js +125 -0
- package/lib/policy/ignore-files.js +169 -0
- package/lib/policy/index.js +53 -0
- package/lib/policy/policy.js +489 -0
- package/lib/pr/diff-parser.js +267 -0
- package/lib/pr/method-mapper.js +148 -0
- package/lib/pr/pr-context.js +829 -0
- package/lib/pr/pr-git.js +149 -0
- package/lib/pr/pr-renderer.js +644 -0
- package/lib/pr/related-tests.js +296 -0
- package/lib/rag/DataSourcePlugin.js +5 -5
- package/lib/rag/EmbeddingProvider.js +19 -8
- package/lib/rag/Indexer.js +14 -5
- package/lib/rag/VectorStoreFactory.js +3 -3
- package/lib/rag/ask.js +122 -0
- package/lib/rag/sources/FileDataSource.js +12 -9
- package/lib/rag/stores/BaseVectorStore.js +5 -5
- package/lib/rag/stores/ChromaDBStore.js +10 -10
- package/lib/rag/stores/LanceDBStore.js +29 -19
- package/lib/rag/stores/PineconeStore.js +11 -9
- package/lib/search/bm25.js +433 -0
- package/lib/search/code-units.js +201 -0
- package/lib/search/symbols.js +189 -0
- package/lib/ui/dashboard.js +120 -89
- package/lib/ui/index.js +1 -6
- package/lib/ui/init-wizard-ui.js +550 -0
- package/lib/ui/progress-bar.js +94 -88
- package/lib/ui/select-input.js +40 -32
- package/lib/ui/template-selector.js +99 -0
- package/lib/ui/wizard.js +374 -329
- package/lib/utils/claude-token-counter.js +151 -0
- package/lib/utils/cli-output.js +17 -0
- package/lib/utils/clipboard-utils.js +49 -50
- package/lib/utils/config-utils.js +81 -66
- package/lib/utils/context-versioning.js +532 -0
- package/lib/utils/error-handler.js +112 -112
- package/lib/utils/file-utils.js +153 -69
- package/lib/utils/format-converter.js +356 -283
- package/lib/utils/git-utils.js +366 -294
- package/lib/utils/llm-cost-estimator.js +281 -0
- package/lib/utils/llm-detector.js +242 -78
- package/lib/utils/logger.js +246 -237
- package/lib/utils/messages.js +355 -0
- package/lib/utils/multi-repo-manager.js +364 -0
- package/lib/utils/profile-manager.js +380 -0
- package/lib/utils/safe-read.js +61 -0
- package/lib/utils/secret-redactor.js +61 -0
- package/lib/utils/snapshot-manager.js +679 -0
- package/lib/utils/template-manager.js +378 -0
- package/lib/utils/token-utils.js +201 -170
- package/lib/utils/tokenizer-adapter.js +421 -377
- package/lib/utils/updater.js +539 -506
- package/lib/watch/FileWatcher.js +12 -8
- package/lib/watch/IncrementalAnalyzer.js +7 -7
- package/lib/why/coupling.js +81 -0
- package/lib/why/errors.js +23 -0
- package/lib/why/git.js +169 -0
- package/lib/why/history.js +108 -0
- package/lib/why/index.js +720 -0
- package/lib/why/ownership.js +79 -0
- package/lib/why/paths.js +207 -0
- package/lib/why/references.js +259 -0
- package/lib/why/render.js +722 -0
- package/lib/why/search.js +358 -0
- package/lib/why/target.js +389 -0
- package/lib/wizards/config-generator.js +257 -0
- package/lib/wizards/project-detector.js +572 -0
- package/package.json +44 -10
- package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
- 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
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import MethodAnalyzer from '../analyzers/method-analyzer.js';
|
|
4
|
-
import MethodFilterParser from '../parsers/method-filter-parser.js';
|
|
5
4
|
import ConfigUtils from '../utils/config-utils.js';
|
|
6
5
|
import TokenUtils from '../utils/token-utils.js';
|
|
7
6
|
import FileUtils from '../utils/file-utils.js';
|
|
7
|
+
import { redactSecrets } from '../utils/secret-redactor.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* GitIngest-style Digest Formatter
|
|
@@ -18,729 +18,764 @@ import FileUtils from '../utils/file-utils.js';
|
|
|
18
18
|
* - Chunk metadata and navigation
|
|
19
19
|
*/
|
|
20
20
|
class GitIngestFormatter {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
+
// Mask API keys, tokens and private keys in file contents (on unless redactSecrets: false)
|
|
39
|
+
this.redactSecrets = options.redactSecrets !== false;
|
|
40
|
+
this.redactions = {};
|
|
41
|
+
|
|
42
|
+
// Auto-detect method filtering configuration
|
|
43
|
+
this.methodFilterEnabled = this.detectMethodFilters();
|
|
44
|
+
if (this.methodFilterEnabled) {
|
|
45
|
+
this.methodAnalyzer = new MethodAnalyzer();
|
|
46
|
+
this.methodFilter = this.initMethodFilter();
|
|
44
47
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
detectMethodFilters() {
|
|
51
|
+
return ConfigUtils.detectMethodFilters(this.projectRoot);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Apply secret redaction to file content and record what was masked
|
|
56
|
+
* @param {string} text - File content
|
|
57
|
+
* @returns {string}
|
|
58
|
+
*/
|
|
59
|
+
redact(text) {
|
|
60
|
+
if (!this.redactSecrets) return text;
|
|
61
|
+
|
|
62
|
+
const { text: redacted, findings } = redactSecrets(text);
|
|
63
|
+
for (const type of findings) {
|
|
64
|
+
this.redactions[type] = (this.redactions[type] || 0) + 1;
|
|
48
65
|
}
|
|
66
|
+
return redacted;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
initMethodFilter() {
|
|
70
|
+
return ConfigUtils.initMethodFilter(this.projectRoot);
|
|
71
|
+
}
|
|
49
72
|
|
|
50
|
-
|
|
51
|
-
|
|
73
|
+
generateDigest() {
|
|
74
|
+
this.redactions = {};
|
|
75
|
+
|
|
76
|
+
// Check if chunking is enabled
|
|
77
|
+
if (this.chunking.enabled) {
|
|
78
|
+
return this.generateChunkedDigest();
|
|
52
79
|
}
|
|
53
80
|
|
|
54
|
-
|
|
55
|
-
// Check if chunking is enabled
|
|
56
|
-
if (this.chunking.enabled) {
|
|
57
|
-
return this.generateChunkedDigest();
|
|
58
|
-
}
|
|
81
|
+
let digest = '';
|
|
59
82
|
|
|
60
|
-
|
|
83
|
+
// Header with project info
|
|
84
|
+
digest += this.generateSummary();
|
|
85
|
+
digest += '\n';
|
|
61
86
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
87
|
+
// Directory tree structure
|
|
88
|
+
digest += this.generateTree();
|
|
89
|
+
digest += '\n';
|
|
65
90
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
digest += '\n';
|
|
91
|
+
// File contents
|
|
92
|
+
digest += this.generateFileContents();
|
|
69
93
|
|
|
70
|
-
|
|
71
|
-
|
|
94
|
+
return digest;
|
|
95
|
+
}
|
|
72
96
|
|
|
73
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Generate chunked digest for large repositories
|
|
99
|
+
* Returns array of chunk objects with metadata
|
|
100
|
+
* v2.3.3: Enhanced with overlap and cross-references
|
|
101
|
+
*/
|
|
102
|
+
generateChunkedDigest() {
|
|
103
|
+
const chunks = this.createChunks();
|
|
104
|
+
const chunkData = [];
|
|
105
|
+
|
|
106
|
+
// Add overlap between chunks (v2.3.3)
|
|
107
|
+
if (this.chunking.overlap > 0) {
|
|
108
|
+
this.addChunkOverlap(chunks);
|
|
74
109
|
}
|
|
75
110
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
* v2.3.3: Enhanced with overlap and cross-references
|
|
80
|
-
*/
|
|
81
|
-
generateChunkedDigest() {
|
|
82
|
-
const chunks = this.createChunks();
|
|
83
|
-
const chunkData = [];
|
|
111
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
112
|
+
const chunk = chunks[i];
|
|
113
|
+
const chunkId = `chunk-${i + 1}`;
|
|
84
114
|
|
|
85
|
-
|
|
86
|
-
if (this.chunking.overlap > 0) {
|
|
87
|
-
this.addChunkOverlap(chunks);
|
|
88
|
-
}
|
|
115
|
+
let digest = '';
|
|
89
116
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
}
|
|
117
|
+
// Chunk header
|
|
118
|
+
digest += this.generateChunkHeader(i + 1, chunks.length, chunk);
|
|
119
|
+
digest += '\n';
|
|
133
120
|
|
|
134
|
-
|
|
135
|
-
|
|
121
|
+
// Summary for this chunk
|
|
122
|
+
digest += this.generateChunkSummary(chunk);
|
|
123
|
+
digest += '\n';
|
|
136
124
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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];
|
|
125
|
+
// Directory tree for files in this chunk
|
|
126
|
+
digest += this.generateChunkTree(chunk);
|
|
127
|
+
digest += '\n';
|
|
145
128
|
|
|
146
|
-
|
|
147
|
-
|
|
129
|
+
// Chunk metadata (v2.3.3)
|
|
130
|
+
if (this.chunking.includeMetadata) {
|
|
131
|
+
digest += this.generateChunkMetadata(chunk, i, chunks);
|
|
132
|
+
digest += '\n';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// File contents
|
|
136
|
+
digest += this.generateChunkFileContents(chunk);
|
|
137
|
+
|
|
138
|
+
// Navigation info
|
|
139
|
+
digest += '\n';
|
|
140
|
+
digest += this.generateNavigationInfo(i, chunks.length);
|
|
141
|
+
|
|
142
|
+
chunkData.push({
|
|
143
|
+
id: chunkId,
|
|
144
|
+
index: i + 1,
|
|
145
|
+
total: chunks.length,
|
|
146
|
+
content: digest,
|
|
147
|
+
files: chunk.files.map((f) => f.relativePath),
|
|
148
|
+
tokens: chunk.tokens,
|
|
149
|
+
metadata: chunk.metadata || {},
|
|
150
|
+
hasOverlap: chunk.hasOverlap || false, // v2.3.3
|
|
151
|
+
overlapFiles: chunk.overlapFiles || [], // v2.3.3
|
|
152
|
+
});
|
|
153
|
+
}
|
|
148
154
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
return chunkData;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Add overlap between chunks
|
|
160
|
+
* v2.3.3: Ensures context continuity
|
|
161
|
+
*/
|
|
162
|
+
addChunkOverlap(chunks) {
|
|
163
|
+
for (let i = 0; i < chunks.length - 1; i++) {
|
|
164
|
+
const currentChunk = chunks[i];
|
|
165
|
+
const nextChunk = chunks[i + 1];
|
|
166
|
+
|
|
167
|
+
// Get last N tokens worth of files from current chunk
|
|
168
|
+
const overlapFiles = this.getOverlapFiles(currentChunk, this.chunking.overlap);
|
|
169
|
+
|
|
170
|
+
if (overlapFiles.length > 0) {
|
|
171
|
+
// Prepend to next chunk
|
|
172
|
+
nextChunk.files = [...overlapFiles, ...nextChunk.files];
|
|
173
|
+
nextChunk.hasOverlap = true;
|
|
174
|
+
nextChunk.overlapFiles = overlapFiles.map((f) => f.relativePath);
|
|
175
|
+
|
|
176
|
+
// Update token count
|
|
177
|
+
const overlapTokens = overlapFiles.reduce((sum, f) => sum + f.tokens, 0);
|
|
178
|
+
nextChunk.tokens += overlapTokens;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get files for overlap based on token count
|
|
185
|
+
* v2.3.3
|
|
186
|
+
*/
|
|
187
|
+
getOverlapFiles(chunk, targetOverlapTokens) {
|
|
188
|
+
const files = [...chunk.files].reverse(); // Start from end
|
|
189
|
+
const overlapFiles = [];
|
|
190
|
+
let tokenCount = 0;
|
|
191
|
+
|
|
192
|
+
for (const file of files) {
|
|
193
|
+
if (tokenCount >= targetOverlapTokens) {
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
overlapFiles.unshift(file);
|
|
197
|
+
tokenCount += file.tokens;
|
|
198
|
+
}
|
|
154
199
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
200
|
+
return overlapFiles;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Generate chunk metadata
|
|
205
|
+
* v2.3.3: Language distribution, file types, etc.
|
|
206
|
+
*/
|
|
207
|
+
generateChunkMetadata(chunk, index, allChunks) {
|
|
208
|
+
let metadata = '\n';
|
|
209
|
+
metadata += 'CHUNK METADATA\n';
|
|
210
|
+
metadata += '='.repeat(60) + '\n';
|
|
211
|
+
|
|
212
|
+
// Language distribution
|
|
213
|
+
const languages = {};
|
|
214
|
+
for (const file of chunk.files) {
|
|
215
|
+
const ext = path.extname(file.relativePath) || 'no-ext';
|
|
216
|
+
languages[ext] = (languages[ext] || 0) + 1;
|
|
160
217
|
}
|
|
161
218
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
}
|
|
219
|
+
metadata += 'Languages:\n';
|
|
220
|
+
for (const [ext, count] of Object.entries(languages).sort((a, b) => b[1] - a[1])) {
|
|
221
|
+
metadata += ` ${ext}: ${count} files\n`;
|
|
222
|
+
}
|
|
178
223
|
|
|
179
|
-
|
|
224
|
+
// Directories covered
|
|
225
|
+
const dirs = new Set(chunk.files.map((f) => path.dirname(f.relativePath)));
|
|
226
|
+
metadata += `\nDirectories: ${dirs.size}\n`;
|
|
227
|
+
|
|
228
|
+
// Cross-references (v2.3.3)
|
|
229
|
+
if (this.chunking.crossReferences && index > 0) {
|
|
230
|
+
const prevChunk = allChunks[index - 1];
|
|
231
|
+
const sharedDirs = this.getSharedDirectories(chunk, prevChunk);
|
|
232
|
+
if (sharedDirs.length > 0) {
|
|
233
|
+
metadata += `\nShared with previous chunk: ${sharedDirs.join(', ')}\n`;
|
|
234
|
+
}
|
|
180
235
|
}
|
|
181
236
|
|
|
182
|
-
|
|
183
|
-
|
|
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';
|
|
237
|
+
return metadata;
|
|
238
|
+
}
|
|
190
239
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
240
|
+
/**
|
|
241
|
+
* Get shared directories between chunks
|
|
242
|
+
* v2.3.3
|
|
243
|
+
*/
|
|
244
|
+
getSharedDirectories(chunk1, chunk2) {
|
|
245
|
+
const dirs1 = new Set(chunk1.files.map((f) => path.dirname(f.relativePath)));
|
|
246
|
+
const dirs2 = new Set(chunk2.files.map((f) => path.dirname(f.relativePath)));
|
|
197
247
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
metadata += ` ${ext}: ${count} files\n`;
|
|
201
|
-
}
|
|
248
|
+
return [...dirs1].filter((dir) => dirs2.has(dir));
|
|
249
|
+
}
|
|
202
250
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
}
|
|
251
|
+
/**
|
|
252
|
+
* Create chunks based on selected strategy
|
|
253
|
+
*/
|
|
254
|
+
createChunks() {
|
|
255
|
+
const strategy = this.chunking.strategy;
|
|
215
256
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
}
|
|
257
|
+
switch (strategy) {
|
|
258
|
+
case 'smart':
|
|
259
|
+
return this.createSmartChunks();
|
|
260
|
+
case 'size':
|
|
261
|
+
return this.createSizeBasedChunks();
|
|
262
|
+
case 'file':
|
|
263
|
+
return this.createFileBasedChunks();
|
|
264
|
+
case 'directory':
|
|
265
|
+
return this.createDirectoryBasedChunks();
|
|
266
|
+
case 'dependency':
|
|
267
|
+
return this.createDependencyBasedChunks();
|
|
268
|
+
default:
|
|
269
|
+
return this.createSizeBasedChunks();
|
|
250
270
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Smart chunking - AI-based semantic grouping
|
|
275
|
+
* Groups related files together based on directory and imports
|
|
276
|
+
*/
|
|
277
|
+
createSmartChunks() {
|
|
278
|
+
const chunks = [];
|
|
279
|
+
const maxTokens = this.chunking.maxTokensPerChunk;
|
|
280
|
+
|
|
281
|
+
// Sort files by directory and then by token count
|
|
282
|
+
const sortedFiles = [...this.analysisResults].sort((a, b) => {
|
|
283
|
+
const dirA = path.dirname(a.relativePath);
|
|
284
|
+
const dirB = path.dirname(b.relativePath);
|
|
285
|
+
if (dirA !== dirB) {
|
|
286
|
+
return dirA.localeCompare(dirB);
|
|
287
|
+
}
|
|
288
|
+
return b.tokens - a.tokens;
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
let currentChunk = { files: [], tokens: 0, directories: new Set() };
|
|
292
|
+
|
|
293
|
+
for (const file of sortedFiles) {
|
|
294
|
+
const fileDir = path.dirname(file.relativePath);
|
|
295
|
+
const wouldExceedLimit = currentChunk.tokens + file.tokens > maxTokens;
|
|
296
|
+
|
|
297
|
+
// Start new chunk if:
|
|
298
|
+
// 1. Would exceed token limit
|
|
299
|
+
// 2. Different directory and chunk already has content
|
|
300
|
+
if (
|
|
301
|
+
wouldExceedLimit ||
|
|
302
|
+
(currentChunk.files.length > 0 &&
|
|
303
|
+
!currentChunk.directories.has(fileDir) &&
|
|
304
|
+
currentChunk.tokens > maxTokens * 0.7)
|
|
305
|
+
) {
|
|
296
306
|
if (currentChunk.files.length > 0) {
|
|
297
|
-
|
|
307
|
+
chunks.push(currentChunk);
|
|
298
308
|
}
|
|
309
|
+
currentChunk = { files: [], tokens: 0, directories: new Set() };
|
|
310
|
+
}
|
|
299
311
|
|
|
300
|
-
|
|
312
|
+
currentChunk.files.push(file);
|
|
313
|
+
currentChunk.tokens += file.tokens;
|
|
314
|
+
currentChunk.directories.add(fileDir);
|
|
301
315
|
}
|
|
302
316
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const chunks = [];
|
|
308
|
-
const maxTokens = this.chunking.maxTokensPerChunk;
|
|
317
|
+
// Add last chunk
|
|
318
|
+
if (currentChunk.files.length > 0) {
|
|
319
|
+
chunks.push(currentChunk);
|
|
320
|
+
}
|
|
309
321
|
|
|
310
|
-
|
|
311
|
-
|
|
322
|
+
return chunks;
|
|
323
|
+
}
|
|
312
324
|
|
|
313
|
-
|
|
325
|
+
/**
|
|
326
|
+
* Size-based chunking - Fixed token size chunks
|
|
327
|
+
*/
|
|
328
|
+
createSizeBasedChunks() {
|
|
329
|
+
const chunks = [];
|
|
330
|
+
const maxTokens = this.chunking.maxTokensPerChunk;
|
|
314
331
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
chunks.push(currentChunk);
|
|
318
|
-
currentChunk = { files: [], tokens: 0 };
|
|
319
|
-
}
|
|
332
|
+
// Sort files by token count (largest first)
|
|
333
|
+
const sortedFiles = [...this.analysisResults].sort((a, b) => b.tokens - a.tokens);
|
|
320
334
|
|
|
321
|
-
|
|
322
|
-
currentChunk.tokens += file.tokens;
|
|
323
|
-
}
|
|
335
|
+
let currentChunk = { files: [], tokens: 0 };
|
|
324
336
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
337
|
+
for (const file of sortedFiles) {
|
|
338
|
+
if (currentChunk.tokens + file.tokens > maxTokens && currentChunk.files.length > 0) {
|
|
339
|
+
chunks.push(currentChunk);
|
|
340
|
+
currentChunk = { files: [], tokens: 0 };
|
|
341
|
+
}
|
|
328
342
|
|
|
329
|
-
|
|
343
|
+
currentChunk.files.push(file);
|
|
344
|
+
currentChunk.tokens += file.tokens;
|
|
330
345
|
}
|
|
331
346
|
|
|
332
|
-
|
|
333
|
-
|
|
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;
|
|
347
|
+
if (currentChunk.files.length > 0) {
|
|
348
|
+
chunks.push(currentChunk);
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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;
|
|
351
|
+
return chunks;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* File-based chunking - One file per chunk (with directory context)
|
|
356
|
+
*/
|
|
357
|
+
createFileBasedChunks() {
|
|
358
|
+
const chunks = [];
|
|
359
|
+
|
|
360
|
+
for (const file of this.analysisResults) {
|
|
361
|
+
chunks.push({
|
|
362
|
+
files: [file],
|
|
363
|
+
tokens: file.tokens,
|
|
364
|
+
metadata: {
|
|
365
|
+
directory: path.dirname(file.relativePath),
|
|
366
|
+
},
|
|
367
|
+
});
|
|
378
368
|
}
|
|
379
369
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
370
|
+
return chunks;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Directory-based chunking - One directory per chunk
|
|
375
|
+
*/
|
|
376
|
+
createDirectoryBasedChunks() {
|
|
377
|
+
const chunks = [];
|
|
378
|
+
const filesByDirectory = new Map();
|
|
379
|
+
|
|
380
|
+
// Group files by directory
|
|
381
|
+
for (const file of this.analysisResults) {
|
|
382
|
+
const dir = path.dirname(file.relativePath);
|
|
383
|
+
if (!filesByDirectory.has(dir)) {
|
|
384
|
+
filesByDirectory.set(dir, []);
|
|
385
|
+
}
|
|
386
|
+
filesByDirectory.get(dir).push(file);
|
|
388
387
|
}
|
|
389
388
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
389
|
+
// Create chunks from directories
|
|
390
|
+
for (const [dir, files] of filesByDirectory) {
|
|
391
|
+
const tokens = files.reduce((sum, f) => sum + f.tokens, 0);
|
|
392
|
+
chunks.push({
|
|
393
|
+
files,
|
|
394
|
+
tokens,
|
|
395
|
+
metadata: { directory: dir },
|
|
396
|
+
});
|
|
398
397
|
}
|
|
399
398
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
399
|
+
return chunks;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Dependency-based chunking - Group by import/dependency graph
|
|
404
|
+
* Placeholder for future implementation with AST analysis
|
|
405
|
+
*/
|
|
406
|
+
createDependencyBasedChunks() {
|
|
407
|
+
// For now, fall back to smart chunking
|
|
408
|
+
// In Phase 2, we'll implement proper dependency analysis
|
|
409
|
+
return this.createSmartChunks();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Generate chunk header
|
|
414
|
+
*/
|
|
415
|
+
generateChunkHeader(index, total, _chunk) {
|
|
416
|
+
let header = '='.repeat(60) + '\n';
|
|
417
|
+
header += `CHUNK ${index} of ${total}\n`;
|
|
418
|
+
header += '='.repeat(60);
|
|
419
|
+
return header;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Generate summary for a chunk
|
|
424
|
+
*/
|
|
425
|
+
generateChunkSummary(chunk) {
|
|
426
|
+
let summary = `Files in this chunk: ${chunk.files.length}\n`;
|
|
427
|
+
summary += `Estimated tokens: ${this.formatTokenCount(chunk.tokens)}`;
|
|
428
|
+
|
|
429
|
+
if (chunk.metadata?.directory) {
|
|
430
|
+
summary += `\nDirectory: ${chunk.metadata.directory}`;
|
|
431
|
+
}
|
|
410
432
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
current = current[part].children;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
433
|
+
return summary;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Generate directory tree for chunk
|
|
438
|
+
*/
|
|
439
|
+
generateChunkTree(chunk) {
|
|
440
|
+
let tree = 'Directory structure:\n';
|
|
441
|
+
|
|
442
|
+
// Build tree structure from chunk files
|
|
443
|
+
const fileTree = this.buildFileTreeFromFiles(chunk.files);
|
|
444
|
+
tree += this.formatTreeNode(fileTree, '', true);
|
|
445
|
+
|
|
446
|
+
return tree;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Build file tree from specific files
|
|
451
|
+
*/
|
|
452
|
+
buildFileTreeFromFiles(files) {
|
|
453
|
+
const root = {
|
|
454
|
+
name: path.basename(this.projectRoot),
|
|
455
|
+
type: 'directory',
|
|
456
|
+
children: {},
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
const sortedFiles = [...files].sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
460
|
+
|
|
461
|
+
for (const fileInfo of sortedFiles) {
|
|
462
|
+
const parts = fileInfo.relativePath.split('/');
|
|
463
|
+
let current = root.children;
|
|
464
|
+
|
|
465
|
+
for (let i = 0; i < parts.length; i++) {
|
|
466
|
+
const part = parts[i];
|
|
467
|
+
const isLastPart = i === parts.length - 1;
|
|
468
|
+
|
|
469
|
+
if (isLastPart) {
|
|
470
|
+
current[part] = {
|
|
471
|
+
name: part,
|
|
472
|
+
type: 'file',
|
|
473
|
+
fileInfo: fileInfo,
|
|
474
|
+
};
|
|
475
|
+
} else {
|
|
476
|
+
if (!current[part]) {
|
|
477
|
+
current[part] = {
|
|
478
|
+
name: part,
|
|
479
|
+
type: 'directory',
|
|
480
|
+
children: {},
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
current = current[part].children;
|
|
466
484
|
}
|
|
467
|
-
|
|
468
|
-
return root;
|
|
485
|
+
}
|
|
469
486
|
}
|
|
470
487
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
*/
|
|
474
|
-
generateChunkFileContents(chunk) {
|
|
475
|
-
let content = '';
|
|
488
|
+
return root;
|
|
489
|
+
}
|
|
476
490
|
|
|
477
|
-
|
|
478
|
-
|
|
491
|
+
/**
|
|
492
|
+
* Generate file contents for chunk
|
|
493
|
+
*/
|
|
494
|
+
generateChunkFileContents(chunk) {
|
|
495
|
+
let content = '';
|
|
479
496
|
|
|
480
|
-
|
|
481
|
-
|
|
497
|
+
// Sort files by token count (largest first)
|
|
498
|
+
const sortedFiles = [...chunk.files].sort((a, b) => b.tokens - a.tokens);
|
|
482
499
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
content += `FILE: ${fileInfo.relativePath}\n`;
|
|
486
|
-
content += '='.repeat(48) + '\n';
|
|
500
|
+
for (const fileInfo of sortedFiles) {
|
|
501
|
+
if (fileInfo.error) continue;
|
|
487
502
|
|
|
488
|
-
|
|
489
|
-
|
|
503
|
+
content += '\n';
|
|
504
|
+
content += '='.repeat(48) + '\n';
|
|
505
|
+
content += `FILE: ${fileInfo.relativePath}\n`;
|
|
506
|
+
content += '='.repeat(48) + '\n';
|
|
490
507
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
content += filteredContent;
|
|
494
|
-
} else {
|
|
495
|
-
content += fileContent;
|
|
496
|
-
}
|
|
508
|
+
try {
|
|
509
|
+
const fileContent = fs.readFileSync(fileInfo.path, 'utf8');
|
|
497
510
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}
|
|
511
|
+
if (this.methodFilterEnabled && this.isCodeFile(fileInfo.path)) {
|
|
512
|
+
const filteredContent = this.generateFilteredFileContent(fileContent, fileInfo.path);
|
|
513
|
+
content += this.redact(filteredContent);
|
|
514
|
+
} else {
|
|
515
|
+
content += this.redact(fileContent);
|
|
504
516
|
}
|
|
505
517
|
|
|
506
|
-
|
|
518
|
+
if (!content.endsWith('\n')) {
|
|
519
|
+
content += '\n';
|
|
520
|
+
}
|
|
521
|
+
} catch (error) {
|
|
522
|
+
content += `Error reading file: ${error.message}\n`;
|
|
523
|
+
}
|
|
507
524
|
}
|
|
508
525
|
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
}
|
|
526
|
+
return content;
|
|
527
|
+
}
|
|
524
528
|
|
|
525
|
-
|
|
526
|
-
|
|
529
|
+
/**
|
|
530
|
+
* Generate navigation info between chunks
|
|
531
|
+
*/
|
|
532
|
+
generateNavigationInfo(currentIndex, totalChunks) {
|
|
533
|
+
let nav = '\n';
|
|
534
|
+
nav += '='.repeat(60) + '\n';
|
|
535
|
+
nav += 'NAVIGATION\n';
|
|
536
|
+
nav += '='.repeat(60) + '\n';
|
|
527
537
|
|
|
528
|
-
|
|
538
|
+
if (currentIndex > 0) {
|
|
539
|
+
nav += `Previous: chunk-${currentIndex}.txt\n`;
|
|
540
|
+
}
|
|
541
|
+
if (currentIndex < totalChunks - 1) {
|
|
542
|
+
nav += `Next: chunk-${currentIndex + 2}.txt\n`;
|
|
529
543
|
}
|
|
530
544
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
let summary = `Directory: ${projectName}\n`;
|
|
534
|
-
summary += `Files analyzed: ${this.stats.totalFiles}\n`;
|
|
545
|
+
nav += `\nTotal Chunks: ${totalChunks}\n`;
|
|
546
|
+
nav += `Current Position: ${currentIndex + 1} / ${totalChunks}`;
|
|
535
547
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
summary += `Method filtering: ${mode} mode active\n`;
|
|
539
|
-
}
|
|
548
|
+
return nav;
|
|
549
|
+
}
|
|
540
550
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
551
|
+
generateSummary() {
|
|
552
|
+
const projectName = path.basename(this.projectRoot);
|
|
553
|
+
let summary = `Directory: ${projectName}\n`;
|
|
554
|
+
summary += `Files analyzed: ${this.stats.totalFiles}\n`;
|
|
545
555
|
|
|
546
|
-
|
|
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;
|
|
556
|
+
if (this.methodFilterEnabled) {
|
|
557
|
+
const mode = this.methodFilter.hasIncludeFile ? 'INCLUDE' : 'EXCLUDE';
|
|
558
|
+
summary += `Method filtering: ${mode} mode active\n`;
|
|
605
559
|
}
|
|
606
560
|
|
|
607
|
-
|
|
608
|
-
|
|
561
|
+
if (this.stats.totalTokens > 0) {
|
|
562
|
+
const tokenStr = this.formatTokenCount(this.stats.totalTokens);
|
|
563
|
+
summary += `\nEstimated tokens: ${tokenStr}`;
|
|
564
|
+
}
|
|
609
565
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
566
|
+
return summary;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
formatTokenCount(tokens) {
|
|
570
|
+
return TokenUtils.format(tokens);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
generateTree() {
|
|
574
|
+
let tree = 'Directory structure:\n';
|
|
575
|
+
|
|
576
|
+
// Build tree structure from analysis results
|
|
577
|
+
const fileTree = this.buildFileTree();
|
|
578
|
+
tree += this.formatTreeNode(fileTree, '', true);
|
|
579
|
+
|
|
580
|
+
return tree;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
buildFileTree() {
|
|
584
|
+
const root = {
|
|
585
|
+
name: path.basename(this.projectRoot),
|
|
586
|
+
type: 'directory',
|
|
587
|
+
children: {},
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
// Sort files by path for consistent tree structure
|
|
591
|
+
const sortedFiles = [...this.analysisResults].sort((a, b) =>
|
|
592
|
+
a.relativePath.localeCompare(b.relativePath)
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
for (const fileInfo of sortedFiles) {
|
|
596
|
+
const parts = fileInfo.relativePath.split('/');
|
|
597
|
+
let current = root.children;
|
|
598
|
+
|
|
599
|
+
for (let i = 0; i < parts.length; i++) {
|
|
600
|
+
const part = parts[i];
|
|
601
|
+
const isLastPart = i === parts.length - 1;
|
|
602
|
+
|
|
603
|
+
if (isLastPart) {
|
|
604
|
+
// It's a file
|
|
605
|
+
current[part] = {
|
|
606
|
+
name: part,
|
|
607
|
+
type: 'file',
|
|
608
|
+
fileInfo: fileInfo,
|
|
609
|
+
};
|
|
610
|
+
} else {
|
|
611
|
+
// It's a directory
|
|
612
|
+
if (!current[part]) {
|
|
613
|
+
current[part] = {
|
|
614
|
+
name: part,
|
|
615
|
+
type: 'directory',
|
|
616
|
+
children: {},
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
current = current[part].children;
|
|
614
620
|
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
615
623
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
const childKeys = Object.keys(node.children).sort();
|
|
624
|
+
return root;
|
|
625
|
+
}
|
|
619
626
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
const childIsLast = index === childKeys.length - 1;
|
|
623
|
-
result += this.formatTreeNode(child, newPrefix, childIsLast);
|
|
624
|
-
});
|
|
625
|
-
}
|
|
627
|
+
formatTreeNode(node, prefix = '', isLast = true) {
|
|
628
|
+
let result = '';
|
|
626
629
|
|
|
627
|
-
|
|
630
|
+
if (node.name) {
|
|
631
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
632
|
+
const displayName = node.type === 'directory' ? `${node.name}/` : node.name;
|
|
633
|
+
result += `${prefix}${connector}${displayName}\n`;
|
|
628
634
|
}
|
|
629
635
|
|
|
630
|
-
|
|
631
|
-
|
|
636
|
+
if (node.type === 'directory' && node.children) {
|
|
637
|
+
const newPrefix = node.name ? prefix + (isLast ? ' ' : '│ ') : '';
|
|
638
|
+
const childKeys = Object.keys(node.children).sort();
|
|
632
639
|
|
|
633
|
-
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
);
|
|
640
|
+
childKeys.forEach((key, index) => {
|
|
641
|
+
const child = node.children[key];
|
|
642
|
+
const childIsLast = index === childKeys.length - 1;
|
|
643
|
+
result += this.formatTreeNode(child, newPrefix, childIsLast);
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return result;
|
|
648
|
+
}
|
|
637
649
|
|
|
638
|
-
|
|
639
|
-
|
|
650
|
+
generateFileContents() {
|
|
651
|
+
let content = '';
|
|
640
652
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
content += `FILE: ${fileInfo.relativePath}\n`;
|
|
644
|
-
content += '='.repeat(48) + '\n';
|
|
653
|
+
// Sort files by token count (largest first)
|
|
654
|
+
const sortedFiles = [...this.analysisResults].sort((a, b) => b.tokens - a.tokens);
|
|
645
655
|
|
|
646
|
-
|
|
647
|
-
|
|
656
|
+
for (const fileInfo of sortedFiles) {
|
|
657
|
+
if (fileInfo.error) continue;
|
|
648
658
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
} else {
|
|
654
|
-
// Include full file content
|
|
655
|
-
content += fileContent;
|
|
656
|
-
}
|
|
659
|
+
content += '\n';
|
|
660
|
+
content += '='.repeat(48) + '\n';
|
|
661
|
+
content += `FILE: ${fileInfo.relativePath}\n`;
|
|
662
|
+
content += '='.repeat(48) + '\n';
|
|
657
663
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
+
try {
|
|
665
|
+
const fileContent = fs.readFileSync(fileInfo.path, 'utf8');
|
|
666
|
+
|
|
667
|
+
// Apply method-level filtering if enabled and file is a code file
|
|
668
|
+
if (this.methodFilterEnabled && this.isCodeFile(fileInfo.path)) {
|
|
669
|
+
const filteredContent = this.generateFilteredFileContent(fileContent, fileInfo.path);
|
|
670
|
+
content += this.redact(filteredContent);
|
|
671
|
+
} else {
|
|
672
|
+
// Include full file content
|
|
673
|
+
content += this.redact(fileContent);
|
|
664
674
|
}
|
|
665
675
|
|
|
666
|
-
|
|
676
|
+
if (!content.endsWith('\n')) {
|
|
677
|
+
content += '\n';
|
|
678
|
+
}
|
|
679
|
+
} catch (error) {
|
|
680
|
+
content += `Error reading file: ${error.message}\n`;
|
|
681
|
+
}
|
|
667
682
|
}
|
|
668
683
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
}
|
|
684
|
+
return content;
|
|
685
|
+
}
|
|
672
686
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
const fileName = path.basename(filePath, path.extname(filePath));
|
|
687
|
+
isCodeFile(filePath) {
|
|
688
|
+
return FileUtils.isCode(filePath);
|
|
689
|
+
}
|
|
677
690
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
691
|
+
generateFilteredFileContent(content, filePath) {
|
|
692
|
+
// Extract all methods from the file
|
|
693
|
+
const methods = this.methodAnalyzer.extractMethods(content, filePath);
|
|
694
|
+
const fileName = path.basename(filePath, path.extname(filePath));
|
|
682
695
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
696
|
+
// Filter methods
|
|
697
|
+
const filteredMethods = methods.filter((method) =>
|
|
698
|
+
this.methodFilter.shouldIncludeMethod(method.name, fileName)
|
|
699
|
+
);
|
|
687
700
|
|
|
688
|
-
|
|
689
|
-
|
|
701
|
+
if (filteredMethods.length === 0) {
|
|
702
|
+
// No methods pass the filter, return a note
|
|
703
|
+
return `// No methods matched the filter criteria for this file\n`;
|
|
704
|
+
}
|
|
690
705
|
|
|
691
|
-
|
|
706
|
+
// Build filtered content with only included methods
|
|
707
|
+
let filteredContent = `// File contains ${methods.length} methods, showing ${filteredMethods.length} filtered methods\n\n`;
|
|
692
708
|
|
|
693
|
-
|
|
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);
|
|
709
|
+
const lines = content.split('\n');
|
|
697
710
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
711
|
+
for (const method of filteredMethods) {
|
|
712
|
+
// Extract method content using a simple approach
|
|
713
|
+
// Find the method start and try to get its content
|
|
714
|
+
const methodContent = this.extractMethodBlock(lines, method.line - 1);
|
|
701
715
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
if (methodLines.length > 100) {
|
|
730
|
-
methodLines.push('// ... (method too long, truncated)');
|
|
731
|
-
break;
|
|
732
|
-
}
|
|
716
|
+
filteredContent += `// Method: ${method.name} (line ${method.line})\n`;
|
|
717
|
+
filteredContent += methodContent + '\n\n';
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
return filteredContent;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
extractMethodBlock(lines, startLine) {
|
|
724
|
+
// Simple method extraction: get the method and its body
|
|
725
|
+
let braceCount = 0;
|
|
726
|
+
let inMethod = false;
|
|
727
|
+
let methodLines = [];
|
|
728
|
+
|
|
729
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
730
|
+
const line = lines[i];
|
|
731
|
+
methodLines.push(line);
|
|
732
|
+
|
|
733
|
+
// Count braces to find method end
|
|
734
|
+
for (const char of line) {
|
|
735
|
+
if (char === '{') {
|
|
736
|
+
braceCount++;
|
|
737
|
+
inMethod = true;
|
|
738
|
+
} else if (char === '}') {
|
|
739
|
+
braceCount--;
|
|
740
|
+
if (inMethod && braceCount === 0) {
|
|
741
|
+
return methodLines.join('\n');
|
|
742
|
+
}
|
|
733
743
|
}
|
|
744
|
+
}
|
|
734
745
|
|
|
735
|
-
|
|
746
|
+
// Safety: limit to 100 lines per method
|
|
747
|
+
if (methodLines.length > 100) {
|
|
748
|
+
methodLines.push('// ... (method too long, truncated)');
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
736
751
|
}
|
|
737
752
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
753
|
+
return methodLines.join('\n');
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Write the digest; a chunked digest becomes one file per chunk next to outputPath
|
|
758
|
+
* (digest-chunk-1-of-3.txt, ...), listed in this.chunkFiles
|
|
759
|
+
* @param {string} outputPath
|
|
760
|
+
* @returns {number} Characters written
|
|
761
|
+
*/
|
|
762
|
+
saveToFile(outputPath) {
|
|
763
|
+
const digest = this.generateDigest();
|
|
764
|
+
this.chunkFiles = [];
|
|
765
|
+
|
|
766
|
+
if (Array.isArray(digest)) {
|
|
767
|
+
const { dir, name, ext } = path.parse(outputPath);
|
|
768
|
+
for (const chunk of digest) {
|
|
769
|
+
const chunkPath = path.join(dir, `${name}-chunk-${chunk.index}-of-${chunk.total}${ext}`);
|
|
770
|
+
fs.writeFileSync(chunkPath, chunk.content, 'utf8');
|
|
771
|
+
this.chunkFiles.push(chunkPath);
|
|
772
|
+
}
|
|
773
|
+
return digest.reduce((size, chunk) => size + chunk.content.length, 0);
|
|
742
774
|
}
|
|
743
|
-
}
|
|
744
775
|
|
|
776
|
+
fs.writeFileSync(outputPath, digest, 'utf8');
|
|
777
|
+
return digest.length;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
745
780
|
|
|
746
781
|
export default GitIngestFormatter;
|