@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.
- package/README.md +1 -0
- package/SYSTEM_INSTRUCTION.md +8 -1
- package/dist/codestore.d.ts +102 -2
- package/dist/codestore.js +499 -17
- package/dist/graph.d.ts +10 -1
- package/dist/graph.js +471 -93
- package/dist/http-server.d.ts +15 -0
- package/dist/http-server.js +732 -70
- package/dist/lib.js +84 -28
- package/dist/notes.d.ts +39 -2
- package/dist/notes.js +233 -24
- package/dist/system_test.js +1 -1
- package/dist/tools/codestore.js +1 -1
- package/dist/tools/filesystem.js +359 -40
- package/dist/tools/thinking.js +7 -1
- package/package.json +1 -1
package/dist/http-server.js
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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({
|
|
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
|
-
|
|
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
|
|
39
|
-
//
|
|
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,
|
|
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({
|
|
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
|
|
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({
|
|
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
|
|
240
|
+
await managers.thinking.clearHistory();
|
|
87
241
|
res.json({ success: true });
|
|
88
242
|
}
|
|
89
243
|
catch (error) {
|
|
90
|
-
res.status(500).json({
|
|
244
|
+
res.status(500).json({
|
|
245
|
+
error: error instanceof Error ? error.message : String(error)
|
|
246
|
+
});
|
|
91
247
|
}
|
|
92
248
|
});
|
|
93
|
-
|
|
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(`[
|
|
267
|
+
console.log(`[Graph] Building for path: ${pathParam}`);
|
|
99
268
|
const safePath = validatePath(pathParam);
|
|
100
|
-
|
|
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 =
|
|
322
|
+
const summary = managers.graph.getSummary();
|
|
123
323
|
res.json(summary);
|
|
124
324
|
}
|
|
125
325
|
catch (error) {
|
|
126
|
-
res.status(500).json({
|
|
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 =
|
|
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({
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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({
|
|
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
|
|
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({
|
|
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
|
|
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({
|
|
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
|
|
226
|
-
|
|
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({
|
|
558
|
+
res.status(500).json({
|
|
559
|
+
error: error instanceof Error ? error.message : String(error)
|
|
560
|
+
});
|
|
230
561
|
}
|
|
231
562
|
});
|
|
232
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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({
|
|
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 ||
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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({
|
|
730
|
+
res.status(500).json({
|
|
731
|
+
error: error instanceof Error ? error.message : String(error)
|
|
732
|
+
});
|
|
272
733
|
}
|
|
273
734
|
});
|
|
274
|
-
|
|
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
|
-
|
|
278
|
-
|
|
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({
|
|
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
|
});
|