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.
Files changed (190) hide show
  1. package/.ctxman/custom-profiles.example.json +20 -0
  2. package/.ctxman/llm-profiles.json +131 -0
  3. package/.ctxman/logs/ctxman-2026-07-02.log +156 -0
  4. package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
  5. package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
  6. package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
  7. package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
  8. package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
  9. package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
  10. package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
  11. package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
  12. package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
  13. package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
  14. package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
  15. package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
  16. package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
  17. package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
  18. package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
  19. package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
  20. package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
  21. package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
  22. package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
  23. package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
  24. package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
  25. package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
  26. package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
  27. package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
  28. package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
  29. package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
  30. package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
  31. package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
  32. package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
  33. package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
  34. package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
  35. package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
  36. package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
  37. package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
  38. package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
  39. package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
  40. package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
  41. package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
  42. package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
  43. package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
  44. package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
  45. package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
  46. package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
  47. package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
  48. package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
  49. package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
  50. package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
  51. package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
  52. package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
  53. package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
  54. package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
  55. package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
  56. package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
  57. package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
  58. package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
  59. package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
  60. package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
  61. package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
  62. package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
  63. package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
  64. package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
  65. package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
  66. package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
  67. package/.ctxman/wizard-profiles/code-review/.contextignore +41 -0
  68. package/.ctxman/wizard-profiles/code-review/.contextinclude +30 -0
  69. package/.ctxman/wizard-profiles/code-review/.methodignore +26 -0
  70. package/.ctxman/wizard-profiles/code-review/.methodinclude +47 -0
  71. package/.ctxman/wizard-profiles/code-review/profile.json +26 -0
  72. package/.ctxman/wizard-profiles/documentation/.contextignore +21 -0
  73. package/.ctxman/wizard-profiles/documentation/.contextinclude +24 -0
  74. package/.ctxman/wizard-profiles/documentation/.methodignore +19 -0
  75. package/.ctxman/wizard-profiles/documentation/.methodinclude +44 -0
  76. package/.ctxman/wizard-profiles/documentation/profile.json +27 -0
  77. package/.ctxman/wizard-profiles/full/.contextignore +23 -0
  78. package/.ctxman/wizard-profiles/full/.contextinclude +38 -0
  79. package/.ctxman/wizard-profiles/full/.methodignore +5 -0
  80. package/.ctxman/wizard-profiles/full/.methodinclude +5 -0
  81. package/.ctxman/wizard-profiles/full/profile.json +28 -0
  82. package/.ctxman/wizard-profiles/llm-explain/.contextignore +25 -0
  83. package/.ctxman/wizard-profiles/llm-explain/.contextinclude +33 -0
  84. package/.ctxman/wizard-profiles/llm-explain/.methodignore +19 -0
  85. package/.ctxman/wizard-profiles/llm-explain/.methodinclude +48 -0
  86. package/.ctxman/wizard-profiles/llm-explain/profile.json +27 -0
  87. package/.ctxman/wizard-profiles/minimal/.contextignore +27 -0
  88. package/.ctxman/wizard-profiles/minimal/.contextinclude +14 -0
  89. package/.ctxman/wizard-profiles/minimal/.methodignore +22 -0
  90. package/.ctxman/wizard-profiles/minimal/.methodinclude +18 -0
  91. package/.ctxman/wizard-profiles/minimal/profile.json +26 -0
  92. package/.ctxman/wizard-profiles/security-audit/.contextignore +36 -0
  93. package/.ctxman/wizard-profiles/security-audit/.contextinclude +37 -0
  94. package/.ctxman/wizard-profiles/security-audit/.methodignore +29 -0
  95. package/.ctxman/wizard-profiles/security-audit/.methodinclude +60 -0
  96. package/.ctxman/wizard-profiles/security-audit/profile.json +34 -0
  97. package/README.md +1167 -0
  98. package/bin/cli.js +650 -0
  99. package/bin/cm-ask.js +95 -0
  100. package/bin/cm-gitingest.js +134 -0
  101. package/bin/cm-update.js +175 -0
  102. package/bin/mcp-server.js +31 -0
  103. package/ctxman.js +236 -0
  104. package/examples/README.md +298 -0
  105. package/examples/custom-llm-profiles.example.json +19 -0
  106. package/examples/wizard-profiles/code-review/.contextignore +41 -0
  107. package/examples/wizard-profiles/code-review/.contextinclude +30 -0
  108. package/examples/wizard-profiles/code-review/.methodignore +26 -0
  109. package/examples/wizard-profiles/code-review/.methodinclude +47 -0
  110. package/examples/wizard-profiles/code-review/profile.json +26 -0
  111. package/examples/wizard-profiles/documentation/.contextignore +21 -0
  112. package/examples/wizard-profiles/documentation/.contextinclude +24 -0
  113. package/examples/wizard-profiles/documentation/.methodignore +19 -0
  114. package/examples/wizard-profiles/documentation/.methodinclude +44 -0
  115. package/examples/wizard-profiles/documentation/profile.json +27 -0
  116. package/examples/wizard-profiles/full/.contextignore +23 -0
  117. package/examples/wizard-profiles/full/.contextinclude +38 -0
  118. package/examples/wizard-profiles/full/.methodignore +5 -0
  119. package/examples/wizard-profiles/full/.methodinclude +5 -0
  120. package/examples/wizard-profiles/full/profile.json +28 -0
  121. package/examples/wizard-profiles/llm-explain/.contextignore +25 -0
  122. package/examples/wizard-profiles/llm-explain/.contextinclude +33 -0
  123. package/examples/wizard-profiles/llm-explain/.methodignore +19 -0
  124. package/examples/wizard-profiles/llm-explain/.methodinclude +48 -0
  125. package/examples/wizard-profiles/llm-explain/profile.json +27 -0
  126. package/examples/wizard-profiles/minimal/.contextignore +27 -0
  127. package/examples/wizard-profiles/minimal/.contextinclude +14 -0
  128. package/examples/wizard-profiles/minimal/.methodignore +22 -0
  129. package/examples/wizard-profiles/minimal/.methodinclude +18 -0
  130. package/examples/wizard-profiles/minimal/profile.json +26 -0
  131. package/examples/wizard-profiles/security-audit/.contextignore +36 -0
  132. package/examples/wizard-profiles/security-audit/.contextinclude +37 -0
  133. package/examples/wizard-profiles/security-audit/.methodignore +29 -0
  134. package/examples/wizard-profiles/security-audit/.methodinclude +60 -0
  135. package/examples/wizard-profiles/security-audit/profile.json +34 -0
  136. package/index.js +68 -0
  137. package/lib/agents/pr-failure-agent.js +201 -0
  138. package/lib/analyzers/go-method-analyzer.js +126 -0
  139. package/lib/analyzers/method-analyzer.js +457 -0
  140. package/lib/analyzers/token-calculator.js +577 -0
  141. package/lib/api/mcp/prompts.js +319 -0
  142. package/lib/api/mcp/resources.js +269 -0
  143. package/lib/api/mcp/server.js +339 -0
  144. package/lib/api/mcp/transports/HttpSseTransport.js +63 -0
  145. package/lib/api/rest/server.js +405 -0
  146. package/lib/cache/CacheManager.js +388 -0
  147. package/lib/core/Analyzer.js +180 -0
  148. package/lib/core/ContextBuilder.js +325 -0
  149. package/lib/core/Reporter.js +270 -0
  150. package/lib/core/Scanner.js +129 -0
  151. package/lib/formatters/format-registry.js +419 -0
  152. package/lib/formatters/gitingest-formatter.js +746 -0
  153. package/lib/formatters/toon-formatter-v1.3.js +731 -0
  154. package/lib/formatters/toon-formatter.js +291 -0
  155. package/lib/integrations/git/BlameTracker.js +142 -0
  156. package/lib/integrations/git/DiffAnalyzer.js +260 -0
  157. package/lib/integrations/git/GitClient.js +336 -0
  158. package/lib/parsers/gitignore-parser.js +127 -0
  159. package/lib/parsers/method-filter-parser.js +50 -0
  160. package/lib/plugins/ExporterPlugin.js +102 -0
  161. package/lib/plugins/LanguagePlugin.js +100 -0
  162. package/lib/plugins/PluginManager.js +302 -0
  163. package/lib/rag/DataSourcePlugin.js +31 -0
  164. package/lib/rag/EmbeddingProvider.js +75 -0
  165. package/lib/rag/Indexer.js +83 -0
  166. package/lib/rag/VectorStoreFactory.js +24 -0
  167. package/lib/rag/sources/FileDataSource.js +70 -0
  168. package/lib/rag/stores/BaseVectorStore.js +43 -0
  169. package/lib/rag/stores/ChromaDBStore.js +85 -0
  170. package/lib/rag/stores/LanceDBStore.js +109 -0
  171. package/lib/rag/stores/PineconeStore.js +74 -0
  172. package/lib/ui/dashboard.js +109 -0
  173. package/lib/ui/index.js +15 -0
  174. package/lib/ui/progress-bar.js +110 -0
  175. package/lib/ui/select-input.js +51 -0
  176. package/lib/ui/wizard.js +359 -0
  177. package/lib/utils/clipboard-utils.js +66 -0
  178. package/lib/utils/config-utils.js +94 -0
  179. package/lib/utils/error-handler.js +145 -0
  180. package/lib/utils/file-utils.js +89 -0
  181. package/lib/utils/format-converter.js +325 -0
  182. package/lib/utils/git-utils.js +337 -0
  183. package/lib/utils/llm-detector.js +409 -0
  184. package/lib/utils/logger.js +278 -0
  185. package/lib/utils/token-utils.js +200 -0
  186. package/lib/utils/tokenizer-adapter.js +454 -0
  187. package/lib/utils/updater.js +565 -0
  188. package/lib/watch/FileWatcher.js +208 -0
  189. package/lib/watch/IncrementalAnalyzer.js +206 -0
  190. 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, '&amp;')
375
+ .replace(/</g, '&lt;')
376
+ .replace(/>/g, '&gt;')
377
+ .replace(/"/g, '&quot;')
378
+ .replace(/'/g, '&apos;');
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;