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,291 @@
1
+ /**
2
+ * TOON Format Wrapper
3
+ * Wraps official @toon-format/toon package with Ctxman integration
4
+ *
5
+ * Official TOON Spec v1.3: https://github.com/toon-format/spec
6
+ * Reference Implementation: https://github.com/toon-format/toon
7
+ *
8
+ * Features (100% Spec Compliant):
9
+ * - Array length markers [N]
10
+ * - Delimiter options: comma, tab, pipe
11
+ * - Length marker prefix: # (optional)
12
+ * - List format for non-uniform arrays
13
+ * - Smart quoting rules
14
+ * - Type normalizations
15
+ * - Full decoder support
16
+ */
17
+
18
+ class ToonFormatter {
19
+ constructor(options = {}) {
20
+ this.options = {
21
+ indent: options.indent || 2,
22
+ delimiter: options.delimiter || ',',
23
+ lengthMarker: options.lengthMarker || false
24
+ };
25
+
26
+ // Initialize indentChar
27
+ this.indentChar = ' '.repeat(this.options.indent);
28
+
29
+ // Lazy load official TOON package (ESM)
30
+ this._toonModule = null;
31
+ }
32
+
33
+ /**
34
+ * Get official TOON module (lazy load)
35
+ */
36
+ async getToonModule() {
37
+ if (!this._toonModule) {
38
+ this._toonModule = await import('@toon-format/toon');
39
+ }
40
+ return this._toonModule;
41
+ }
42
+
43
+ /**
44
+ * Encode data to TOON format (Async - uses official package)
45
+ */
46
+ async encodeAsync(data, options = {}) {
47
+ const toon = await this.getToonModule();
48
+ const opts = { ...this.options, ...options };
49
+ return toon.encode(data, opts);
50
+ }
51
+
52
+ /**
53
+ * Encode data to TOON format (Sync - uses custom implementation for backward compat)
54
+ */
55
+ encode(data, options = {}) {
56
+ // Fallback to synchronous custom implementation
57
+ // This maintains backward compatibility with existing code
58
+ const opts = { ...this.options, ...options };
59
+ return this.encodeSync(data, opts);
60
+ }
61
+
62
+ /**
63
+ * Synchronous encoder (custom implementation - basic features only)
64
+ * For full spec compliance, use encodeAsync()
65
+ */
66
+ encodeSync(data, options = {}) {
67
+ const { indent = 2 } = options;
68
+ this.indentChar = ' '.repeat(indent);
69
+ return this.encodeValue(data, 0, true);
70
+ }
71
+
72
+ encodeValue(value, indent = 0, compact = true) {
73
+ if (value === null || value === undefined) return 'null';
74
+ if (typeof value === 'boolean') return String(value);
75
+ if (typeof value === 'number') return String(value);
76
+ if (typeof value === 'string') return this.encodeString(value);
77
+ if (Array.isArray(value)) return this.encodeArray(value, indent, compact);
78
+ if (typeof value === 'object') return this.encodeObject(value, indent, compact);
79
+ return 'null';
80
+ }
81
+
82
+ encodeString(str) {
83
+ const escaped = str
84
+ .replace(/\\/g, '\\\\')
85
+ .replace(/"/g, '\\"')
86
+ .replace(/\n/g, '\\n')
87
+ .replace(/\r/g, '\\r')
88
+ .replace(/\t/g, '\\t');
89
+
90
+ if (/[\s,:{}\[\]]/.test(escaped)) {
91
+ return `"${escaped}"`;
92
+ }
93
+
94
+ return escaped;
95
+ }
96
+
97
+ encodeArray(arr, indent = 0, compact = true) {
98
+ if (arr.length === 0) return '[]';
99
+
100
+ if (this.isTabularArray(arr)) {
101
+ return this.encodeTabular(arr, indent, compact);
102
+ }
103
+
104
+ return this.encodeRegularArray(arr, indent, compact);
105
+ }
106
+
107
+ isTabularArray(arr) {
108
+ if (arr.length < 2) return false;
109
+ if (!arr.every(item => typeof item === 'object' && item !== null && !Array.isArray(item))) {
110
+ return false;
111
+ }
112
+
113
+ const firstKeys = Object.keys(arr[0]).sort();
114
+ return arr.every(item => {
115
+ const keys = Object.keys(item).sort();
116
+ return keys.length === firstKeys.length &&
117
+ keys.every((key, i) => key === firstKeys[i]);
118
+ });
119
+ }
120
+
121
+ encodeTabular(arr, indent = 0, compact = true) {
122
+ const prefix = this.indentChar.repeat(indent);
123
+ const keys = Object.keys(arr[0]).sort();
124
+
125
+ let result = `{${keys.join(',')}}:\n`;
126
+
127
+ for (const item of arr) {
128
+ result += prefix + this.indentChar;
129
+ const values = keys.map(key => this.encodeValue(item[key], 0, true));
130
+ result += values.join(',') + '\n';
131
+ }
132
+
133
+ return result;
134
+ }
135
+
136
+ encodeRegularArray(arr, indent = 0, compact = true) {
137
+ const prefix = this.indentChar.repeat(indent);
138
+
139
+ if (compact && arr.every(item => ['string', 'number', 'boolean'].includes(typeof item) || item === null)) {
140
+ return '[' + arr.map(item => this.encodeValue(item, 0, true)).join(',') + ']';
141
+ }
142
+
143
+ let result = '[\n';
144
+ for (let i = 0; i < arr.length; i++) {
145
+ result += prefix + this.indentChar + this.encodeValue(arr[i], indent + 1, compact);
146
+ if (i < arr.length - 1) result += ',';
147
+ result += '\n';
148
+ }
149
+ result += prefix + ']';
150
+
151
+ return result;
152
+ }
153
+
154
+ encodeObject(obj, indent = 0, compact = true) {
155
+ const keys = Object.keys(obj);
156
+ if (keys.length === 0) return '{}';
157
+
158
+ const prefix = this.indentChar.repeat(indent);
159
+ let result = '{\n';
160
+
161
+ for (let i = 0; i < keys.length; i++) {
162
+ const key = keys[i];
163
+ const value = obj[key];
164
+ result += prefix + this.indentChar + key + ': ';
165
+
166
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
167
+ result += '\n' + this.encodeObject(value, indent + 1, compact);
168
+ } else {
169
+ result += this.encodeValue(value, indent + 1, compact);
170
+ }
171
+
172
+ if (i < keys.length - 1) result += ',';
173
+ result += '\n';
174
+ }
175
+
176
+ result += prefix + '}';
177
+ return result;
178
+ }
179
+
180
+ /**
181
+ * Decode TOON string to JavaScript value (Async - uses official package)
182
+ */
183
+ async decodeAsync(toonString, options = {}) {
184
+ const toon = await this.getToonModule();
185
+ return toon.decode(toonString, options);
186
+ }
187
+
188
+ /**
189
+ * Decode TOON string (Sync - throws error, decoder not available in sync mode)
190
+ */
191
+ decode(toonString, options = {}) {
192
+ throw new Error('Synchronous decoder not available. Use decodeAsync() instead.');
193
+ }
194
+
195
+ /**
196
+ * Validate TOON string
197
+ */
198
+ validate(toonString) {
199
+ const errors = [];
200
+ let braceDepth = 0;
201
+ let bracketDepth = 0;
202
+
203
+ for (const char of toonString) {
204
+ if (char === '{') braceDepth++;
205
+ if (char === '}') braceDepth--;
206
+ if (char === '[') bracketDepth++;
207
+ if (char === ']') bracketDepth--;
208
+
209
+ if (braceDepth < 0) errors.push('Unbalanced braces: extra closing brace');
210
+ if (bracketDepth < 0) errors.push('Unbalanced brackets: extra closing bracket');
211
+ }
212
+
213
+ if (braceDepth > 0) errors.push('Unbalanced braces: missing closing braces');
214
+ if (bracketDepth > 0) errors.push('Unbalanced brackets: missing closing brackets');
215
+
216
+ return {
217
+ valid: errors.length === 0,
218
+ errors
219
+ };
220
+ }
221
+
222
+ /**
223
+ * Estimate tokens (GPT-4 compatible)
224
+ */
225
+ estimateTokens(toonString) {
226
+ return Math.ceil(toonString.length / 4);
227
+ }
228
+
229
+ /**
230
+ * Optimize TOON output
231
+ */
232
+ optimize(toonString) {
233
+ const lines = toonString.split('\n');
234
+ const optimized = lines.map(line => line.trimEnd()).join('\n');
235
+ return optimized.replace(/\n{3,}/g, '\n\n');
236
+ }
237
+
238
+ /**
239
+ * Minify TOON output
240
+ */
241
+ minify(toonString) {
242
+ return toonString
243
+ .split('\n')
244
+ .map(line => line.trim())
245
+ .filter(line => line.length > 0)
246
+ .join('\n');
247
+ }
248
+
249
+ /**
250
+ * Compare TOON with JSON (token savings)
251
+ */
252
+ compareWithJSON(data) {
253
+ const toon = this.encode(data);
254
+ const json = JSON.stringify(data, null, 2);
255
+
256
+ const toonSize = toon.length;
257
+ const jsonSize = json.length;
258
+ const savings = jsonSize - toonSize;
259
+ const savingsPercentage = ((savings / jsonSize) * 100).toFixed(1);
260
+
261
+ return {
262
+ toon,
263
+ json,
264
+ toonSize,
265
+ jsonSize,
266
+ savings,
267
+ savingsPercentage: parseFloat(savingsPercentage),
268
+ toonTokens: this.estimateTokens(toon),
269
+ jsonTokens: this.estimateTokens(json)
270
+ };
271
+ }
272
+
273
+ /**
274
+ * Compare with official TOON encoder (async)
275
+ */
276
+ async compareWithOfficial(data, options = {}) {
277
+ const customEncoded = this.encode(data);
278
+ const officialEncoded = await this.encodeAsync(data, options);
279
+
280
+ return {
281
+ custom: customEncoded,
282
+ official: officialEncoded,
283
+ customSize: customEncoded.length,
284
+ officialSize: officialEncoded.length,
285
+ difference: customEncoded.length - officialEncoded.length,
286
+ officialIsBetter: officialEncoded.length < customEncoded.length
287
+ };
288
+ }
289
+ }
290
+
291
+ export default ToonFormatter;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * BlameTracker - Author Attribution Module
3
+ * v3.0.0 - Complete Git integration
4
+ *
5
+ * Responsibilities:
6
+ * - Track file ownership
7
+ * - Author contribution analysis
8
+ * - Code ownership distribution
9
+ * - Hot spot detection
10
+ */
11
+
12
+ import GitClient from './GitClient.js';
13
+ import { getLogger } from '../../utils/logger.js';
14
+
15
+ const logger = getLogger('BlameTracker');
16
+
17
+ export class BlameTracker {
18
+ constructor(repositoryPath) {
19
+ this.git = new GitClient(repositoryPath);
20
+ }
21
+
22
+ /**
23
+ * Get primary author for file (most commits)
24
+ * @param {string} filePath
25
+ * @returns {Author|null}
26
+ */
27
+ getPrimaryAuthor(filePath) {
28
+ const authors = this.git.getFileAuthors(filePath);
29
+ return authors.length > 0 ? authors[0] : null;
30
+ }
31
+
32
+ /**
33
+ * Get author contribution distribution
34
+ * @param {Array<string>} filePaths
35
+ * @returns {Map<string, AuthorStats>}
36
+ */
37
+ getAuthorContributions(filePaths) {
38
+ const contributions = new Map();
39
+
40
+ for (const filePath of filePaths) {
41
+ const authors = this.git.getFileAuthors(filePath);
42
+
43
+ for (const author of authors) {
44
+ if (!contributions.has(author.email)) {
45
+ contributions.set(author.email, {
46
+ name: author.name,
47
+ email: author.email,
48
+ filesOwned: 0,
49
+ totalCommits: 0
50
+ });
51
+ }
52
+
53
+ const stats = contributions.get(author.email);
54
+ stats.filesOwned++;
55
+ stats.totalCommits += author.commits;
56
+ }
57
+ }
58
+
59
+ return contributions;
60
+ }
61
+
62
+ /**
63
+ * Get code ownership map
64
+ * @param {Array<string>} filePaths
65
+ * @returns {Map<string, Author>}
66
+ */
67
+ getOwnershipMap(filePaths) {
68
+ const ownership = new Map();
69
+
70
+ for (const filePath of filePaths) {
71
+ const owner = this.getPrimaryAuthor(filePath);
72
+ if (owner) {
73
+ ownership.set(filePath, owner);
74
+ }
75
+ }
76
+
77
+ return ownership;
78
+ }
79
+
80
+ /**
81
+ * Detect hot spots (frequently changed files)
82
+ * @param {Array<string>} filePaths
83
+ * @param {number} threshold - Minimum commits to be considered hot
84
+ * @returns {Array<HotSpot>}
85
+ */
86
+ detectHotSpots(filePaths, threshold = 10) {
87
+ const hotSpots = [];
88
+
89
+ for (const filePath of filePaths) {
90
+ const commitCount = this.git.getCommitCount(filePath);
91
+
92
+ if (commitCount >= threshold) {
93
+ const authors = this.git.getFileAuthors(filePath);
94
+
95
+ hotSpots.push({
96
+ path: filePath,
97
+ commitCount,
98
+ authorCount: authors.length,
99
+ primaryAuthor: authors[0],
100
+ risk: this.calculateRiskScore(commitCount, authors.length)
101
+ });
102
+ }
103
+ }
104
+
105
+ return hotSpots.sort((a, b) => b.risk - a.risk);
106
+ }
107
+
108
+ /**
109
+ * Calculate risk score for a file
110
+ * @param {number} commitCount
111
+ * @param {number} authorCount
112
+ * @returns {number}
113
+ */
114
+ calculateRiskScore(commitCount, authorCount) {
115
+ // High commit count + multiple authors = higher risk
116
+ const commitScore = Math.log(commitCount + 1) * 10;
117
+ const authorScore = authorCount * 5;
118
+ return commitScore + authorScore;
119
+ }
120
+
121
+ /**
122
+ * Get file ownership summary
123
+ * @param {Array<string>} filePaths
124
+ * @returns {object}
125
+ */
126
+ getOwnershipSummary(filePaths) {
127
+ const contributions = this.getAuthorContributions(filePaths);
128
+
129
+ const topContributors = Array.from(contributions.values())
130
+ .sort((a, b) => b.totalCommits - a.totalCommits)
131
+ .slice(0, 10);
132
+
133
+ return {
134
+ totalAuthors: contributions.size,
135
+ totalFiles: filePaths.length,
136
+ topContributors,
137
+ averageFilesPerAuthor: filePaths.length / contributions.size
138
+ };
139
+ }
140
+ }
141
+
142
+ export default BlameTracker;
@@ -0,0 +1,260 @@
1
+ /**
2
+ * DiffAnalyzer - Git Diff Analysis Module
3
+ * v3.0.0 - Complete Git integration
4
+ *
5
+ * Responsibilities:
6
+ * - Analyze git diffs
7
+ * - Detect change impact
8
+ * - Find related files
9
+ * - Generate change summaries
10
+ */
11
+
12
+ import GitClient from './GitClient.js';
13
+ import { getLogger } from '../../utils/logger.js';
14
+
15
+ const logger = getLogger('DiffAnalyzer');
16
+
17
+ export class DiffAnalyzer {
18
+ constructor(repositoryPath) {
19
+ this.git = new GitClient(repositoryPath);
20
+ this.repoPath = repositoryPath;
21
+ }
22
+
23
+ /**
24
+ * Analyze changes since a reference point
25
+ * @param {string} since - Commit/branch reference
26
+ * @returns {ChangeAnalysis}
27
+ */
28
+ analyzeChanges(since = null) {
29
+ logger.info(`Analyzing changes since: ${since || 'uncommitted'}`);
30
+
31
+ const changedFiles = since
32
+ ? this.git.getChangedFiles(since)
33
+ : this.git.getAllModifiedFiles();
34
+
35
+ const analysis = {
36
+ changedFiles: changedFiles,
37
+ totalChangedFiles: changedFiles.length,
38
+ since: since,
39
+ relatedFiles: [],
40
+ impact: this.calculateImpact(changedFiles)
41
+ };
42
+
43
+ return analysis;
44
+ }
45
+
46
+ /**
47
+ * Get changed files with detailed diff
48
+ * @param {string} since
49
+ * @returns {Array<FileChange>}
50
+ */
51
+ getDetailedChanges(since = null) {
52
+ const changedFiles = since
53
+ ? this.git.getChangedFiles(since)
54
+ : this.git.getAllModifiedFiles();
55
+
56
+ return changedFiles.map(filePath => {
57
+ const diff = this.getFileDiff(filePath, since);
58
+ return {
59
+ path: filePath,
60
+ ...diff
61
+ };
62
+ });
63
+ }
64
+
65
+ /**
66
+ * Get diff for specific file
67
+ * @param {string} filePath
68
+ * @param {string} since
69
+ * @returns {FileDiff}
70
+ */
71
+ getFileDiff(filePath, since = null) {
72
+ try {
73
+ const command = since
74
+ ? `diff ${since} -- "${filePath}"`
75
+ : `diff -- "${filePath}"`;
76
+
77
+ const diffOutput = this.git.exec(command);
78
+ const stats = this.parseDiffStats(diffOutput);
79
+
80
+ return {
81
+ added: stats.added,
82
+ deleted: stats.deleted,
83
+ modified: stats.added + stats.deleted,
84
+ diff: diffOutput
85
+ };
86
+ } catch (error) {
87
+ logger.warn(`Failed to get diff for ${filePath}: ${error.message}`);
88
+ return { added: 0, deleted: 0, modified: 0, diff: '' };
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Parse diff statistics
94
+ * @param {string} diffOutput
95
+ * @returns {object}
96
+ */
97
+ parseDiffStats(diffOutput) {
98
+ let added = 0;
99
+ let deleted = 0;
100
+
101
+ const lines = diffOutput.split('\n');
102
+ for (const line of lines) {
103
+ if (line.startsWith('+') && !line.startsWith('+++')) {
104
+ added++;
105
+ } else if (line.startsWith('-') && !line.startsWith('---')) {
106
+ deleted++;
107
+ }
108
+ }
109
+
110
+ return { added, deleted };
111
+ }
112
+
113
+ /**
114
+ * Calculate change impact score
115
+ * @param {Array<string>} changedFiles
116
+ * @returns {object}
117
+ */
118
+ calculateImpact(changedFiles) {
119
+ const impact = {
120
+ level: 'low',
121
+ score: 0,
122
+ details: {
123
+ coreFiles: 0,
124
+ configFiles: 0,
125
+ testFiles: 0,
126
+ documentationFiles: 0
127
+ }
128
+ };
129
+
130
+ for (const file of changedFiles) {
131
+ const fileLower = file.toLowerCase();
132
+
133
+ // Core files (high impact)
134
+ if (fileLower.includes('src/') || fileLower.includes('lib/') ||
135
+ fileLower.includes('index.') || fileLower.includes('main.') ||
136
+ fileLower.includes('app.') || fileLower.includes('server.')) {
137
+ impact.details.coreFiles++;
138
+ impact.score += 10;
139
+ }
140
+
141
+ // Config files (medium-high impact)
142
+ if (fileLower.endsWith('.json') || fileLower.endsWith('.yml') ||
143
+ fileLower.endsWith('.yaml') || fileLower.endsWith('.config.js')) {
144
+ impact.details.configFiles++;
145
+ impact.score += 7;
146
+ }
147
+
148
+ // Test files (medium impact)
149
+ if (fileLower.includes('test') || fileLower.includes('spec')) {
150
+ impact.details.testFiles++;
151
+ impact.score += 3;
152
+ }
153
+
154
+ // Documentation (low impact)
155
+ if (fileLower.endsWith('.md') || fileLower.includes('docs/')) {
156
+ impact.details.documentationFiles++;
157
+ impact.score += 1;
158
+ }
159
+ }
160
+
161
+ // Determine impact level
162
+ if (impact.score >= 50) {
163
+ impact.level = 'critical';
164
+ } else if (impact.score >= 25) {
165
+ impact.level = 'high';
166
+ } else if (impact.score >= 10) {
167
+ impact.level = 'medium';
168
+ } else {
169
+ impact.level = 'low';
170
+ }
171
+
172
+ return impact;
173
+ }
174
+
175
+ /**
176
+ * Find related files based on imports/dependencies
177
+ * @param {Array<string>} changedFiles
178
+ * @returns {Array<string>}
179
+ */
180
+ findRelatedFiles(changedFiles) {
181
+ // TODO: Implement import graph analysis
182
+ // For now, return empty array
183
+ logger.debug('Related files detection not yet implemented');
184
+ return [];
185
+ }
186
+
187
+ /**
188
+ * Get file change frequency (how often file is modified)
189
+ * @param {string} filePath
190
+ * @returns {number}
191
+ */
192
+ getFileChangeFrequency(filePath) {
193
+ return this.git.getCommitCount(filePath);
194
+ }
195
+
196
+ /**
197
+ * Compare two branches
198
+ * @param {string} base - Base branch
199
+ * @param {string} compare - Compare branch
200
+ * @returns {BranchComparison}
201
+ */
202
+ compareBranches(base, compare) {
203
+ try {
204
+ const command = `diff --name-status ${base}...${compare}`;
205
+ const output = this.git.exec(command);
206
+
207
+ const changes = {
208
+ added: [],
209
+ modified: [],
210
+ deleted: [],
211
+ renamed: []
212
+ };
213
+
214
+ if (!output) return changes;
215
+
216
+ const lines = output.split('\n');
217
+ for (const line of lines) {
218
+ const [status, ...pathParts] = line.split('\t');
219
+ const filePath = pathParts.join('\t');
220
+
221
+ if (status === 'A') changes.added.push(filePath);
222
+ else if (status === 'M') changes.modified.push(filePath);
223
+ else if (status === 'D') changes.deleted.push(filePath);
224
+ else if (status.startsWith('R')) changes.renamed.push(filePath);
225
+ }
226
+
227
+ return changes;
228
+ } catch (error) {
229
+ logger.error(`Branch comparison failed: ${error.message}`);
230
+ throw error;
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Get commit range statistics
236
+ * @param {string} range - Commit range (e.g., 'v1.0.0..v2.0.0')
237
+ * @returns {object}
238
+ */
239
+ getRangeStats(range) {
240
+ try {
241
+ const command = `diff --shortstat ${range}`;
242
+ const output = this.git.exec(command);
243
+
244
+ // Parse: "3 files changed, 45 insertions(+), 12 deletions(-)"
245
+ const match = output.match(/(\d+) file[s]? changed(?:, (\d+) insertion[s]?\(\+\))?(?:, (\d+) deletion[s]?\(-\))?/);
246
+
247
+ if (!match) return null;
248
+
249
+ return {
250
+ filesChanged: parseInt(match[1], 10),
251
+ insertions: parseInt(match[2] || 0, 10),
252
+ deletions: parseInt(match[3] || 0, 10)
253
+ };
254
+ } catch (error) {
255
+ return null;
256
+ }
257
+ }
258
+ }
259
+
260
+ export default DiffAnalyzer;