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,405 @@
1
+ /**
2
+ * REST API Server
3
+ * v3.0.0 - API infrastructure
4
+ *
5
+ * Responsibilities:
6
+ * - HTTP server for Ctxman API
7
+ * - RESTful endpoints
8
+ * - WebSocket support for watch mode
9
+ * - Authentication (optional)
10
+ */
11
+
12
+ import http from 'http';
13
+ import { URL } from 'url';
14
+ import { getLogger } from '../../utils/logger.js';
15
+ import Scanner from '../../core/Scanner.js';
16
+ import Analyzer from '../../core/Analyzer.js';
17
+ import ContextBuilder from '../../core/ContextBuilder.js';
18
+ import Reporter from '../../core/Reporter.js';
19
+ import GitClient from '../../integrations/git/GitClient.js';
20
+ import DiffAnalyzer from '../../integrations/git/DiffAnalyzer.js';
21
+
22
+ const logger = getLogger('APIServer');
23
+
24
+ export class APIServer {
25
+ constructor(options = {}) {
26
+ this.options = {
27
+ port: 3000,
28
+ host: 'localhost',
29
+ authToken: null,
30
+ cors: true,
31
+ ...options
32
+ };
33
+
34
+ this.server = null;
35
+ this.isRunning = false;
36
+ }
37
+
38
+ /**
39
+ * Start API server
40
+ */
41
+ start() {
42
+ if (this.isRunning) {
43
+ logger.warn('Server already running');
44
+ return;
45
+ }
46
+
47
+ this.server = http.createServer((req, res) => {
48
+ this.handleRequest(req, res);
49
+ });
50
+
51
+ this.server.listen(this.options.port, this.options.host, () => {
52
+ this.isRunning = true;
53
+ logger.info(`API Server started: http://${this.options.host}:${this.options.port}`);
54
+ console.log(`\n🌐 Ctxman API Server`);
55
+ console.log(` Listening on: http://${this.options.host}:${this.options.port}`);
56
+ console.log(` API Version: v1`);
57
+ console.log(` Documentation: http://${this.options.host}:${this.options.port}/api/v1/docs`);
58
+ console.log();
59
+ });
60
+
61
+ this.server.on('error', (error) => {
62
+ logger.error(`Server error: ${error.message}`);
63
+ if (error.code === 'EADDRINUSE') {
64
+ console.error(`❌ Port ${this.options.port} is already in use`);
65
+ console.error(` Try a different port: ctxman serve --port 3001`);
66
+ }
67
+ });
68
+ }
69
+
70
+ /**
71
+ * Stop API server
72
+ */
73
+ stop() {
74
+ if (!this.isRunning) {
75
+ return;
76
+ }
77
+
78
+ this.server.close(() => {
79
+ this.isRunning = false;
80
+ logger.info('API Server stopped');
81
+ console.log('\n✅ Server stopped');
82
+ });
83
+ }
84
+
85
+ /**
86
+ * Handle HTTP request
87
+ * @param {http.IncomingMessage} req
88
+ * @param {http.ServerResponse} res
89
+ */
90
+ async handleRequest(req, res) {
91
+ // CORS headers
92
+ if (this.options.cors) {
93
+ res.setHeader('Access-Control-Allow-Origin', '*');
94
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
95
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
96
+
97
+ if (req.method === 'OPTIONS') {
98
+ res.writeHead(200);
99
+ res.end();
100
+ return;
101
+ }
102
+ }
103
+
104
+ // Authentication
105
+ if (this.options.authToken && !this.checkAuth(req)) {
106
+ this.sendError(res, 401, 'Unauthorized');
107
+ return;
108
+ }
109
+
110
+ // Parse URL
111
+ const url = new URL(req.url, `http://${req.headers.host}`);
112
+ const pathname = url.pathname;
113
+
114
+ logger.debug(`${req.method} ${pathname}`);
115
+
116
+ try {
117
+ // Route to handlers
118
+ if (pathname.startsWith('/api/v1')) {
119
+ await this.handleAPIv1(req, res, pathname, url);
120
+ } else {
121
+ this.sendError(res, 404, 'Not Found');
122
+ }
123
+ } catch (error) {
124
+ logger.error(`Request error: ${error.message}`);
125
+ this.sendError(res, 500, error.message);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Handle API v1 routes
131
+ * @param {http.IncomingMessage} req
132
+ * @param {http.ServerResponse} res
133
+ * @param {string} pathname
134
+ * @param {URL} url
135
+ */
136
+ async handleAPIv1(req, res, pathname, url) {
137
+ // GET /api/v1/analyze
138
+ if (pathname === '/api/v1/analyze' && req.method === 'GET') {
139
+ await this.handleAnalyze(req, res, url);
140
+ return;
141
+ }
142
+
143
+ // GET /api/v1/methods
144
+ if (pathname === '/api/v1/methods' && req.method === 'GET') {
145
+ await this.handleMethods(req, res, url);
146
+ return;
147
+ }
148
+
149
+ // GET /api/v1/stats
150
+ if (pathname === '/api/v1/stats' && req.method === 'GET') {
151
+ await this.handleStats(req, res, url);
152
+ return;
153
+ }
154
+
155
+ // GET /api/v1/diff
156
+ if (pathname === '/api/v1/diff' && req.method === 'GET') {
157
+ await this.handleDiff(req, res, url);
158
+ return;
159
+ }
160
+
161
+ // POST /api/v1/context
162
+ if (pathname === '/api/v1/context' && req.method === 'POST') {
163
+ await this.handleContext(req, res);
164
+ return;
165
+ }
166
+
167
+ // GET /api/v1/docs
168
+ if (pathname === '/api/v1/docs' && req.method === 'GET') {
169
+ this.handleDocs(req, res);
170
+ return;
171
+ }
172
+
173
+ this.sendError(res, 404, 'Endpoint not found');
174
+ }
175
+
176
+ /**
177
+ * Handle /api/v1/analyze endpoint
178
+ */
179
+ async handleAnalyze(req, res, url) {
180
+ const params = Object.fromEntries(url.searchParams);
181
+ const projectPath = params.path || process.cwd();
182
+
183
+ const scanner = new Scanner(projectPath);
184
+ const files = scanner.scan();
185
+
186
+ const analyzer = new Analyzer({ methodLevel: params.methods === 'true' });
187
+ const result = await analyzer.analyze(files);
188
+
189
+ this.sendJSON(res, result);
190
+ }
191
+
192
+ /**
193
+ * Handle /api/v1/methods endpoint
194
+ */
195
+ async handleMethods(req, res, url) {
196
+ const params = Object.fromEntries(url.searchParams);
197
+ const filePath = params.file;
198
+
199
+ if (!filePath) {
200
+ this.sendError(res, 400, 'Missing "file" parameter');
201
+ return;
202
+ }
203
+
204
+ // Analyze single file
205
+ const analyzer = new Analyzer({ methodLevel: true });
206
+ const fileInfo = {
207
+ path: filePath,
208
+ relativePath: filePath,
209
+ name: path.basename(filePath),
210
+ extension: path.extname(filePath)
211
+ };
212
+
213
+ const analysis = await analyzer.analyzeFile(fileInfo);
214
+
215
+ this.sendJSON(res, {
216
+ file: filePath,
217
+ methods: analysis.methods || [],
218
+ totalMethods: analysis.methodCount || 0
219
+ });
220
+ }
221
+
222
+ /**
223
+ * Handle /api/v1/stats endpoint
224
+ */
225
+ async handleStats(req, res, url) {
226
+ const params = Object.fromEntries(url.searchParams);
227
+ const projectPath = params.path || process.cwd();
228
+
229
+ const scanner = new Scanner(projectPath);
230
+ const files = scanner.scan();
231
+
232
+ const analyzer = new Analyzer();
233
+ const result = await analyzer.analyze(files);
234
+
235
+ this.sendJSON(res, result.stats);
236
+ }
237
+
238
+ /**
239
+ * Handle /api/v1/diff endpoint
240
+ */
241
+ async handleDiff(req, res, url) {
242
+ const params = Object.fromEntries(url.searchParams);
243
+ const projectPath = params.path || process.cwd();
244
+ const since = params.since || null;
245
+
246
+ const diffAnalyzer = new DiffAnalyzer(projectPath);
247
+ const analysis = diffAnalyzer.analyzeChanges(since);
248
+
249
+ this.sendJSON(res, analysis);
250
+ }
251
+
252
+ /**
253
+ * Handle /api/v1/context endpoint (POST)
254
+ */
255
+ async handleContext(req, res) {
256
+ const body = await this.parseBody(req);
257
+
258
+ const projectPath = body.path || process.cwd();
259
+ const options = {
260
+ methodLevel: body.methodLevel || false,
261
+ targetModel: body.targetModel || null,
262
+ targetTokens: body.targetTokens || null,
263
+ useCase: body.useCase || 'custom'
264
+ };
265
+
266
+ // Build context
267
+ const scanner = new Scanner(projectPath);
268
+ const files = scanner.scan();
269
+
270
+ const analyzer = new Analyzer(options);
271
+ const result = await analyzer.analyze(files);
272
+
273
+ const builder = new ContextBuilder(options);
274
+ const context = builder.build(result);
275
+
276
+ this.sendJSON(res, context);
277
+ }
278
+
279
+ /**
280
+ * Handle /api/v1/docs endpoint
281
+ */
282
+ handleDocs(req, res) {
283
+ const docs = {
284
+ version: 'v1',
285
+ endpoints: [
286
+ {
287
+ path: '/api/v1/analyze',
288
+ method: 'GET',
289
+ description: 'Analyze project files and get token counts',
290
+ parameters: {
291
+ path: 'Project path (optional, defaults to cwd)',
292
+ methods: 'Include method-level analysis (true/false)'
293
+ }
294
+ },
295
+ {
296
+ path: '/api/v1/methods',
297
+ method: 'GET',
298
+ description: 'Extract methods from a file',
299
+ parameters: {
300
+ file: 'File path (required)'
301
+ }
302
+ },
303
+ {
304
+ path: '/api/v1/stats',
305
+ method: 'GET',
306
+ description: 'Get project statistics',
307
+ parameters: {
308
+ path: 'Project path (optional)'
309
+ }
310
+ },
311
+ {
312
+ path: '/api/v1/diff',
313
+ method: 'GET',
314
+ description: 'Analyze git diff',
315
+ parameters: {
316
+ path: 'Project path (optional)',
317
+ since: 'Git reference (optional, e.g., "main", "HEAD~5")'
318
+ }
319
+ },
320
+ {
321
+ path: '/api/v1/context',
322
+ method: 'POST',
323
+ description: 'Generate optimized LLM context',
324
+ body: {
325
+ path: 'Project path',
326
+ methodLevel: 'Include methods (boolean)',
327
+ targetModel: 'Target LLM model',
328
+ targetTokens: 'Target token budget',
329
+ useCase: 'Use case template'
330
+ }
331
+ }
332
+ ]
333
+ };
334
+
335
+ this.sendJSON(res, docs);
336
+ }
337
+
338
+ /**
339
+ * Check authentication
340
+ * @param {http.IncomingMessage} req
341
+ * @returns {boolean}
342
+ */
343
+ checkAuth(req) {
344
+ const authHeader = req.headers.authorization;
345
+
346
+ if (!authHeader) {
347
+ return false;
348
+ }
349
+
350
+ const token = authHeader.replace('Bearer ', '');
351
+ return token === this.options.authToken;
352
+ }
353
+
354
+ /**
355
+ * Parse request body
356
+ * @param {http.IncomingMessage} req
357
+ * @returns {Promise<object>}
358
+ */
359
+ parseBody(req) {
360
+ return new Promise((resolve, reject) => {
361
+ let body = '';
362
+
363
+ req.on('data', chunk => {
364
+ body += chunk.toString();
365
+ });
366
+
367
+ req.on('end', () => {
368
+ try {
369
+ const parsed = JSON.parse(body);
370
+ resolve(parsed);
371
+ } catch (error) {
372
+ reject(new Error('Invalid JSON'));
373
+ }
374
+ });
375
+
376
+ req.on('error', reject);
377
+ });
378
+ }
379
+
380
+ /**
381
+ * Send JSON response
382
+ * @param {http.ServerResponse} res
383
+ * @param {object} data
384
+ * @param {number} statusCode
385
+ */
386
+ sendJSON(res, data, statusCode = 200) {
387
+ res.writeHead(statusCode, { 'Content-Type': 'application/json' });
388
+ res.end(JSON.stringify(data, null, 2));
389
+ }
390
+
391
+ /**
392
+ * Send error response
393
+ * @param {http.ServerResponse} res
394
+ * @param {number} statusCode
395
+ * @param {string} message
396
+ */
397
+ sendError(res, statusCode, message) {
398
+ this.sendJSON(res, {
399
+ error: message,
400
+ statusCode: statusCode
401
+ }, statusCode);
402
+ }
403
+ }
404
+
405
+ export default APIServer;