@gotza02/sequential-thinking 2026.2.30 → 2026.2.32

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.
@@ -1,4 +1,19 @@
1
1
  #!/usr/bin/env node
2
+ /**
3
+ * Sequential Thinking HTTP Server
4
+ *
5
+ * REST API wrapper for MCP Sequential Thinking components.
6
+ *
7
+ * ARCHITECTURE NOTES:
8
+ * - This server uses in-memory managers (NotesManager, CodeDatabase)
9
+ * - Each process maintains its own state - NOT suitable for multi-process deployments
10
+ * - For clustering (PM2 cluster mode, Kubernetes), use a shared database (Redis, PostgreSQL)
11
+ *
12
+ * CONCURRENCY WARNING:
13
+ * - Single process mode: SAFE
14
+ * - Multi-process mode: DATA WILL BE OUT OF SYNC between processes
15
+ * - Solution: Use external storage with proper locking for multi-process setups
16
+ */
2
17
  import express from 'express';
3
18
  import cors from 'cors';
4
19
  import { SequentialThinkingServer } from './lib.js';
@@ -10,16 +25,112 @@ const app = express();
10
25
  const PORT = process.env.PORT || 3000;
11
26
  app.use(cors());
12
27
  app.use(express.json());
13
- // Initialize MCP components
14
- const thinkingServer = new SequentialThinkingServer(process.env.THOUGHTS_STORAGE_PATH || 'thoughts_history.json', parseInt(process.env.THOUGHT_DELAY_MS || '0', 10));
15
- const knowledgeGraph = new ProjectKnowledgeGraph();
16
- const notesManager = new NotesManager(process.env.NOTES_STORAGE_PATH || 'project_notes.json');
17
- const codeDb = new CodeDatabase(process.env.CODE_DB_PATH || 'code_database.json');
18
- // Health check
28
+ /**
29
+ * ============================================================================
30
+ * SINGLETON MANAGERS
31
+ * ============================================================================
32
+ * Each server instance gets its own managers.
33
+ * For multi-process deployments, consider using Redis/PostgreSQL as backing store.
34
+ */
35
+ class ServerManagers {
36
+ static instance = null;
37
+ thinking;
38
+ graph;
39
+ notes;
40
+ codeDb;
41
+ constructor() {
42
+ this.thinking = new SequentialThinkingServer(process.env.THOUGHTS_STORAGE_PATH || 'thoughts_history.json', parseInt(process.env.THOUGHT_DELAY_MS || '0', 10));
43
+ this.graph = new ProjectKnowledgeGraph();
44
+ this.notes = new NotesManager(process.env.NOTES_STORAGE_PATH || 'project_notes.json');
45
+ this.codeDb = new CodeDatabase(process.env.CODE_DB_PATH || 'code_database.json');
46
+ }
47
+ static getInstance() {
48
+ if (!ServerManagers.instance) {
49
+ ServerManagers.instance = new ServerManagers();
50
+ }
51
+ return ServerManagers.instance;
52
+ }
53
+ /**
54
+ * Reload all managers from disk (useful for external file changes)
55
+ */
56
+ async reloadAll() {
57
+ await this.notes.reload();
58
+ await this.codeDb.reload();
59
+ }
60
+ }
61
+ // Get singleton instance
62
+ const managers = ServerManagers.getInstance();
63
+ /**
64
+ * ============================================================================
65
+ * HEALTH & METADATA ENDPOINTS
66
+ * ============================================================================
67
+ */
19
68
  app.get('/health', (req, res) => {
20
- res.json({ status: 'ok', service: 'sequential-thinking-http-wrapper' });
69
+ res.json({
70
+ status: 'ok',
71
+ service: 'sequential-thinking-http-wrapper',
72
+ version: '2.0',
73
+ timestamp: new Date().toISOString()
74
+ });
21
75
  });
