ctxman 3.0.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/.ctxman/llm-profiles.json +1067 -39
  2. package/.ctxman/profiles/backend-team.json +13 -0
  3. package/.ctxman/profiles/default.json +12 -0
  4. package/.ctxman/profiles/devops-team.json +21 -0
  5. package/.ctxman/profiles/frontend-team.json +20 -0
  6. package/.ctxman/templates/bug-fix.json +17 -0
  7. package/.ctxman/templates/code-review.json +9 -0
  8. package/.ctxman/templates/documentation.json +9 -0
  9. package/.ctxman/templates/feature.json +9 -0
  10. package/.ctxman/templates/full-context.json +9 -0
  11. package/.ctxman/templates/refactoring.json +18 -0
  12. package/.ctxman/templates/testing.json +9 -0
  13. package/.ctxman/wizard-profiles/security-audit/profile.json +7 -1
  14. package/LICENSE +21 -0
  15. package/README.md +381 -136
  16. package/bin/cli.js +1880 -529
  17. package/bin/cm-ask.js +18 -67
  18. package/bin/cm-audit.js +121 -0
  19. package/bin/cm-count.js +151 -0
  20. package/bin/cm-gitingest.js +128 -115
  21. package/bin/cm-map.js +242 -0
  22. package/bin/cm-mcp.js +293 -0
  23. package/bin/cm-pack.js +405 -0
  24. package/bin/cm-policy.js +170 -0
  25. package/bin/cm-pr.js +248 -0
  26. package/bin/cm-search.js +178 -0
  27. package/bin/cm-update.js +128 -128
  28. package/bin/cm-why.js +197 -0
  29. package/bin/mcp-server.js +8 -24
  30. package/ctxman.js +237 -183
  31. package/examples/README.md +19 -4
  32. package/examples/github-actions/ctxman-check.yml +49 -0
  33. package/examples/wizard-profiles/security-audit/profile.json +7 -1
  34. package/index.d.ts +1422 -0
  35. package/index.js +55 -31
  36. package/lib/agents/pr-failure-agent.js +48 -45
  37. package/lib/analyzers/ai-suggester.js +451 -0
  38. package/lib/analyzers/dependency-scanner.js +505 -0
  39. package/lib/analyzers/duplicate-detector.js +264 -0
  40. package/lib/analyzers/go-method-analyzer.js +151 -110
  41. package/lib/analyzers/import-tracker.js +431 -0
  42. package/lib/analyzers/method-analyzer.js +1055 -434
  43. package/lib/analyzers/performance-dashboard.js +687 -0
  44. package/lib/analyzers/token-calculator.js +895 -504
  45. package/lib/analyzers/unused-exports-detector.js +336 -0
  46. package/lib/api/mcp/install.js +424 -0
  47. package/lib/api/mcp/prompts.js +281 -281
  48. package/lib/api/mcp/resources.js +300 -241
  49. package/lib/api/mcp/sandbox.js +89 -0
  50. package/lib/api/mcp/server.js +772 -312
  51. package/lib/api/mcp/start.js +125 -0
  52. package/lib/api/mcp/transports/HttpSseTransport.js +60 -52
  53. package/lib/api/rest/server.js +105 -28
  54. package/lib/cache/CacheManager.js +9 -17
  55. package/lib/core/Analyzer.js +16 -10
  56. package/lib/core/ContextBuilder.js +19 -28
  57. package/lib/core/Reporter.js +15 -14
  58. package/lib/core/Scanner.js +81 -12
  59. package/lib/count/index.js +100 -0
  60. package/lib/formatters/format-registry.js +394 -372
  61. package/lib/formatters/gitingest-formatter.js +656 -621
  62. package/lib/formatters/toon-formatter-v1.3.js +637 -632
  63. package/lib/formatters/toon-formatter.js +239 -240
  64. package/lib/integrations/git/BlameTracker.js +4 -4
  65. package/lib/integrations/git/DiffAnalyzer.js +38 -31
  66. package/lib/integrations/git/GitClient.js +71 -53
  67. package/lib/lsp/server.js +311 -0
  68. package/lib/map/graph.js +299 -0
  69. package/lib/map/imports.js +233 -0
  70. package/lib/map/index.js +33 -0
  71. package/lib/map/rank.js +253 -0
  72. package/lib/map/render.js +346 -0
  73. package/lib/map/resolver.js +574 -0
  74. package/lib/pack/budget.js +111 -0
  75. package/lib/pack/code-lexer.js +360 -0
  76. package/lib/pack/compress.js +414 -0
  77. package/lib/pack/index.js +354 -0
  78. package/lib/pack/renderers/common.js +134 -0
  79. package/lib/pack/renderers/index.js +24 -0
  80. package/lib/pack/renderers/json.js +47 -0
  81. package/lib/pack/renderers/markdown.js +54 -0
  82. package/lib/pack/renderers/plain.js +38 -0
  83. package/lib/pack/renderers/xml.js +85 -0
  84. package/lib/pack/select.js +177 -0
  85. package/lib/pack/split.js +182 -0
  86. package/lib/pack/tree.js +35 -0
  87. package/lib/parsers/gitignore-parser.js +116 -107
  88. package/lib/parsers/method-filter-parser.js +50 -33
  89. package/lib/plugins/ExporterPlugin.js +3 -3
  90. package/lib/plugins/LanguagePlugin.js +3 -3
  91. package/lib/plugins/PluginManager.js +7 -13
  92. package/lib/policy/audit.js +312 -0
  93. package/lib/policy/enforce.js +125 -0
  94. package/lib/policy/ignore-files.js +169 -0
  95. package/lib/policy/index.js +53 -0
  96. package/lib/policy/policy.js +489 -0
  97. package/lib/pr/diff-parser.js +267 -0
  98. package/lib/pr/method-mapper.js +148 -0
  99. package/lib/pr/pr-context.js +829 -0
  100. package/lib/pr/pr-git.js +149 -0
  101. package/lib/pr/pr-renderer.js +644 -0
  102. package/lib/pr/related-tests.js +296 -0
  103. package/lib/rag/DataSourcePlugin.js +5 -5
  104. package/lib/rag/EmbeddingProvider.js +19 -8
  105. package/lib/rag/Indexer.js +14 -5
  106. package/lib/rag/VectorStoreFactory.js +3 -3
  107. package/lib/rag/ask.js +122 -0
  108. package/lib/rag/sources/FileDataSource.js +12 -9
  109. package/lib/rag/stores/BaseVectorStore.js +5 -5
  110. package/lib/rag/stores/ChromaDBStore.js +10 -10
  111. package/lib/rag/stores/LanceDBStore.js +29 -19
  112. package/lib/rag/stores/PineconeStore.js +11 -9
  113. package/lib/search/bm25.js +433 -0
  114. package/lib/search/code-units.js +201 -0
  115. package/lib/search/symbols.js +189 -0
  116. package/lib/ui/dashboard.js +120 -89
  117. package/lib/ui/index.js +1 -6
  118. package/lib/ui/init-wizard-ui.js +550 -0
  119. package/lib/ui/progress-bar.js +94 -88
  120. package/lib/ui/select-input.js +40 -32
  121. package/lib/ui/template-selector.js +99 -0
  122. package/lib/ui/wizard.js +374 -329
  123. package/lib/utils/claude-token-counter.js +151 -0
  124. package/lib/utils/cli-output.js +17 -0
  125. package/lib/utils/clipboard-utils.js +49 -50
  126. package/lib/utils/config-utils.js +81 -66
  127. package/lib/utils/context-versioning.js +532 -0
  128. package/lib/utils/error-handler.js +112 -112
  129. package/lib/utils/file-utils.js +153 -69
  130. package/lib/utils/format-converter.js +356 -283
  131. package/lib/utils/git-utils.js +366 -294
  132. package/lib/utils/llm-cost-estimator.js +281 -0
  133. package/lib/utils/llm-detector.js +242 -78
  134. package/lib/utils/logger.js +246 -237
  135. package/lib/utils/messages.js +355 -0
  136. package/lib/utils/multi-repo-manager.js +364 -0
  137. package/lib/utils/profile-manager.js +380 -0
  138. package/lib/utils/safe-read.js +61 -0
  139. package/lib/utils/secret-redactor.js +61 -0
  140. package/lib/utils/snapshot-manager.js +679 -0
  141. package/lib/utils/template-manager.js +378 -0
  142. package/lib/utils/token-utils.js +201 -170
  143. package/lib/utils/tokenizer-adapter.js +421 -377
  144. package/lib/utils/updater.js +539 -506
  145. package/lib/watch/FileWatcher.js +12 -8
  146. package/lib/watch/IncrementalAnalyzer.js +7 -7
  147. package/lib/why/coupling.js +81 -0
  148. package/lib/why/errors.js +23 -0
  149. package/lib/why/git.js +169 -0
  150. package/lib/why/history.js +108 -0
  151. package/lib/why/index.js +720 -0
  152. package/lib/why/ownership.js +79 -0
  153. package/lib/why/paths.js +207 -0
  154. package/lib/why/references.js +259 -0
  155. package/lib/why/render.js +722 -0
  156. package/lib/why/search.js +358 -0
  157. package/lib/why/target.js +389 -0
  158. package/lib/wizards/config-generator.js +257 -0
  159. package/lib/wizards/project-detector.js +572 -0
  160. package/package.json +44 -10
  161. package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
  162. package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
  163. package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
  164. package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
  165. package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
  166. package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
  167. package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
  168. package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
  169. package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
  170. package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
  171. package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
  172. package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
  173. package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
  174. package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
  175. package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
  176. package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
  177. package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
  178. package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
  179. package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
  180. package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
  181. package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
  182. package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
  183. package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
  184. package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
  185. package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
  186. package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
  187. package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
  188. package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
  189. package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
  190. package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
  191. package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
  192. package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
  193. package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
  194. package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
  195. package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
  196. package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
  197. package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
  198. package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
  199. package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
  200. package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
  201. package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
  202. package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
  203. package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
  204. package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
  205. package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
  206. package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
  207. package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
  208. package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
  209. package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
  210. package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
  211. package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
  212. package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
  213. package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
  214. package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
  215. package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
  216. package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
  217. package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
  218. package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
  219. package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
  220. package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
  221. package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
  222. package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
  223. package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
  224. package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
