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,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scanner - File System Scanning Module
|
|
3
|
+
* v3.0.0 - Modular architecture
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import GitIgnoreParser from '../parsers/gitignore-parser.js';
|
|
9
|
+
import FileUtils from '../utils/file-utils.js';
|
|
10
|
+
import { getLogger } from '../utils/logger.js';
|
|
11
|
+
|
|
12
|
+
const logger = getLogger('Scanner');
|
|
13
|
+
|
|
14
|
+
export class Scanner {
|
|
15
|
+
constructor(rootPath, options = {}) {
|
|
16
|
+
this.rootPath = rootPath;
|
|
17
|
+
this.options = {
|
|
18
|
+
respectGitignore: true,
|
|
19
|
+
followSymlinks: false,
|
|
20
|
+
maxDepth: Infinity,
|
|
21
|
+
...options
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Initialize GitIgnoreParser with correct file paths
|
|
25
|
+
const gitignorePath = path.join(rootPath, '.gitignore');
|
|
26
|
+
const contextIgnorePath = path.join(rootPath, '.contextignore');
|
|
27
|
+
const contextIncludePath = path.join(rootPath, '.contextinclude');
|
|
28
|
+
|
|
29
|
+
this.gitIgnore = new GitIgnoreParser(gitignorePath, contextIgnorePath, contextIncludePath);
|
|
30
|
+
this.stats = {
|
|
31
|
+
filesScanned: 0,
|
|
32
|
+
directoriesTraversed: 0,
|
|
33
|
+
filesIgnored: 0,
|
|
34
|
+
errors: 0
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
scan() {
|
|
39
|
+
logger.info(`Starting scan: ${this.rootPath}`);
|
|
40
|
+
const startTime = Date.now();
|
|
41
|
+
|
|
42
|
+
const files = this.scanDirectory(this.rootPath, 0);
|
|
43
|
+
|
|
44
|
+
const elapsed = Date.now() - startTime;
|
|
45
|
+
logger.info(`Scan complete: ${this.stats.filesScanned} files in ${elapsed}ms`);
|
|
46
|
+
|
|
47
|
+
return files;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
scanDirectory(dirPath, depth = 0) {
|
|
51
|
+
if (depth > this.options.maxDepth) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const files = [];
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
59
|
+
this.stats.directoriesTraversed++;
|
|
60
|
+
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
63
|
+
const relativePath = path.relative(this.rootPath, fullPath);
|
|
64
|
+
|
|
65
|
+
if (this.shouldIgnore(relativePath)) {
|
|
66
|
+
this.stats.filesIgnored++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (entry.isDirectory()) {
|
|
71
|
+
const subFiles = this.scanDirectory(fullPath, depth + 1);
|
|
72
|
+
files.push(...subFiles);
|
|
73
|
+
} else if (entry.isFile()) {
|
|
74
|
+
// Check if it's a text file (not binary)
|
|
75
|
+
try {
|
|
76
|
+
if (FileUtils.isText(fullPath)) {
|
|
77
|
+
const fileInfo = this.getFileInfo(fullPath, relativePath);
|
|
78
|
+
files.push(fileInfo);
|
|
79
|
+
this.stats.filesScanned++;
|
|
80
|
+
} else {
|
|
81
|
+
this.stats.filesIgnored++;
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// Skip files that can't be processed
|
|
85
|
+
this.stats.filesIgnored++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
logger.error(`Error scanning directory ${dirPath}: ${error.message}`);
|
|
91
|
+
this.stats.errors++;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return files;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
getFileInfo(fullPath, relativePath) {
|
|
98
|
+
const stats = fs.statSync(fullPath);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
path: fullPath,
|
|
102
|
+
relativePath: relativePath,
|
|
103
|
+
name: path.basename(fullPath),
|
|
104
|
+
extension: path.extname(fullPath),
|
|
105
|
+
size: stats.size,
|
|
106
|
+
modified: stats.mtime,
|
|
107
|
+
created: stats.birthtime
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
shouldIgnore(relativePath) {
|
|
112
|
+
return this.gitIgnore.isIgnored(null, relativePath);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
getStats() {
|
|
116
|
+
return { ...this.stats };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
reset() {
|
|
120
|
+
this.stats = {
|
|
121
|
+
filesScanned: 0,
|
|
122
|
+
directoriesTraversed: 0,
|
|
123
|
+
filesIgnored: 0,
|
|
124
|
+
errors: 0
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export default Scanner;
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format Registry
|
|
3
|
+
* Central registry for all output format exporters
|
|
4
|
+
* Supports: TOON, JSON, YAML, CSV, XML, Markdown, Protobuf, GitIngest
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import ToonFormatter from './toon-formatter.js';
|
|
8
|
+
import GitIngestFormatter from './gitingest-formatter.js';
|
|
9
|
+
|
|
10
|
+
class FormatRegistry {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.formatters = new Map();
|
|
13
|
+
this.registerDefaultFormatters();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Register all default formatters
|
|
18
|
+
*/
|
|
19
|
+
registerDefaultFormatters() {
|
|
20
|
+
// TOON format (40-50% token reduction)
|
|
21
|
+
this.register('toon', {
|
|
22
|
+
name: 'TOON',
|
|
23
|
+
description: 'Tabular Object Oriented Notation (40-50% token reduction)',
|
|
24
|
+
extension: '.toon',
|
|
25
|
+
mimeType: 'text/plain',
|
|
26
|
+
encoder: (data) => {
|
|
27
|
+
const formatter = new ToonFormatter();
|
|
28
|
+
return formatter.encode(data);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// JSON format (standard)
|
|
33
|
+
this.register('json', {
|
|
34
|
+
name: 'JSON',
|
|
35
|
+
description: 'JavaScript Object Notation (standard format)',
|
|
36
|
+
extension: '.json',
|
|
37
|
+
mimeType: 'application/json',
|
|
38
|
+
encoder: (data) => JSON.stringify(data, null, 2)
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// JSON Compact (no whitespace)
|
|
42
|
+
this.register('json-compact', {
|
|
43
|
+
name: 'JSON Compact',
|
|
44
|
+
description: 'Minified JSON without whitespace',
|
|
45
|
+
extension: '.json',
|
|
46
|
+
mimeType: 'application/json',
|
|
47
|
+
encoder: (data) => JSON.stringify(data)
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// YAML format
|
|
51
|
+
this.register('yaml', {
|
|
52
|
+
name: 'YAML',
|
|
53
|
+
description: 'YAML Ain\'t Markup Language (human-readable)',
|
|
54
|
+
extension: '.yaml',
|
|
55
|
+
mimeType: 'text/yaml',
|
|
56
|
+
encoder: (data) => this.encodeYAML(data)
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Markdown format
|
|
60
|
+
this.register('markdown', {
|
|
61
|
+
name: 'Markdown',
|
|
62
|
+
description: 'Markdown documentation format',
|
|
63
|
+
extension: '.md',
|
|
64
|
+
mimeType: 'text/markdown',
|
|
65
|
+
encoder: (data) => this.encodeMarkdown(data)
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// CSV format
|
|
69
|
+
this.register('csv', {
|
|
70
|
+
name: 'CSV',
|
|
71
|
+
description: 'Comma-Separated Values (spreadsheet compatible)',
|
|
72
|
+
extension: '.csv',
|
|
73
|
+
mimeType: 'text/csv',
|
|
74
|
+
encoder: (data) => this.encodeCSV(data)
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// XML format
|
|
78
|
+
this.register('xml', {
|
|
79
|
+
name: 'XML',
|
|
80
|
+
description: 'Extensible Markup Language (enterprise compatible)',
|
|
81
|
+
extension: '.xml',
|
|
82
|
+
mimeType: 'application/xml',
|
|
83
|
+
encoder: (data) => this.encodeXML(data)
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// GitIngest format
|
|
87
|
+
this.register('gitingest', {
|
|
88
|
+
name: 'GitIngest',
|
|
89
|
+
description: 'Single-file digest for LLM consumption',
|
|
90
|
+
extension: '.txt',
|
|
91
|
+
mimeType: 'text/plain',
|
|
92
|
+
encoder: (data, options) => {
|
|
93
|
+
// GitIngest uses a different interface
|
|
94
|
+
if (options && options.formatter) {
|
|
95
|
+
return options.formatter.generateDigest();
|
|
96
|
+
}
|
|
97
|
+
throw new Error('GitIngest format requires a GitIngestFormatter instance');
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Register a custom formatter
|
|
104
|
+
*/
|
|
105
|
+
register(name, formatter) {
|
|
106
|
+
if (!formatter.encoder || typeof formatter.encoder !== 'function') {
|
|
107
|
+
throw new Error(`Formatter '${name}' must have an encoder function`);
|
|
108
|
+
}
|
|
109
|
+
this.formatters.set(name, formatter);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get formatter by name
|
|
114
|
+
*/
|
|
115
|
+
get(name) {
|
|
116
|
+
const formatter = this.formatters.get(name);
|
|
117
|
+
if (!formatter) {
|
|
118
|
+
throw new Error(`Unknown format: ${name}. Available formats: ${this.listFormats().join(', ')}`);
|
|
119
|
+
}
|
|
120
|
+
return formatter;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Check if format exists
|
|
125
|
+
*/
|
|
126
|
+
has(name) {
|
|
127
|
+
return this.formatters.has(name);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* List all available formats
|
|
132
|
+
*/
|
|
133
|
+
listFormats() {
|
|
134
|
+
return Array.from(this.formatters.keys());
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get format info
|
|
139
|
+
*/
|
|
140
|
+
getInfo(name) {
|
|
141
|
+
const formatter = this.get(name);
|
|
142
|
+
return {
|
|
143
|
+
name: formatter.name,
|
|
144
|
+
description: formatter.description,
|
|
145
|
+
extension: formatter.extension,
|
|
146
|
+
mimeType: formatter.mimeType
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get all formats info
|
|
152
|
+
*/
|
|
153
|
+
getAllInfo() {
|
|
154
|
+
const info = {};
|
|
155
|
+
for (const [name, formatter] of this.formatters) {
|
|
156
|
+
info[name] = {
|
|
157
|
+
name: formatter.name,
|
|
158
|
+
description: formatter.description,
|
|
159
|
+
extension: formatter.extension,
|
|
160
|
+
mimeType: formatter.mimeType
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return info;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Encode data to specified format
|
|
168
|
+
*/
|
|
169
|
+
encode(name, data, options = {}) {
|
|
170
|
+
const formatter = this.get(name);
|
|
171
|
+
return formatter.encoder(data, options);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* YAML encoder (simple implementation)
|
|
176
|
+
*/
|
|
177
|
+
encodeYAML(data, indent = 0) {
|
|
178
|
+
const prefix = ' '.repeat(indent);
|
|
179
|
+
const type = this.getType(data);
|
|
180
|
+
|
|
181
|
+
switch (type) {
|
|
182
|
+
case 'null':
|
|
183
|
+
case 'undefined':
|
|
184
|
+
return 'null';
|
|
185
|
+
|
|
186
|
+
case 'boolean':
|
|
187
|
+
case 'number':
|
|
188
|
+
return String(data);
|
|
189
|
+
|
|
190
|
+
case 'string':
|
|
191
|
+
// Quote if contains special characters
|
|
192
|
+
if (/[:\n\r{}[\],&*#?|<>=!%@`]/.test(data)) {
|
|
193
|
+
return `"${data.replace(/"/g, '\\"')}"`;
|
|
194
|
+
}
|
|
195
|
+
return data;
|
|
196
|
+
|
|
197
|
+
case 'array':
|
|
198
|
+
if (data.length === 0) return '[]';
|
|
199
|
+
let arrResult = '\n';
|
|
200
|
+
for (const item of data) {
|
|
201
|
+
arrResult += prefix + '- ' + this.encodeYAML(item, indent + 1) + '\n';
|
|
202
|
+
}
|
|
203
|
+
return arrResult.trimEnd();
|
|
204
|
+
|
|
205
|
+
case 'object':
|
|
206
|
+
const keys = Object.keys(data);
|
|
207
|
+
if (keys.length === 0) return '{}';
|
|
208
|
+
let objResult = '\n';
|
|
209
|
+
for (const key of keys) {
|
|
210
|
+
objResult += prefix + key + ': ' + this.encodeYAML(data[key], indent + 1) + '\n';
|
|
211
|
+
}
|
|
212
|
+
return objResult.trimEnd();
|
|
213
|
+
|
|
214
|
+
default:
|
|
215
|
+
return 'null';
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Markdown encoder
|
|
221
|
+
*/
|
|
222
|
+
encodeMarkdown(data) {
|
|
223
|
+
const { project, paths, methods, methodStats } = data;
|
|
224
|
+
|
|
225
|
+
let md = `# ${project?.root || 'Project'} - Context Analysis\n\n`;
|
|
226
|
+
|
|
227
|
+
// Project summary
|
|
228
|
+
if (project) {
|
|
229
|
+
md += '## Project Summary\n\n';
|
|
230
|
+
md += `- **Total Files**: ${project.totalFiles}\n`;
|
|
231
|
+
md += `- **Total Tokens**: ${project.totalTokens?.toLocaleString()}\n\n`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Paths
|
|
235
|
+
if (paths) {
|
|
236
|
+
md += '## File Structure\n\n';
|
|
237
|
+
for (const [dir, files] of Object.entries(paths)) {
|
|
238
|
+
md += `### ${dir}\n\n`;
|
|
239
|
+
for (const file of files) {
|
|
240
|
+
md += `- ${file}\n`;
|
|
241
|
+
}
|
|
242
|
+
md += '\n';
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Methods
|
|
247
|
+
if (methods) {
|
|
248
|
+
md += '## Methods\n\n';
|
|
249
|
+
for (const [file, methodList] of Object.entries(methods)) {
|
|
250
|
+
md += `### ${file}\n\n`;
|
|
251
|
+
md += '| Method | Line | Tokens |\n';
|
|
252
|
+
md += '|--------|------|--------|\n';
|
|
253
|
+
for (const method of methodList) {
|
|
254
|
+
md += `| ${method.name} | ${method.line} | ${method.tokens} |\n`;
|
|
255
|
+
}
|
|
256
|
+
md += '\n';
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Method stats
|
|
261
|
+
if (methodStats) {
|
|
262
|
+
md += '## Statistics\n\n';
|
|
263
|
+
md += `- **Total Methods**: ${methodStats.totalMethods}\n`;
|
|
264
|
+
md += `- **Included Methods**: ${methodStats.includedMethods}\n`;
|
|
265
|
+
md += `- **Total Method Tokens**: ${methodStats.totalMethodTokens || 0}\n`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return md;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* CSV encoder
|
|
273
|
+
*/
|
|
274
|
+
encodeCSV(data) {
|
|
275
|
+
const { project, methods } = data;
|
|
276
|
+
|
|
277
|
+
if (!methods) {
|
|
278
|
+
// File-level CSV
|
|
279
|
+
return this.encodeFilesCSV(data);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Method-level CSV
|
|
283
|
+
let csv = 'File,Method,Line,Tokens\n';
|
|
284
|
+
|
|
285
|
+
for (const [file, methodList] of Object.entries(methods)) {
|
|
286
|
+
for (const method of methodList) {
|
|
287
|
+
csv += `"${file}","${method.name}",${method.line},${method.tokens}\n`;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return csv;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* CSV encoder for file-level data
|
|
296
|
+
*/
|
|
297
|
+
encodeFilesCSV(data) {
|
|
298
|
+
const { paths } = data;
|
|
299
|
+
|
|
300
|
+
if (!paths) {
|
|
301
|
+
return 'Directory,File\n';
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
let csv = 'Directory,File\n';
|
|
305
|
+
for (const [dir, files] of Object.entries(paths)) {
|
|
306
|
+
for (const file of files) {
|
|
307
|
+
csv += `"${dir}","${file}"\n`;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return csv;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* XML encoder
|
|
316
|
+
*/
|
|
317
|
+
encodeXML(data, indent = 0) {
|
|
318
|
+
const prefix = ' '.repeat(indent);
|
|
319
|
+
const type = this.getType(data);
|
|
320
|
+
|
|
321
|
+
if (indent === 0) {
|
|
322
|
+
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
|
323
|
+
xml += '<context>\n';
|
|
324
|
+
xml += this.encodeXML(data, 1);
|
|
325
|
+
xml += '</context>';
|
|
326
|
+
return xml;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
switch (type) {
|
|
330
|
+
case 'null':
|
|
331
|
+
case 'undefined':
|
|
332
|
+
return '';
|
|
333
|
+
|
|
334
|
+
case 'boolean':
|
|
335
|
+
case 'number':
|
|
336
|
+
case 'string':
|
|
337
|
+
return this.escapeXML(String(data));
|
|
338
|
+
|
|
339
|
+
case 'array':
|
|
340
|
+
let arrXML = '';
|
|
341
|
+
for (const item of data) {
|
|
342
|
+
arrXML += prefix + '<item>' + this.encodeXML(item, 0) + '</item>\n';
|
|
343
|
+
}
|
|
344
|
+
return arrXML;
|
|
345
|
+
|
|
346
|
+
case 'object':
|
|
347
|
+
let objXML = '';
|
|
348
|
+
for (const [key, value] of Object.entries(data)) {
|
|
349
|
+
const safeKey = this.sanitizeXMLTag(key);
|
|
350
|
+
const valueType = this.getType(value);
|
|
351
|
+
|
|
352
|
+
if (valueType === 'object' || valueType === 'array') {
|
|
353
|
+
objXML += prefix + `<${safeKey}>\n`;
|
|
354
|
+
objXML += this.encodeXML(value, indent + 1);
|
|
355
|
+
objXML += prefix + `</${safeKey}>\n`;
|
|
356
|
+
} else {
|
|
357
|
+
objXML += prefix + `<${safeKey}>`;
|
|
358
|
+
objXML += this.encodeXML(value, 0);
|
|
359
|
+
objXML += `</${safeKey}>\n`;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return objXML;
|
|
363
|
+
|
|
364
|
+
default:
|
|
365
|
+
return '';
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Escape XML special characters
|
|
371
|
+
*/
|
|
372
|
+
escapeXML(str) {
|
|
373
|
+
return str
|
|
374
|
+
.replace(/&/g, '&')
|
|
375
|
+
.replace(/</g, '<')
|
|
376
|
+
.replace(/>/g, '>')
|
|
377
|
+
.replace(/"/g, '"')
|
|
378
|
+
.replace(/'/g, ''');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Sanitize string for use as XML tag
|
|
383
|
+
*/
|
|
384
|
+
sanitizeXMLTag(str) {
|
|
385
|
+
return str.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Get value type
|
|
390
|
+
*/
|
|
391
|
+
getType(value) {
|
|
392
|
+
if (value === null) return 'null';
|
|
393
|
+
if (value === undefined) return 'undefined';
|
|
394
|
+
if (Array.isArray(value)) return 'array';
|
|
395
|
+
return typeof value;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Auto-detect best format for data
|
|
400
|
+
*/
|
|
401
|
+
suggestFormat(data) {
|
|
402
|
+
const { methods, paths } = data;
|
|
403
|
+
|
|
404
|
+
// Method-level data benefits most from TOON
|
|
405
|
+
if (methods) {
|
|
406
|
+
return 'toon';
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// File lists work well with TOON too
|
|
410
|
+
if (paths) {
|
|
411
|
+
return 'toon';
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Default to JSON
|
|
415
|
+
return 'json';
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export default FormatRegistry;
|