22
- // Sequential Thinking endpoints
76
+ app.get('/', (req, res) => {
77
+ res.json({
78
+ name: 'Sequential Thinking HTTP API',
79
+ version: '2.0',
80
+ endpoints: {
81
+ health: 'GET /health',
82
+ thinking: {
83
+ create: 'POST /api/thinking/thought',
84
+ list: 'GET /api/thinking/history',
85
+ clear: 'DELETE /api/thinking/history',
86
+ search: 'GET /api/thinking/search?q=query'
87
+ },
88
+ graph: {
89
+ build: 'POST /api/graph/build',
90
+ summary: 'GET /api/graph/summary',
91
+ visualization: 'GET /api/graph/visualization',
92
+ relationships: 'GET /api/graph/relationships?file=path'
93
+ },
94
+ notes: {
95
+ create: 'POST /api/notes',
96
+ list: 'GET /api/notes',
97
+ get: 'GET /api/notes/:id',
98
+ update: 'PUT /api/notes/:id',
99
+ delete: 'DELETE /api/notes/:id'
100
+ },
101
+ codeDb: {
102
+ create: 'POST /api/codedb',
103
+ list: 'GET /api/codedb',
104
+ get: 'GET /api/codedb/:id',
105
+ update: 'PUT /api/codedb/:id',
106
+ delete: 'DELETE /api/codedb/:id',
107
+ search: 'GET /api/codedb/search?q=query',
108
+ stats: 'GET /api/codedb/stats'
109
+ }
110
+ }
111
+ });
112
+ });
113
+ /**
114
+ * ============================================================================
115
+ * SEQUENTIAL THINKING ENDPOINTS
116
+ * ============================================================================
117
+ */
118
+ /**
119
+ * POST /api/thinking/thought
120
+ * Process a single thought through the sequential thinking engine
121
+ *
122
+ * Request Body:
123
+ * {
124
+ * "thought": string | "text": string, // Text is for TestSprite compatibility
125
+ * "thoughtNumber": number,
126
+ * "totalThoughts": number,
127
+ * "nextThoughtNeeded": boolean,
128
+ * "thoughtType": "analysis" | "planning" | "execution" | "observation" | "hypothesis" | "reflexion" | "solution",
129
+ * "blockId"?: string,
130
+ * "relatedToolCall"?: string,
131
+ * "toolResult"?: string
132
+ * }
133
+ */
23
134
  app.post('/api/thinking/thought', async (req, res) => {
24
135
  try {
25
136
  // Support both TestSprite format (text) and native format (thought)
@@ -30,37 +141,48 @@ app.post('/api/thinking/thought', async (req, res) => {
30
141
  totalThoughts: req.body.totalThoughts || 1,
31
142
  nextThoughtNeeded: req.body.nextThoughtNeeded !== undefined ? req.body.nextThoughtNeeded : true,
32
143
  thoughtType: req.body.thoughtType || req.body.type || 'analysis',
144
+ blockId: req.body.blockId,
145
+ relatedToolCall: req.body.relatedToolCall,
146
+ toolResult: req.body.toolResult,
33
147
  isRevision: req.body.isRevision,
34
148
  revisesThought: req.body.revisesThought,
35
149
  branchFromThought: req.body.branchFromThought,
36
150
  branchId: req.body.branchId
37
151
  };
38
- const result = await thinkingServer.processThought(payload);
39
- // Transform response to match TestSprite expectations
152
+ const result = await managers.thinking.processThought(payload);
153
+ // Parse response
40
154
  const responseData = result.content[0]?.text ?
41
155
  JSON.parse(result.content[0].text) : {};
42
- // Generate a unique ID for the thought
43
156
  const thoughtId = `${Date.now()}-${responseData.thoughtNumber || 1}`;
157
+ // Return response compatible with both native and TestSprite formats
44
158
  res.json({
45
159
  id: thoughtId,
46
160
  thought: responseData.thoughtNumber ? `Thought ${responseData.thoughtNumber}` : thoughtText,
47
- text: thoughtText, // Add text field for TestSprite compatibility
161
+ text: thoughtText,
48
162
  type: payload.thoughtType,
49
163
  thoughtNumber: responseData.thoughtNumber,
50
164
  totalThoughts: responseData.totalThoughts,
51
165
  nextThoughtNeeded: responseData.nextThoughtNeeded,
52
166
  visualization: responseData.visualization,
53
- feedback: responseData.feedback
167
+ feedback: responseData.feedback,
168
+ currentBlock: responseData.currentBlock,
169
+ blockContext: responseData.blockContext
54
170
  });
55
171
  }
56
172
  catch (error) {
57
- res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
173
+ res.status(500).json({
174
+ error: error instanceof Error ? error.message : String(error),
175
+ status: 'error'
176
+ });
58
177
  }
59
178
  });
179
+ /**
180
+ * GET /api/thinking/history
181
+ * Get all thoughts from history
182
+ */
60
183
  app.get('/api/thinking/history', async (req, res) => {
61
184
  try {
62
- const history = await thinkingServer.searchHistory('');
63
- // Transform to include id and text fields for TestSprite compatibility
185
+ const history = await managers.thinking.searchHistory('');
64
186
  const transformedHistory = history.map((t, index) => ({
65
187
  id: `thought-${index}-${t.thoughtNumber}`,
66
188
  text: t.thought,
@@ -69,42 +191,89 @@ app.get('/api/thinking/history', async (req, res) => {
69
191
  thoughtNumber: t.thoughtNumber,
70
192
  totalThoughts: t.totalThoughts,
71
193
  nextThoughtNeeded: t.nextThoughtNeeded,
194
+ blockId: t.blockId,
195
+ relatedToolCall: t.relatedToolCall,
72
196
  isRevision: t.isRevision,
73
197
  revisesThought: t.revisesThought,
74
198
  branchFromThought: t.branchFromThought,
75
- branchId: t.branchId,
76
- blockId: t.blockId
199
+ branchId: t.branchId
77
200
  }));
78
201
  res.json(transformedHistory);
79
202
  }
80
203
  catch (error) {
81
- res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
204
+ res.status(500).json({
205
+ error: error instanceof Error ? error.message : String(error)
206
+ });
207
+ }
208
+ });
209
+ /**
210
+ * GET /api/thinking/search
211
+ * Search thoughts by query
212
+ */
213
+ app.get('/api/thinking/search', async (req, res) => {
214
+ try {
215
+ const query = req.query.q || '';
216
+ const results = await managers.thinking.searchHistory(query);
217
+ const transformedResults = results.map((t, index) => ({
218
+ id: `thought-${index}-${t.thoughtNumber}`,
219
+ text: t.thought,
220
+ thought: t.thought,
221
+ type: t.thoughtType,
222
+ thoughtNumber: t.thoughtNumber,
223
+ totalThoughts: t.totalThoughts,
224
+ blockId: t.blockId
225
+ }));
226
+ res.json(transformedResults);
227
+ }
228
+ catch (error) {
229
+ res.status(500).json({
230
+ error: error instanceof Error ? error.message : String(error)
231
+ });
82
232
  }
83
233
  });
234
+ /**
235
+ * DELETE /api/thinking/history
236
+ * Clear all thought history
237
+ */
84
238
  app.delete('/api/thinking/history', async (req, res) => {
85
239
  try {
86
- await thinkingServer.clearHistory();
240
+ await managers.thinking.clearHistory();
87
241
  res.json({ success: true });
88
242
  }
89
243
  catch (error) {
90
- res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
244
+ res.status(500).json({
245
+ error: error instanceof Error ? error.message : String(error)
246
+ });
91
247
  }
92
248
  });
93
- // Knowledge Graph endpoints
249
+ /**
250
+ * ============================================================================
251
+ * KNOWLEDGE GRAPH ENDPOINTS
252
+ * ============================================================================
253
+ */
254
+ /**
255
+ * POST /api/graph/build
256
+ * Build or rebuild the project knowledge graph
257
+ *
258
+ * Request Body:
259
+ * {
260
+ * "path"?: string, // Project path (default: current directory)
261
+ * "directory"?: string // Alias for path
262
+ * }
263
+ */
94
264
  app.post('/api/graph/build', async (req, res) => {
95
265
  try {
96
- // Support both 'path' and 'directory' parameter names
97
266
  const pathParam = req.body.path || req.body.directory || '.';
98
- console.log(`[DEBUG] Build Graph request for path: ${pathParam}`);
267
+ console.log(`[Graph] Building for path: ${pathParam}`);
99
268
  const safePath = validatePath(pathParam);
100
- console.log(`[DEBUG] Safe Path: ${safePath}`);
101
- const result = await knowledgeGraph.build(safePath);
102
- // Return status field for TestSprite compatibility
269
+ const result = await managers.graph.build(safePath);
103
270
  res.json({
104
271
  status: 'success',
105
272
  success: true,
106
273
  nodeCount: result.nodeCount,
107
- totalFiles: result.totalFiles
274
+ totalFiles: result.totalFiles,
275
+ cachedFiles: result.cachedFiles,
276
+ parsedFiles: result.parsedFiles
108
277
  });
109
278
  }
110
279
  catch (error) {
@@ -117,35 +286,78 @@ app.post('/api/graph/build', async (req, res) => {
117
286
  });
118
287
  }
119
288
  });
289
+ /**
290
+ * POST /api/graph/rebuild
291
+ * Force rebuild the graph (clears cache first)
292
+ */
293
+ app.post('/api/graph/rebuild', async (req, res) => {
294
+ try {
295
+ const pathParam = req.body.path || req.body.directory || '.';
296
+ const safePath = validatePath(pathParam);
297
+ const result = await managers.graph.forceRebuild(safePath);
298
+ res.json({
299
+ status: 'success',
300
+ success: true,
301
+ nodeCount: result.nodeCount,
302
+ totalFiles: result.totalFiles,
303
+ cachedFiles: 0,
304
+ parsedFiles: result.totalFiles
305
+ });
306
+ }
307
+ catch (error) {
308
+ const message = error instanceof Error ? error.message : String(error);
309
+ const status = message.includes('Access denied') ? 403 : 500;
310
+ res.status(status).json({
311
+ status: 'error',
312
+ error: message
313
+ });
314
+ }
315
+ });
316
+ /**
317
+ * GET /api/graph/summary
318
+ * Get graph summary statistics
319
+ */
120
320
  app.get('/api/graph/summary', async (req, res) => {
121
321
  try {
122
- const summary = knowledgeGraph.getSummary();
322
+ const summary = managers.graph.getSummary();
123
323
  res.json(summary);
124
324
  }
125
325
  catch (error) {
126
- res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
326
+ res.status(500).json({
327
+ error: error instanceof Error ? error.message : String(error)
328
+ });
127
329
  }
128
330
  });
331
+ /**
332
+ * GET /api/graph/visualization
333
+ * Get Mermaid diagram of the graph
334
+ */
129
335
  app.get('/api/graph/visualization', async (req, res) => {
130
336
  try {
131
- const viz = knowledgeGraph.toMermaid();
132
- // Return as text/plain for TestSprite compatibility
337
+ const viz = managers.graph.toMermaid();
133
338
  res.type('text/plain').send(viz);
134
339
  }
135
340
  catch (error) {
136
- res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
341
+ res.status(500).json({
342
+ error: error instanceof Error ? error.message : String(error)
343
+ });
137
344
  }
138
345
  });
346
+ /**
347
+ * GET /api/graph/relationships
348
+ * Get relationships for a specific file
349
+ *
350
+ * Query Parameters:
351
+ * - file: File path (relative or absolute)
352
+ */
139
353
  app.get('/api/graph/relationships', async (req, res) => {
140
354
  try {
141
355
  const filePath = req.query.file;
142
356
  if (!filePath) {
143
- // If no file specified, return empty list for TestSprite compatibility
144
357
  return res.json([]);
145
358
  }
146
- // Ensure path is safe
147
359
  validatePath(filePath);
148
- const relationships = knowledgeGraph.getRelationships(filePath);
360
+ const relationships = managers.graph.getRelationships(filePath);
149
361
  res.json(relationships || null);
150
362
  }
151
363
  catch (error) {
@@ -154,13 +366,45 @@ app.get('/api/graph/relationships', async (req, res) => {
154
366
  res.status(status).json({ error: message });
155
367
  }
156
368
  });
157
- // Notes endpoints
369
+ /**
370
+ * GET /api/graph/deep-context
371
+ * Get deep context for a specific file
372
+ */
373
+ app.get('/api/graph/deep-context', async (req, res) => {
374
+ try {
375
+ const filePath = req.query.file;
376
+ if (!filePath) {
377
+ return res.status(400).json({ error: 'File parameter is required' });
378
+ }
379
+ validatePath(filePath);
380
+ const context = managers.graph.getDeepContext(filePath);
381
+ res.json(context || null);
382
+ }
383
+ catch (error) {
384
+ const message = error instanceof Error ? error.message : String(error);
385
+ const status = message.includes('Access denied') ? 403 : 500;
386
+ res.status(status).json({ error: message });
387
+ }
388
+ });
389
+ /**
390
+ * ============================================================================
391
+ * NOTES ENDPOINTS
392
+ * ============================================================================
393
+ */
394
+ /**
395
+ * POST /api/notes
396
+ * Create a new note
397
+ */
158
398
  app.post('/api/notes', async (req, res) => {
159
399
  try {
160
400
  const { title, content, tags, priority, expiresAt, expiration_date } = req.body;
161
- const note = await notesManager.addNote(title, content, tags || [], priority, expiration_date || expiresAt);
162
- // Return 200 for compatibility with some test runners
163
- res.status(200).json({
401
+ if (!title || !content) {
402
+ return res.status(400).json({
403
+ error: 'Title and content are required'
404
+ });
405
+ }
406
+ const note = await managers.notes.addNote(title, content, tags || [], priority, expiration_date || expiresAt);
407
+ res.status(201).json({
164
408
  id: note.id,
165
409
  _id: note.id,
166
410
  title: note.title,
@@ -168,19 +412,30 @@ app.post('/api/notes', async (req, res) => {
168
412
  tags: note.tags,
169
413
  priority: note.priority,
170
414
  expiration_date: note.expiresAt,
171
- expiresAt: note.expiresAt
415
+ expiresAt: note.expiresAt,
416
+ createdAt: note.createdAt,
417
+ updatedAt: note.updatedAt
172
418
  });
173
419
  }
174
420
  catch (error) {
175
- res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
421
+ res.status(500).json({
422
+ error: error instanceof Error ? error.message : String(error)
423
+ });
176
424
  }
177
425
  });
426
+ /**
427
+ * GET /api/notes
428
+ * List all notes with optional filtering
429
+ *
430
+ * Query Parameters:
431
+ * - tag: Filter by tag
432
+ * - includeExpired: Include expired notes (default: false)
433
+ */
178
434
  app.get('/api/notes', async (req, res) => {
179
435
  try {
180
436
  const tag = req.query.tag;
181
437
  const includeExpired = req.query.includeExpired === 'true';
182
- const notes = await notesManager.listNotes(tag, includeExpired);
183
- // Transform notes to include both id formats
438
+ const notes = await managers.notes.listNotes(tag, includeExpired);
184
439
  const transformedNotes = notes.map(note => ({
185
440
  id: note.id,
186
441
  _id: note.id,
@@ -189,22 +444,53 @@ app.get('/api/notes', async (req, res) => {
189
444
  tags: note.tags,
190
445
  priority: note.priority,
191
446
  expiration_date: note.expiresAt,
447
+ expiresAt: note.expiresAt,
448
+ createdAt: note.createdAt,
449
+ updatedAt: note.updatedAt
450
+ }));
451
+ res.json(transformedNotes);
452
+ }
453
+ catch (error) {
454
+ res.status(500).json({
455
+ error: error instanceof Error ? error.message : String(error)
456
+ });
457
+ }
458
+ });
459
+ /**
460
+ * GET /api/notes/search
461
+ * Search notes by query
462
+ */
463
+ app.get('/api/notes/search', async (req, res) => {
464
+ try {
465
+ const query = req.query.q || '';
466
+ const results = await managers.notes.searchNotes(query);
467
+ const transformedNotes = results.map(note => ({
468
+ id: note.id,
469
+ _id: note.id,
470
+ title: note.title,
471
+ content: note.content,
472
+ tags: note.tags,
473
+ priority: note.priority,
192
474
  expiresAt: note.expiresAt
193
475
  }));
194
476
  res.json(transformedNotes);
195
477
  }
196
478
  catch (error) {
197
- res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
479
+ res.status(500).json({
480
+ error: error instanceof Error ? error.message : String(error)
481
+ });
198
482
  }
199
483
  });
484
+ /**
485
+ * GET /api/notes/:id
486
+ * Get a specific note by ID
487
+ */
200
488
  app.get('/api/notes/:id', async (req, res) => {
201
489
  try {
202
- const notes = await notesManager.listNotes();
203
- const note = notes.find(n => n.id === req.params.id);
490
+ const note = await managers.notes.getNote(req.params.id);
204
491
  if (!note) {
205
492
  return res.status(404).json({ error: 'Note not found' });
206
493
  }
207
- // Transform note to include both id formats
208
494
  res.json({
209
495
  id: note.id,
210
496
  _id: note.id,
@@ -213,29 +499,93 @@ app.get('/api/notes/:id', async (req, res) => {
213
499
  tags: note.tags,
214
500
  priority: note.priority,
215
501
  expiration_date: note.expiresAt,
216
- expiresAt: note.expiresAt
502
+ expiresAt: note.expiresAt,
503
+ createdAt: note.createdAt,
504
+ updatedAt: note.updatedAt
217
505
  });
218
506
  }
219
507
  catch (error) {
220
- res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
508
+ res.status(500).json({
509
+ error: error instanceof Error ? error.message : String(error)
510
+ });
221
511
  }
222
512
  });
513
+ /**
514
+ * PUT /api/notes/:id
515
+ * Update a note
516
+ */
517
+ app.put('/api/notes/:id', async (req, res) => {
518
+ try {
519
+ const { title, content, tags } = req.body;
520
+ const note = await managers.notes.updateNote(req.params.id, {
521
+ title,
522
+ content,
523
+ tags
524
+ });
525
+ if (!note) {
526
+ return res.status(404).json({ error: 'Note not found' });
527
+ }
528
+ res.json({
529
+ id: note.id,
530
+ _id: note.id,
531
+ title: note.title,
532
+ content: note.content,
533
+ tags: note.tags,
534
+ priority: note.priority,
535
+ expiresAt: note.expiresAt,
536
+ updatedAt: note.updatedAt
537
+ });
538
+ }
539
+ catch (error) {
540
+ res.status(500).json({
541
+ error: error instanceof Error ? error.message : String(error)
542
+ });
543
+ }
544
+ });
545
+ /**
546
+ * DELETE /api/notes/:id
547
+ * Delete a note
548
+ */
223
549
  app.delete('/api/notes/:id', async (req, res) => {
224
550
  try {
225
- await notesManager.deleteNote(req.params.id);
226
- res.status(200).json({ success: true });
551
+ const deleted = await managers.notes.deleteNote(req.params.id);
552
+ if (!deleted) {
553
+ return res.status(404).json({ error: 'Note not found' });
554
+ }
555
+ res.json({ success: true });
227
556
  }
228
557
  catch (error) {
229
- res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
558
+ res.status(500).json({
559
+ error: error instanceof Error ? error.message : String(error)
560
+ });
230
561
  }
231
562
  });
232
- // Code Database endpoints
563
+ /**
564
+ * ============================================================================
565
+ * CODE DATABASE ENDPOINTS
566
+ * ============================================================================
567
+ */
568
+ /**
569
+ * POST /api/codedb
570
+ * Create a new code snippet
571
+ */
233
572
  app.post('/api/codedb', async (req, res) => {
234
573
  try {
235
- const { title, code, language, description, tags } = req.body;
236
- const snippet = await codeDb.addSnippet({ title, code, language, description, tags: tags || [] });
237
- // Return 200 for compatibility
238
- res.status(200).json({
574
+ const { title, code, language, description, tags, context } = req.body;
575
+ if (!title || !code || !language) {
576
+ return res.status(400).json({
577
+ error: 'Title, code, and language are required'
578
+ });
579
+ }
580
+ const snippet = await managers.codeDb.addSnippet({
581
+ title,
582
+ code,
583
+ language,
584
+ description: description || '',
585
+ tags: tags || [],
586
+ context
587
+ });
588
+ res.status(201).json({
239
589
  id: snippet.id,
240
590
  _id: snippet.id,
241
591
  title: snippet.title,
@@ -243,19 +593,128 @@ app.post('/api/codedb', async (req, res) => {
243
593
  language: snippet.language,
244
594
  description: snippet.description,
245
595
  tags: snippet.tags,
596
+ context: snippet.context,
246
597
  updatedAt: snippet.updatedAt
247
598
  });
248
599
  }
249
600
  catch (error) {
250
- res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
601
+ res.status(500).json({
602
+ error: error instanceof Error ? error.message : String(error)
603
+ });
251
604
  }
252
605
  });
606
+ /**
607
+ * GET /api/codedb
608
+ * List all code snippets (legacy endpoint - uses search)
609
+ *
610
+ * Query Parameters:
611
+ * - q: Search query
612
+ * - title: Search by title (alias for q)
613
+ * - code: Search by code (alias for q)
614
+ * - tag: Search by tag (alias for q)
615
+ */
253
616
  app.get('/api/codedb', async (req, res) => {
254
617
  try {
255
- const query = req.query.q || req.query.title || req.query.code || req.query.tag || '';
256
- const results = await codeDb.searchSnippets(query);
257
- // Transform snippets to include both id formats
258
- const transformedSnippets = results.map(snippet => ({
618
+ const query = req.query.q ||
619
+ req.query.title ||
620
+ req.query.code ||
621
+ req.query.tag || '';
622
+ const searchResults = await managers.codeDb.searchSnippets(query, {
623
+ fuzzy: true,
624
+ threshold: 0,
625
+ limit: 100,
626
+ includeCode: true
627
+ });
628
+ const transformedSnippets = searchResults.map(r => ({
629
+ id: r.snippet.id,
630
+ _id: r.snippet.id,
631
+ title: r.snippet.title,
632
+ code: r.snippet.code,
633
+ language: r.snippet.language,
634
+ description: r.snippet.description,
635
+ tags: r.snippet.tags,
636
+ context: r.snippet.context,
637
+ updatedAt: r.snippet.updatedAt,
638
+ relevance: r.relevance
639
+ }));
640
+ res.json(transformedSnippets);
641
+ }
642
+ catch (error) {
643
+ res.status(500).json({
644
+ error: error instanceof Error ? error.message : String(error)
645
+ });
646
+ }
647
+ });
648
+ /**
649
+ * GET /api/codedb/search
650
+ * Search code snippets with advanced options
651
+ *
652
+ * Query Parameters:
653
+ * - q: Search query
654
+ * - fuzzy: Enable fuzzy matching (default: true)
655
+ * - threshold: Minimum relevance score 0-1 (default: 0.1)
656
+ * - limit: Max results (default: 50)
657
+ * - includeCode: Include code in search (default: true)
658
+ */
659
+ app.get('/api/codedb/search', async (req, res) => {
660
+ try {
661
+ const query = req.query.q || '';
662
+ if (!query.trim()) {
663
+ return res.json([]);
664
+ }
665
+ const options = {
666
+ fuzzy: req.query.fuzzy !== 'false',
667
+ threshold: parseFloat(req.query.threshold) || 0.1,
668
+ limit: parseInt(req.query.limit) || 50,
669
+ includeCode: req.query.includeCode !== 'false'
670
+ };
671
+ const results = await managers.codeDb.searchSnippets(query, options);
672
+ const transformedResults = results.map(r => ({
673
+ id: r.snippet.id,
674
+ _id: r.snippet.id,
675
+ title: r.snippet.title,
676
+ code: r.snippet.code,
677
+ language: r.snippet.language,
678
+ description: r.snippet.description,
679
+ tags: r.snippet.tags,
680
+ context: r.snippet.context,
681
+ updatedAt: r.snippet.updatedAt,
682
+ relevance: r.relevance
683
+ }));
684
+ res.json(transformedResults);
685
+ }
686
+ catch (error) {
687
+ res.status(500).json({
688
+ error: error instanceof Error ? error.message : String(error)
689
+ });
690
+ }
691
+ });
692
+ /**
693
+ * GET /api/codedb/stats
694
+ * Get code database statistics
695
+ */
696
+ app.get('/api/codedb/stats', async (req, res) => {
697
+ try {
698
+ const stats = await managers.codeDb.getStats();
699
+ res.json(stats);
700
+ }
701
+ catch (error) {
702
+ res.status(500).json({
703
+ error: error instanceof Error ? error.message : String(error)
704
+ });
705
+ }
706
+ });
707
+ /**
708
+ * GET /api/codedb/:id
709
+ * Get a specific code snippet by ID
710
+ */
711
+ app.get('/api/codedb/:id', async (req, res) => {
712
+ try {
713
+ const snippet = await managers.codeDb.getSnippet(req.params.id);
714
+ if (!snippet) {
715
+ return res.status(404).json({ error: 'Code snippet not found' });
716
+ }
717
+ res.json({
259
718
  id: snippet.id,
260
719
  _id: snippet.id,
261
720
  title: snippet.title,
@@ -263,25 +722,228 @@ app.get('/api/codedb', async (req, res) => {
263
722
  language: snippet.language,
264
723
  description: snippet.description,
265
724
  tags: snippet.tags,
725
+ context: snippet.context,
266
726
  updatedAt: snippet.updatedAt
267
- }));
268
- res.json(transformedSnippets);
727
+ });
269
728
  }
270
729
  catch (error) {
271
- res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
730
+ res.status(500).json({
731
+ error: error instanceof Error ? error.message : String(error)
732
+ });
272
733
  }
273
734
  });
274
- // DELETE endpoint for codedb (for TestSprite compatibility - no actual delete in CodeDatabase)
735
+ /**
736
+ * PUT /api/codedb/:id
737
+ * Update a code snippet
738
+ */
739
+ app.put('/api/codedb/:id', async (req, res) => {
740
+ try {
741
+ const { title, code, description, tags, context } = req.body;
742
+ const snippet = await managers.codeDb.updateSnippet(req.params.id, {
743
+ title,
744
+ code,
745
+ description,
746
+ tags,
747
+ context
748
+ });
749
+ if (!snippet) {
750
+ return res.status(404).json({ error: 'Code snippet not found' });
751
+ }
752
+ res.json({
753
+ id: snippet.id,
754
+ _id: snippet.id,
755
+ title: snippet.title,
756
+ code: snippet.code,
757
+ language: snippet.language,
758
+ description: snippet.description,
759
+ tags: snippet.tags,
760
+ context: snippet.context,
761
+ updatedAt: snippet.updatedAt
762
+ });
763
+ }
764
+ catch (error) {
765
+ res.status(500).json({
766
+ error: error instanceof Error ? error.message : String(error)
767
+ });
768
+ }
769
+ });
770
+ /**
771
+ * DELETE /api/codedb/:id
772
+ * Delete a code snippet (FIXED - now actually deletes!)
773
+ */
275
774
  app.delete('/api/codedb/:id', async (req, res) => {
276
775
  try {
277
- // CodeDatabase doesn't have delete method, just return success for testing
278
- res.status(200).json({ success: true });
776
+ const deleted = await managers.codeDb.deleteSnippet(req.params.id);
777
+ if (!deleted) {
778
+ return res.status(404).json({ error: 'Code snippet not found' });
779
+ }
780
+ res.json({ success: true, deleted: true });
279
781
  }
280
782
  catch (error) {
281
- res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
783
+ res.status(500).json({
784
+ error: error instanceof Error ? error.message : String(error)
785
+ });
786
+ }
787
+ });
788
+ /**
789
+ * ============================================================================
790
+ * PATTERNS ENDPOINTS
791
+ * ============================================================================
792
+ */
793
+ /**
794
+ * POST /api/patterns
795
+ * Learn a new architectural pattern
796
+ */
797
+ app.post('/api/patterns', async (req, res) => {
798
+ try {
799
+ const { name, description } = req.body;
800
+ if (!name || !description) {
801
+ return res.status(400).json({
802
+ error: 'Name and description are required'
803
+ });
804
+ }
805
+ await managers.codeDb.learnPattern(name, description);
806
+ res.status(201).json({ success: true, name, description });
807
+ }
808
+ catch (error) {
809
+ res.status(500).json({
810
+ error: error instanceof Error ? error.message : String(error)
811
+ });
812
+ }
813
+ });
814
+ /**
815
+ * GET /api/patterns
816
+ * List all patterns
817
+ */
818
+ app.get('/api/patterns', async (req, res) => {
819
+ try {
820
+ const patterns = await managers.codeDb.listAllPatterns();
821
+ res.json(patterns);
822
+ }
823
+ catch (error) {
824
+ res.status(500).json({
825
+ error: error instanceof Error ? error.message : String(error)
826
+ });
827
+ }
828
+ });
829
+ /**
830
+ * GET /api/patterns/search
831
+ * Search patterns
832
+ */
833
+ app.get('/api/patterns/search', async (req, res) => {
834
+ try {
835
+ const query = req.query.q || '';
836
+ const limit = parseInt(req.query.limit) || 20;
837
+ const results = await managers.codeDb.searchPatterns(query, limit);
838
+ res.json(results);
839
+ }
840
+ catch (error) {
841
+ res.status(500).json({
842
+ error: error instanceof Error ? error.message : String(error)
843
+ });
844
+ }
845
+ });
846
+ /**
847
+ * GET /api/patterns/:name
848
+ * Get a specific pattern
849
+ */
850
+ app.get('/api/patterns/:name', async (req, res) => {
851
+ try {
852
+ // Decode URL-encoded name
853
+ const name = decodeURIComponent(req.params.name);
854
+ const pattern = await managers.codeDb.getPattern(name);
855
+ if (!pattern) {
856
+ return res.status(404).json({ error: 'Pattern not found' });
857
+ }
858
+ res.json({ name, description: pattern });
859
+ }
860
+ catch (error) {
861
+ res.status(500).json({
862
+ error: error instanceof Error ? error.message : String(error)
863
+ });
864
+ }
865
+ });
866
+ /**
867
+ * DELETE /api/patterns/:name
868
+ * Delete a pattern
869
+ */
870
+ app.delete('/api/patterns/:name', async (req, res) => {
871
+ try {
872
+ const name = decodeURIComponent(req.params.name);
873
+ const deleted = await managers.codeDb.deletePattern(name);
874
+ if (!deleted) {
875
+ return res.status(404).json({ error: 'Pattern not found' });
876
+ }
877
+ res.json({ success: true });
878
+ }
879
+ catch (error) {
880
+ res.status(500).json({
881
+ error: error instanceof Error ? error.message : String(error)
882
+ });
883
+ }
884
+ });
885
+ /**
886
+ * ============================================================================
887
+ * ADMIN ENDPOINTS
888
+ * ============================================================================
889
+ */
890
+ /**
891
+ * POST /api/admin/reload
892
+ * Reload all managers from disk
893
+ */
894
+ app.post('/api/admin/reload', async (req, res) => {
895
+ try {
896
+ await managers.reloadAll();
897
+ res.json({
898
+ success: true,
899
+ message: 'All managers reloaded from disk'
900
+ });
901
+ }
902
+ catch (error) {
903
+ res.status(500).json({
904
+ error: error instanceof Error ? error.message : String(error)
905
+ });
906
+ }
907
+ });
908
+ /**
909
+ * GET /api/admin/stats
910
+ * Get overall system statistics
911
+ */
912
+ app.get('/api/admin/stats', async (req, res) => {
913
+ try {
914
+ const codeStats = await managers.codeDb.getStats();
915
+ const notesList = await managers.notes.listNotes();
916
+ const thoughts = await managers.thinking.searchHistory('');
917
+ res.json({
918
+ codeDatabase: {
919
+ snippetCount: codeStats.snippetCount,
920
+ patternCount: codeStats.patternCount,
921
+ languages: codeStats.languages
922
+ },
923
+ notes: {
924
+ count: notesList.length
925
+ },
926
+ thoughts: {
927
+ count: thoughts.length
928
+ },
929
+ uptime: process.uptime(),
930
+ memory: process.memoryUsage(),
931
+ timestamp: new Date().toISOString()
932
+ });
933
+ }
934
+ catch (error) {
935
+ res.status(500).json({
936
+ error: error instanceof Error ? error.message : String(error)
937
+ });
282
938
  }
283
939
  });
940
+ // Start server
284
941
  app.listen(PORT, () => {
285
942
  console.log(`Sequential Thinking HTTP Server running on port ${PORT}`);
286
943
  console.log(`Health check: http://localhost:${PORT}/health`);
944
+ console.log(`API docs: http://localhost:${PORT}/`);
945
+ console.log('');
946
+ console.log('CONCURRENCY WARNING:');
947
+ console.log('This server uses in-memory storage. Do NOT run in cluster mode');
948
+ console.log('without external storage (Redis/PostgreSQL) for data persistence.');
287
949
  });