@@ -4,197 +4,228 @@
4
4
  */
5
5
 
6
6
  import path from 'path';
7
- import { getTokenizerManager } from './tokenizer-adapter.js';
7
+ import {
8
+ getTokenizerManager,
9
+ TiktokenAdapter,
10
+ encodingForModel,
11
+ isExactEncoding,
12
+ } from './tokenizer-adapter.js';
8
13
 
9
14
  // Load tiktoken for backward compatibility (dynamic import for optional dep)
10
15
  let tiktoken = null;
11
16
  try {
12
- const tiktokenModule = await import('tiktoken');
13
- tiktoken = tiktokenModule.default || tiktokenModule;
14
- } catch (err) {
15
- // tiktoken is optional
17
+ const tiktokenModule = await import('tiktoken');
18
+ tiktoken = tiktokenModule.default || tiktokenModule;
19
+ } catch (_err) {
20
+ // tiktoken is optional
16
21
  }
17
22
 
18
23
  // Tokenizer manager singleton
19
24
  let tokenizerManager = null;
20
25
 
21
- export default class TokenUtils {
22
- /**
23
- * Calculate tokens using model-specific tokenizer
24
- * @param {string} content - File content
25
- * @param {string} modelName - Model name (e.g., 'gpt-5', 'claude-sonnet-4.5', 'gemini-pro')
26
- * @returns {Promise<number>} Token count
27
- */
28
- static async calculateForModel(content, modelName) {
29
- if (!tokenizerManager) {
30
- tokenizerManager = await getTokenizerManager();
31
- }
32
-
33
- try {
34
- return tokenizerManager.countWithTelemetry(content, modelName);
35
- } catch (error) {
36
- // Fallback to estimation
37
- return this.estimate(content, 'default.txt');
38
- }
39
- }
26
+ // get_encoding() parses the whole BPE table (~150 ms), so build each encoder once
27
+ // and reuse it; encoding itself takes a few ms per file
28
+ const encoders = new Map();
40
29
 
41
- /**
42
- * Calculate exact or estimated tokens for content (backward compatible)
43
- * Uses tiktoken if available, otherwise estimation
44
- * @param {string} content - File content
45
- * @param {string} filePath - File path for extension-based estimation
46
- * @returns {number} Token count
47
- */
48
- static calculate(content, filePath) {
49
- if (tiktoken) {
50
- try {
51
- const encoding = tiktoken.get_encoding('cl100k_base');
52
- const tokens = encoding.encode(content);
53
- encoding.free();
54
- return tokens.length;
55
- } catch (error) {
56
- return this.estimate(content, filePath);
57
- }
58
- }
59
- return this.estimate(content, filePath);
60
- }
30
+ function getEncoder(encodingName) {
31
+ if (!encoders.has(encodingName)) {
32
+ encoders.set(encodingName, tiktoken.get_encoding(encodingName));
33
+ }
34
+ return encoders.get(encodingName);
35
+ }
61
36
 
62
- /**
63
- * Estimate tokens based on file type and content length
64
- * @param {string} content - File content
65
- * @param {string} filePath - File path for extension detection
66
- * @returns {number} Estimated token count
67
- */
68
- static estimate(content, filePath) {
69
- const ext = path.extname(filePath).toLowerCase();
70
-
71
- const charsPerToken = {
72
- '.json': 2.5,
73
- '.js': 3.2,
74
- '.ts': 3.2,
75
- '.jsx': 3.2,
76
- '.tsx': 3.2,
77
- '.py': 3.0,
78
- '.java': 3.5,
79
- '.go': 3.3,
80
- '.rs': 3.4,
81
- '.php': 3.1,
82
- '.rb': 3.0,
83
- '.swift': 3.3,
84
- '.kt': 3.5,
85
- '.cs': 3.5,
86
- '.cpp': 3.6,
87
- '.c': 3.6,
88
- '.h': 3.6,
89
- '.hpp': 3.6,
90
- '.scala': 3.5,
91
- '.md': 3.5,
92
- '.txt': 4.0,
93
- 'default': 3.5
94
- };
95
-
96
- const ratio = charsPerToken[ext] || charsPerToken['default'];
97
- return Math.round(content.length / ratio);
37
+ export default class TokenUtils {
38
+ /**
39
+ * Calculate tokens using model-specific tokenizer
40
+ * @param {string} content - File content
41
+ * @param {string} modelName - Model name (e.g., 'gpt-5', 'claude-sonnet-5-5', 'gemini-2.5-pro')
42
+ * @returns {Promise<number>} Token count
43
+ */
44
+ static async calculateForModel(content, modelName) {
45
+ if (!tokenizerManager) {
46
+ tokenizerManager = await getTokenizerManager();
98
47
  }
99
48
 
100
- /**
101
- * Format token count with K/M suffixes
102
- * @param {number} tokens - Token count
103
- * @returns {string} Formatted string
104
- */
105
- static format(tokens) {
106
- if (tokens >= 1000000) {
107
- return `${(tokens / 1000000).toFixed(1)}M`;
108
- } else if (tokens >= 1000) {
109
- return `${(tokens / 1000).toFixed(1)}K`;
110
- }
111
- return tokens.toString();
49
+ try {
50
+ return tokenizerManager.countWithTelemetry(content, modelName);
51
+ } catch (_error) {
52
+ // Fallback to estimation
53
+ return this.estimate(content, 'default.txt');
112
54
  }
113
-
114
- /**
115
- * Get token counting method description for a specific model
116
- * @param {string} modelName - Model name
117
- * @returns {Promise<string>} Description
118
- */
119
- static async getMethodForModel(modelName) {
120
- if (!tokenizerManager) {
121
- tokenizerManager = await getTokenizerManager();
122
- }
123
-
124
- try {
125
- const tokenizer = tokenizerManager.getTokenizerForModel(modelName);
126
- return `✅ Exact (using ${tokenizer.getName()})`;
127
- } catch (error) {
128
- return '⚠️ Estimated (~95% accurate)';
129
- }
55
+ }
56
+
57
+ /**
58
+ * Calculate exact or estimated tokens for content (backward compatible)
59
+ * Uses tiktoken if available, otherwise estimation
60
+ * @param {string} content - File content
61
+ * @param {string} filePath - File path for extension-based estimation
62
+ * @param {string} [encodingName='cl100k_base'] - tiktoken encoding (see encodingFor)
63
+ * @returns {number} Token count
64
+ */
65
+ static calculate(content, filePath, encodingName = 'cl100k_base') {
66
+ if (tiktoken) {
67
+ try {
68
+ return getEncoder(encodingName).encode(content).length;
69
+ } catch (_error) {
70
+ return this.estimate(content, filePath);
71
+ }
130
72
  }
131
-
132
- /**
133
- * Get token counting method description (backward compatible)
134
- * @returns {string} Description
135
- */
136
- static getMethod() {
137
- return tiktoken ? '✅ Exact (using tiktoken)' : '⚠️ Estimated (~95% accurate)';
73
+ return this.estimate(content, filePath);
74
+ }
75
+
76
+ /**
77
+ * tiktoken encoding to count with for a target model: o200k_base for OpenAI models
78
+ * since GPT-4o, cl100k_base for everything else (exact for GPT-4/3.5, an
79
+ * approximation for other vendors' tokenizers)
80
+ * @param {string} [modelName] - Target model
81
+ * @returns {'o200k_base'|'cl100k_base'}
82
+ */
83
+ static encodingFor(modelName) {
84
+ return modelName ? encodingForModel(modelName) : 'cl100k_base';
85
+ }
86
+
87
+ /**
88
+ * Estimate tokens based on file type and content length
89
+ * @param {string} content - File content
90
+ * @param {string} filePath - File path for extension detection
91
+ * @returns {number} Estimated token count
92
+ */
93
+ static estimate(content, filePath) {
94
+ const ext = path.extname(filePath).toLowerCase();
95
+
96
+ const charsPerToken = {
97
+ '.json': 2.5,
98
+ '.js': 3.2,
99
+ '.ts': 3.2,
100
+ '.jsx': 3.2,
101
+ '.tsx': 3.2,
102
+ '.py': 3.0,
103
+ '.java': 3.5,
104
+ '.go': 3.3,
105
+ '.rs': 3.4,
106
+ '.php': 3.1,
107
+ '.rb': 3.0,
108
+ '.swift': 3.3,
109
+ '.kt': 3.5,
110
+ '.cs': 3.5,
111
+ '.cpp': 3.6,
112
+ '.c': 3.6,
113
+ '.h': 3.6,
114
+ '.hpp': 3.6,
115
+ '.scala': 3.5,
116
+ '.md': 3.5,
117
+ '.txt': 4.0,
118
+ default: 3.5,
119
+ };
120
+
121
+ const ratio = charsPerToken[ext] || charsPerToken['default'];
122
+ return Math.round(content.length / ratio);
123
+ }
124
+
125
+ /**
126
+ * Format token count with K/M suffixes
127
+ * @param {number} tokens - Token count
128
+ * @returns {string} Formatted string
129
+ */
130
+ static format(tokens) {
131
+ if (tokens >= 1000000) {
132
+ return `${(tokens / 1000000).toFixed(1)}M`;
133
+ } else if (tokens >= 1000) {
134
+ return `${(tokens / 1000).toFixed(1)}K`;
138
135
  }
139
-
140
- /**
141
- * Check if exact token counting is available
142
- * @returns {boolean} True if tiktoken is available
143
- */
144
- static isExact() {
145
- return tiktoken !== null;
136
+ return tokens.toString();
137
+ }
138
+
139
+ /**
140
+ * Get token counting method description for a specific model
141
+ * @param {string} modelName - Model name
142
+ * @returns {Promise<string>} Description
143
+ */
144
+ static async getMethodForModel(modelName) {
145
+ if (!tokenizerManager) {
146
+ tokenizerManager = await getTokenizerManager();
146
147
  }
147
148
 
148
- /**
149
- * Alias for isExact() for backward compatibility
150
- * @returns {boolean} True if tiktoken is available
151
- */
152
- static hasExactCounting() {
153
- return this.isExact();
149
+ try {
150
+ const tokenizer = tokenizerManager.getTokenizerForModel(modelName);
151
+ if (tokenizer instanceof TiktokenAdapter) {
152
+ const label = isExactEncoding(modelName) ? '✅ Exact' : '≈ Approximate';
153
+ return `${label} (using ${tokenizer.getName()}, ${encodingForModel(modelName)})`;
154
+ }
155
+ return tokenizer.exact
156
+ ? `✅ Exact (using ${tokenizer.getName()})`
157
+ : `≈ Approximate (using ${tokenizer.getName()})`;
158
+ } catch (_error) {
159
+ return '⚠️ Estimated (~95% accurate)';
154
160
  }
155
-
156
- /**
157
- * Get list of available tokenizers
158
- * @returns {Promise<Array>} List of available tokenizers
159
- */
160
- static async getAvailableTokenizers() {
161
- if (!tokenizerManager) {
162
- tokenizerManager = await getTokenizerManager();
163
- }
164
- return tokenizerManager.getAvailableTokenizers();
161
+ }
162
+
163
+ /**
164
+ * Get token counting method description (backward compatible)
165
+ * @returns {string} Description
166
+ */
167
+ static getMethod() {
168
+ return tiktoken ? '✅ Exact (using tiktoken)' : '⚠️ Estimated (~95% accurate)';
169
+ }
170
+
171
+ /**
172
+ * Check if exact token counting is available
173
+ * @returns {boolean} True if tiktoken is available
174
+ */
175
+ static isExact() {
176
+ return tiktoken !== null;
177
+ }
178
+
179
+ /**
180
+ * Alias for isExact() for backward compatibility
181
+ * @returns {boolean} True if tiktoken is available
182
+ */
183
+ static hasExactCounting() {
184
+ return this.isExact();
185
+ }
186
+
187
+ /**
188
+ * Get list of available tokenizers
189
+ * @returns {Promise<Array>} List of available tokenizers
190
+ */
191
+ static async getAvailableTokenizers() {
192
+ if (!tokenizerManager) {
193
+ tokenizerManager = await getTokenizerManager();
165
194
  }
166
-
167
- /**
168
- * Detect which tokenizer would be used for a model
169
- * @param {string} modelName - Model name
170
- * @returns {Promise<string>} Tokenizer name
171
- */
172
- static async detectTokenizer(modelName) {
173
- if (!tokenizerManager) {
174
- tokenizerManager = await getTokenizerManager();
175
- }
176
- const tokenizer = tokenizerManager.getTokenizerForModel(modelName);
177
- return tokenizer.getName();
195
+ return tokenizerManager.getAvailableTokenizers();
196
+ }
197
+
198
+ /**
199
+ * Detect which tokenizer would be used for a model
200
+ * @param {string} modelName - Model name
201
+ * @returns {Promise<string>} Tokenizer name
202
+ */
203
+ static async detectTokenizer(modelName) {
204
+ if (!tokenizerManager) {
205
+ tokenizerManager = await getTokenizerManager();
178
206
  }
179
-
180
- /**
181
- * Get telemetry statistics
182
- * @returns {Promise<Object>} Telemetry data
183
- */
184
- static async getTelemetry() {
185
- if (!tokenizerManager) {
186
- tokenizerManager = await getTokenizerManager();
187
- }
188
- return tokenizerManager.getTelemetry();
207
+ const tokenizer = tokenizerManager.getTokenizerForModel(modelName);
208
+ return tokenizer.getName();
209
+ }
210
+
211
+ /**
212
+ * Get telemetry statistics
213
+ * @returns {Promise<Object>} Telemetry data
214
+ */
215
+ static async getTelemetry() {
216
+ if (!tokenizerManager) {
217
+ tokenizerManager = await getTokenizerManager();
189
218
  }
190
-
191
- /**
192
- * Reset telemetry statistics
193
- */
194
- static async resetTelemetry() {
195
- if (!tokenizerManager) {
196
- tokenizerManager = await getTokenizerManager();
197
- }
198
- tokenizerManager.resetTelemetry();
219
+ return tokenizerManager.getTelemetry();
220
+ }
221
+
222
+ /**
223
+ * Reset telemetry statistics
224
+ */
225
+ static async resetTelemetry() {
226
+ if (!tokenizerManager) {
227
+ tokenizerManager = await getTokenizerManager();
199
228
  }
229
+ tokenizerManager.resetTelemetry();
230
+ }
200
231
  }