@gotza02/sequential-thinking 2026.2.19 → 2026.2.21

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 (51) hide show
  1. package/README.md +1 -1
  2. package/dist/codestore.d.ts +28 -0
  3. package/dist/graph.d.ts +60 -0
  4. package/dist/graph.js +19 -0
  5. package/dist/http-server.d.ts +2 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +2 -0
  8. package/dist/lib.d.ts +67 -0
  9. package/dist/lib.js +323 -83
  10. package/dist/notes.d.ts +25 -0
  11. package/dist/system_test.d.ts +1 -0
  12. package/dist/test_ts_req.d.ts +1 -0
  13. package/dist/tools/codestore.d.ts +3 -0
  14. package/dist/tools/coding.d.ts +3 -0
  15. package/dist/tools/coding.js +40 -3
  16. package/dist/tools/filesystem.d.ts +2 -0
  17. package/dist/tools/graph.d.ts +3 -0
  18. package/dist/tools/graph.js +18 -0
  19. package/dist/tools/human.d.ts +65 -0
  20. package/dist/tools/human.js +305 -0
  21. package/dist/tools/notes.d.ts +3 -0
  22. package/dist/tools/thinking.d.ts +3 -0
  23. package/dist/tools/thinking.js +137 -65
  24. package/dist/tools/web.d.ts +2 -0
  25. package/dist/utils.d.ts +32 -0
  26. package/package.json +3 -1
  27. package/dist/chaos.test.js +0 -72
  28. package/dist/codestore.test.js +0 -59
  29. package/dist/coding.test.js +0 -130
  30. package/dist/e2e.test.js +0 -122
  31. package/dist/filesystem.test.js +0 -189
  32. package/dist/graph.test.js +0 -150
  33. package/dist/graph_repro.test.js +0 -63
  34. package/dist/integration.test.js +0 -58
  35. package/dist/notes.test.js +0 -74
  36. package/dist/registration.test.js +0 -39
  37. package/dist/repro_dollar.js +0 -30
  38. package/dist/repro_dollar_simple.js +0 -22
  39. package/dist/repro_history.js +0 -41
  40. package/dist/repro_path.js +0 -17
  41. package/dist/repro_search.test.js +0 -79
  42. package/dist/repro_ts_req.js +0 -3
  43. package/dist/server.test.js +0 -127
  44. package/dist/stress.test.js +0 -68
  45. package/dist/utils.test.js +0 -40
  46. package/dist/verify_cache.test.js +0 -27
  47. package/dist/verify_edit.test.js +0 -66
  48. package/dist/verify_notes.test.js +0 -36
  49. package/dist/verify_viz.test.js +0 -25
  50. package/dist/web_fallback.test.js +0 -103
  51. package/dist/web_read.test.js +0 -60
package/dist/lib.js CHANGED
@@ -5,12 +5,12 @@ import * as path from 'path';
5
5
  import { AsyncMutex } from './utils.js';
6
6
  export class SequentialThinkingServer {
7
7
  thoughtHistory = [];
8
+ blocks = [];
9
+ currentBlockId = null;
8
10
  branches = {};
9
11
  disableThoughtLogging;
10
12
  storagePath;
11
13
  delayMs;
12
- isSaving = false;
13
- hasPendingSave = false;
14
14
  saveMutex = new AsyncMutex();
15
15
  constructor(storagePath = 'thoughts_history.json', delayMs = 0) {
16
16
  this.disableThoughtLogging = (process.env.DISABLE_THOUGHT_LOGGING || "").toLowerCase() === "true";
@@ -23,39 +23,38 @@ export class SequentialThinkingServer {
23
23
  if (existsSync(this.storagePath)) {
24
24
  const data = readFileSync(this.storagePath, 'utf-8');
25
25
  if (!data.trim())
26
- return; // Empty file is fine
26
+ return;
27
27
  try {
28
- const history = JSON.parse(data);
29
- if (Array.isArray(history)) {
28
+ const parsed = JSON.parse(data);
29
+ // New format with version
30
+ if (parsed.version && parsed.blocks) {
31
+ this.blocks = parsed.blocks;
32
+ this.thoughtHistory = parsed.thoughtHistory || [];
33
+ if (this.blocks.length > 0) {
34
+ const activeBlock = this.blocks.find(b => b.status === 'active');
35
+ this.currentBlockId = activeBlock?.id || this.blocks[this.blocks.length - 1].id;
36
+ }
37
+ this.rebuildBranches();
38
+ return;
39
+ }
40
+ // Legacy format (array of thoughts)
41
+ if (Array.isArray(parsed)) {
30
42
  this.thoughtHistory = [];
31
43
  this.branches = {};
32
- history.forEach(thought => {
44
+ parsed.forEach(thought => {
33
45
  if (thought && typeof thought === 'object' && 'thought' in thought) {
46
+ // Migrate to new format
47
+ thought.thoughtType = thought.thoughtType || 'analysis';
48
+ thought.blockId = thought.blockId || 'legacy-default';
34
49
  this.addToMemory(thought);
35
50
  }
36
51
  });
52
+ this.rebuildBlocks();
37
53
  }
38
54
  }
39
55
  catch (parseError) {
40
56
  console.error(`Error parsing history from ${this.storagePath}, attempting recovery:`, parseError);
41
- // Basic Recovery: Try to fix truncated JSON by finding the last complete object
42
- // This is a simple heuristic: find the last '}' that closes a thought object
43
- const lastBrace = data.lastIndexOf('}');
44
- if (lastBrace !== -1) {
45
- try {
46
- const recoveredData = data.substring(0, lastBrace + 1).trim();
47
- // If it's part of an array, we might need to add ']'
48
- const attemptedJson = recoveredData.endsWith(']') ? recoveredData : recoveredData + ']';
49
- const history = JSON.parse(attemptedJson);
50
- if (Array.isArray(history)) {
51
- history.forEach(thought => this.addToMemory(thought));
52
- console.log(`Successfully recovered ${history.length} thoughts.`);
53
- }
54
- }
55
- catch (recoveryError) {
56
- console.error('Recovery failed, starting with empty history.');
57
- }
58
- }
57
+ this.attemptRecovery(data);
59
58
  }
60
59
  }
61
60
  }
@@ -63,12 +62,72 @@ export class SequentialThinkingServer {
63
62
  console.error(`Error loading history from ${this.storagePath}:`, error);
64
63
  }
65
64
  }
65
+ attemptRecovery(data) {
66
+ const lastBrace = data.lastIndexOf('}');
67
+ if (lastBrace !== -1) {
68
+ try {
69
+ const recoveredData = data.substring(0, lastBrace + 1).trim();
70
+ const attemptedJson = recoveredData.endsWith(']') ? recoveredData : recoveredData + ']';
71
+ const history = JSON.parse(attemptedJson);
72
+ if (Array.isArray(history)) {
73
+ history.forEach(thought => this.addToMemory(thought));
74
+ this.rebuildBlocks();
75
+ console.log(`Successfully recovered ${history.length} thoughts.`);
76
+ }
77
+ }
78
+ catch (recoveryError) {
79
+ console.error('Recovery failed, starting with empty history.');
80
+ }
81
+ }
82
+ }
83
+ rebuildBlocks() {
84
+ const blockMap = new Map();
85
+ for (const t of this.thoughtHistory) {
86
+ const bid = t.blockId || 'default';
87
+ if (!blockMap.has(bid)) {
88
+ const newBlock = {
89
+ id: bid,
90
+ topic: t.thought.substring(0, 50),
91
+ status: 'active',
92
+ thoughts: [],
93
+ createdAt: new Date().toISOString(),
94
+ updatedAt: new Date().toISOString()
95
+ };
96
+ blockMap.set(bid, newBlock);
97
+ this.blocks.push(newBlock);
98
+ }
99
+ blockMap.get(bid).thoughts.push(t);
100
+ }
101
+ if (this.blocks.length > 0) {
102
+ this.currentBlockId = this.blocks[this.blocks.length - 1].id;
103
+ }
104
+ }
105
+ rebuildBranches() {
106
+ this.branches = {};
107
+ for (const t of this.thoughtHistory) {
108
+ if (t.branchFromThought && t.branchId) {
109
+ const branchKey = `${t.branchFromThought}-${t.branchId}`;
110
+ if (!this.branches[branchKey]) {
111
+ this.branches[branchKey] = [];
112
+ }
113
+ this.branches[branchKey].push(t);
114
+ }
115
+ }
116
+ }
66
117
  async saveHistory() {
67
- // Use mutex to ensure only one file operation happens at a time
68
118
  await this.saveMutex.dispatch(async () => {
69
119
  try {
120
+ // Auto-Pruning: Keep max 100 thoughts, summarize older blocks
121
+ if (this.thoughtHistory.length > 100) {
122
+ this.autoPrune();
123
+ }
124
+ const storage = {
125
+ version: '2.0',
126
+ blocks: this.blocks,
127
+ thoughtHistory: this.thoughtHistory
128
+ };
70
129
  const tmpPath = `${this.storagePath}.tmp`;
71
- await fs.writeFile(tmpPath, JSON.stringify(this.thoughtHistory, null, 2), 'utf-8');
130
+ await fs.writeFile(tmpPath, JSON.stringify(storage, null, 2), 'utf-8');
72
131
  await fs.rename(tmpPath, this.storagePath);
73
132
  }
74
133
  catch (error) {
@@ -76,9 +135,24 @@ export class SequentialThinkingServer {
76
135
  }
77
136
  });
78
137
  }
138
+ autoPrune() {
139
+ // Keep last 2 blocks full, mark others as completed
140
+ if (this.blocks.length > 2) {
141
+ for (let i = 0; i < this.blocks.length - 2; i++) {
142
+ this.blocks[i].status = 'completed';
143
+ }
144
+ }
145
+ // Keep only last 100 thoughts
146
+ if (this.thoughtHistory.length > 100) {
147
+ const removed = this.thoughtHistory.splice(0, this.thoughtHistory.length - 100);
148
+ console.error(`[AutoPrune] Removed ${removed.length} old thoughts to maintain performance.`);
149
+ }
150
+ }
79
151
  async clearHistory() {
80
152
  this.thoughtHistory = [];
81
153
  this.branches = {};
154
+ this.blocks = [];
155
+ this.currentBlockId = null;
82
156
  await this.saveHistory();
83
157
  }
84
158
  async archiveHistory(startIndex, endIndex, summary) {
@@ -90,29 +164,25 @@ export class SequentialThinkingServer {
90
164
  thoughtNumber: startIndex,
91
165
  totalThoughts: this.thoughtHistory[this.thoughtHistory.length - 1].totalThoughts - (endIndex - startIndex),
92
166
  nextThoughtNeeded: true,
93
- thoughtType: 'analysis'
167
+ thoughtType: 'analysis',
168
+ blockId: this.currentBlockId || undefined
94
169
  };
95
- // Remove the range and insert summary
96
170
  const removedCount = endIndex - startIndex + 1;
97
171
  this.thoughtHistory.splice(startIndex - 1, removedCount, summaryThought);
98
- // Update all thoughts to reflect the new total and renumber subsequent ones
99
172
  const newTotal = this.thoughtHistory.length;
100
173
  const shiftAmount = removedCount - 1;
101
174
  for (let i = 0; i < this.thoughtHistory.length; i++) {
102
175
  const t = this.thoughtHistory[i];
103
- // 1. Update total thoughts for everyone
104
176
  t.totalThoughts = newTotal;
105
- // 2. Renumber thoughts that came after the summarized range
106
177
  if (i >= startIndex) {
107
178
  t.thoughtNumber -= shiftAmount;
108
179
  }
109
- // 3. Update references for all thoughts
110
180
  if (t.branchFromThought) {
111
181
  if (t.branchFromThought > endIndex) {
112
182
  t.branchFromThought -= shiftAmount;
113
183
  }
114
184
  else if (t.branchFromThought >= startIndex) {
115
- t.branchFromThought = startIndex; // Point to summary
185
+ t.branchFromThought = startIndex;
116
186
  }
117
187
  }
118
188
  if (t.revisesThought) {
@@ -120,20 +190,12 @@ export class SequentialThinkingServer {
120
190
  t.revisesThought -= shiftAmount;
121
191
  }
122
192
  else if (t.revisesThought >= startIndex) {
123
- t.revisesThought = startIndex; // Point to summary
193
+ t.revisesThought = startIndex;
124
194
  }
125
195
  }
126
196
  }
127
- // Rebuild branches (simplification: clear and let it rebuild if needed, or just clear)
128
- this.branches = {};
129
- this.thoughtHistory.forEach(t => {
130
- if (t.branchFromThought && t.branchId) {
131
- const branchKey = `${t.branchFromThought}-${t.branchId}`;
132
- if (!this.branches[branchKey])
133
- this.branches[branchKey] = [];
134
- this.branches[branchKey].push(t);
135
- }
136
- });
197
+ this.rebuildBranches();
198
+ this.rebuildBlocks();
137
199
  await this.saveHistory();
138
200
  return {
139
201
  newHistoryLength: this.thoughtHistory.length,
@@ -144,7 +206,9 @@ export class SequentialThinkingServer {
144
206
  const lowerQuery = query.toLowerCase();
145
207
  return this.thoughtHistory.filter(t => t.thought.toLowerCase().includes(lowerQuery) ||
146
208
  (t.thoughtType && t.thoughtType.toLowerCase().includes(lowerQuery)) ||
147
- (t.branchId && t.branchId.toLowerCase().includes(lowerQuery)));
209
+ (t.branchId && t.branchId.toLowerCase().includes(lowerQuery)) ||
210
+ (t.blockId && t.blockId.toLowerCase().includes(lowerQuery)) ||
211
+ (t.relatedToolCall && t.relatedToolCall.toLowerCase().includes(lowerQuery)));
148
212
  }
149
213
  addToMemory(input) {
150
214
  if (input.thoughtNumber > input.totalThoughts) {
@@ -158,81 +222,190 @@ export class SequentialThinkingServer {
158
222
  }
159
223
  this.branches[branchKey].push(input);
160
224
  }
225
+ // Update block
226
+ if (input.blockId) {
227
+ let block = this.blocks.find(b => b.id === input.blockId);
228
+ if (!block) {
229
+ block = {
230
+ id: input.blockId,
231
+ topic: input.thought.substring(0, 50),
232
+ status: 'active',
233
+ thoughts: [],
234
+ createdAt: new Date().toISOString(),
235
+ updatedAt: new Date().toISOString()
236
+ };
237
+ this.blocks.push(block);
238
+ }
239
+ block.thoughts.push(input);
240
+ block.updatedAt = new Date().toISOString();
241
+ }
161
242
  }
162
243
  formatThought(thoughtData) {
163
- const { thoughtNumber, totalThoughts, thought, isRevision, revisesThought, branchFromThought, branchId, thoughtType, score, options, selectedOption } = thoughtData;
244
+ const { thoughtNumber, totalThoughts, thought, isRevision, revisesThought, branchFromThought, branchId, thoughtType, score, options, selectedOption, blockId, relatedToolCall } = thoughtData;
245
+ const typeEmoji = {
246
+ 'analysis': '🔍',
247
+ 'planning': '📋',
248
+ 'execution': '⚡',
249
+ 'observation': '👁️',
250
+ 'hypothesis': '💡',
251
+ 'reflexion': '🔄',
252
+ 'solution': '✅',
253
+ 'generation': '💭',
254
+ 'evaluation': '⚖️',
255
+ 'selection': '🎯'
256
+ };
164
257
  let prefix = '';
165
258
  let context = '';
166
- if (thoughtType === 'reflexion' || isRevision) {
167
- prefix = chalk.yellow('🔄 Reflexion');
259
+ const type = thoughtType || 'analysis';
260
+ const emoji = typeEmoji[type] || '💭';
261
+ if (type === 'reflexion' || isRevision) {
262
+ prefix = chalk.yellow(`${emoji} Reflexion`);
168
263
  if (revisesThought)
169
- context += ` (revising thought ${revisesThought})`;
264
+ context += ` (revising #${revisesThought})`;
265
+ }
266
+ else if (type === 'execution') {
267
+ prefix = chalk.magenta(`${emoji} Execution`);
268
+ if (relatedToolCall)
269
+ context += ` [Tool: ${relatedToolCall}]`;
170
270
  }
171
- else if (thoughtType === 'generation') {
172
- prefix = chalk.magenta('💡 Generation');
271
+ else if (type === 'observation') {
272
+ prefix = chalk.cyan(`${emoji} Observation`);
173
273
  }
174
- else if (thoughtType === 'evaluation') {
175
- prefix = chalk.cyan('⚖️ Evaluation');
176
- if (score)
177
- context += ` (Score: ${score})`;
274
+ else if (type === 'planning') {
275
+ prefix = chalk.blue(`${emoji} Planning`);
178
276
  }
179
- else if (thoughtType === 'selection') {
180
- prefix = chalk.green('✅ Selection');
181
- if (selectedOption)
182
- context += ` (Selected: ${selectedOption})`;
277
+ else if (type === 'solution') {
278
+ prefix = chalk.green(`${emoji} Solution`);
183
279
  }
184
280
  else if (branchFromThought) {
185
- prefix = chalk.green('🌿 Branch');
186
- context = ` (from thought ${branchFromThought}, ID: ${branchId})`;
281
+ prefix = chalk.green(`🌿 Branch`);
282
+ context = ` (from #${branchFromThought}, ID: ${branchId})`;
187
283
  }
188
284
  else {
189
- prefix = chalk.blue('💭 Thought');
190
- context = '';
285
+ prefix = chalk.blue(`${emoji} ${type.charAt(0).toUpperCase() + type.slice(1)}`);
191
286
  }
287
+ if (blockId && blockId !== 'default' && blockId !== 'legacy-default') {
288
+ context += ` [Block: ${blockId}]`;
289
+ }
290
+ if (score)
291
+ context += ` (Score: ${score})`;
292
+ if (selectedOption)
293
+ context += ` (Selected: ${selectedOption})`;
192
294
  const header = `${prefix} ${thoughtNumber}/${totalThoughts}${context}`;
193
- const borderLength = Math.max(header.length, thought.length) + 4;
295
+ const borderLength = Math.max(header.length, Math.min(thought.length, 80)) + 4;
194
296
  const border = '─'.repeat(borderLength);
195
297
  let extraContent = '';
196
298
  if (options && options.length > 0) {
197
- extraContent += `
198
- │ Options:
199
- ` + options.map(o => `│ - ${o}`).join('\n');
299
+ extraContent += `\n│ Options:\n` + options.map(o => `│ - ${o}`).join('\n');
200
300
  }
301
+ // Wrap long thoughts
302
+ const wrappedThought = thought.length > 76
303
+ ? thought.match(/.{1,76}/g)?.map(line => `│ ${line.padEnd(borderLength - 2)} │`).join('\n') || thought
304
+ : `│ ${thought.padEnd(borderLength - 2)} │`;
201
305
  return `
202
306
  ┌${border}┐
203
- │ ${header} │
307
+ │ ${header.padEnd(borderLength - 2)} │
204
308
  ├${border}┤
205
- │ ${thought.padEnd(borderLength - 2)} │${extraContent}
309
+ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('') ? wrappedThought : `│ ${thought.padEnd(borderLength - 2)} │`}${extraContent}
206
310
  └${border}┘`;
207
311
  }
312
+ // --- 2. The New Brain: Process Thought with Interleaved Logic (Opus 4.5 Style) ---
208
313
  async processThought(input) {
209
314
  try {
210
315
  if (this.delayMs > 0) {
211
316
  await new Promise(resolve => setTimeout(resolve, this.delayMs));
212
317
  }
213
- // --- 🧠 Logic Upgrade: Intelligence & Feedback ---
318
+ // A. Block Management (Auto-Create Block)
319
+ if (!input.blockId) {
320
+ if (!this.currentBlockId) {
321
+ this.currentBlockId = `block-${Date.now()}`;
322
+ this.blocks.push({
323
+ id: this.currentBlockId,
324
+ topic: input.thought.substring(0, 50),
325
+ status: 'active',
326
+ thoughts: [],
327
+ createdAt: new Date().toISOString(),
328
+ updatedAt: new Date().toISOString()
329
+ });
330
+ }
331
+ input.blockId = this.currentBlockId;
332
+ }
333
+ else if (input.blockId !== this.currentBlockId) {
334
+ // Context switch detected
335
+ const oldBlock = this.blocks.find(b => b.id === this.currentBlockId);
336
+ if (oldBlock) {
337
+ oldBlock.status = 'completed';
338
+ }
339
+ this.currentBlockId = input.blockId;
340
+ const existingBlock = this.blocks.find(b => b.id === input.blockId);
341
+ if (!existingBlock) {
342
+ this.blocks.push({
343
+ id: input.blockId,
344
+ topic: input.thought.substring(0, 50),
345
+ status: 'active',
346
+ thoughts: [],
347
+ createdAt: new Date().toISOString(),
348
+ updatedAt: new Date().toISOString()
349
+ });
350
+ }
351
+ else {
352
+ existingBlock.status = 'active';
353
+ }
354
+ }
355
+ // B. Smart Loop Detection (Opus Style)
214
356
  const warnings = [];
215
- // 1. Loop Detection (ตรวจการคิดวน)
216
- const recentThoughts = this.thoughtHistory.slice(-3);
217
- if (recentThoughts.length >= 3 && recentThoughts.every(t => t.thoughtType === input.thoughtType)) {
218
- warnings.push(`⚠️ WARNING: You have used '${input.thoughtType}' for 4 consecutive steps. Consider 'reflexion' or 'generation' to progress.`);
357
+ const blockThoughts = this.thoughtHistory.filter(t => t.blockId === input.blockId);
358
+ const recentInBlock = blockThoughts.slice(-3);
359
+ const lastThought = this.thoughtHistory[this.thoughtHistory.length - 1];
360
+ // Rule 1: Stalling Detection (Thinking too much without Action)
361
+ const thinkingTypes = ['analysis', 'planning', 'hypothesis', 'generation'];
362
+ if (recentInBlock.length >= 3 &&
363
+ recentInBlock.every(t => thinkingTypes.includes(t.thoughtType || 'analysis')) &&
364
+ thinkingTypes.includes(input.thoughtType || 'analysis')) {
365
+ warnings.push(`⚠️ STALLING DETECTED: You have been thinking for 4 consecutive steps without execution. Consider changing thoughtType to 'execution' and calling a tool to make progress.`);
366
+ }
367
+ // Rule 2: Repeating Action Detection (Insanity Check)
368
+ if (input.thoughtType === 'execution' &&
369
+ recentInBlock.some(t => t.thoughtType === 'execution' && t.thought === input.thought)) {
370
+ warnings.push(`🛑 LOOP DETECTED: You are attempting to execute the exact same action again. You MUST change your strategy or create a branch with a different approach.`);
371
+ }
372
+ // Rule 3: Missing Observation (Interleaved Enforcer)
373
+ if (lastThought &&
374
+ lastThought.thoughtType === 'execution' &&
375
+ input.thoughtType !== 'observation') {
376
+ warnings.push(`💡 INTERLEAVED HINT: Your last step was 'execution'. The next step SHOULD be 'observation' to record and analyze the tool result before continuing.`);
219
377
  }
220
- // 2. Completion Guard (เตือนถ้าลืมเทส)
378
+ // Rule 4: Skipping Planning (Rushing Detection)
379
+ const hasPlanning = blockThoughts.some(t => t.thoughtType === 'planning');
380
+ if (!hasPlanning &&
381
+ blockThoughts.length >= 2 &&
382
+ input.thoughtType === 'execution') {
383
+ warnings.push(`📋 PLANNING HINT: You're about to execute without a planning step. Consider adding a 'planning' thought first to outline your approach.`);
384
+ }
385
+ // Rule 5: Ending without Verification
221
386
  if (input.nextThoughtNeeded === false) {
222
387
  const hasVerification = this.thoughtHistory.some(t => t.thought.toLowerCase().includes('test') ||
223
388
  t.thought.toLowerCase().includes('verify') ||
224
- t.thought.toLowerCase().includes('check'));
389
+ t.thought.toLowerCase().includes('check') ||
390
+ t.thoughtType === 'observation');
225
391
  if (!hasVerification) {
226
- warnings.push(`🚨 CRITICAL: You are ending the task without explicit verification/testing steps. It is recommended to verify changes first.`);
392
+ warnings.push(`🚨 CRITICAL: You are ending without explicit verification/testing. Consider adding an 'observation' or verification step.`);
227
393
  }
228
394
  }
395
+ // Rule 6: Same type loop (general)
396
+ if (recentInBlock.length >= 3 &&
397
+ recentInBlock.every(t => t.thoughtType === input.thoughtType)) {
398
+ warnings.push(`⚠️ TYPE LOOP: You've used '${input.thoughtType}' for 4 consecutive steps. Consider using a different thought type to progress.`);
399
+ }
400
+ // C. Update State
229
401
  this.addToMemory(input);
230
402
  await this.saveHistory();
231
403
  if (!this.disableThoughtLogging) {
232
404
  const formattedThought = this.formatThought(input);
233
405
  console.error(formattedThought);
234
406
  }
235
- // 3. Visualization (Mermaid Diagram)
407
+ // D. Generate Contextual Output
408
+ const currentBlock = this.blocks.find(b => b.id === input.blockId);
236
409
  const mermaid = this.generateMermaid();
237
410
  return {
238
411
  content: [{
@@ -241,9 +414,14 @@ export class SequentialThinkingServer {
241
414
  thoughtNumber: input.thoughtNumber,
242
415
  totalThoughts: input.totalThoughts,
243
416
  nextThoughtNeeded: input.nextThoughtNeeded,
417
+ currentBlock: input.blockId,
418
+ blockContext: currentBlock
419
+ ? `Block '${currentBlock.topic.substring(0, 30)}' has ${currentBlock.thoughts.length} thoughts.`
420
+ : 'No active block',
244
421
  branches: Object.keys(this.branches),
245
422
  thoughtHistoryLength: this.thoughtHistory.length,
246
- feedback: warnings.length > 0 ? warnings : "Analysis healthy",
423
+ feedback: warnings.length > 0 ? warnings : "Flow Healthy ✅",
424
+ interleavedState: this.getInterleavedState(input),
247
425
  visualization: mermaid
248
426
  }, null, 2)
249
427
  }]
@@ -262,22 +440,84 @@ export class SequentialThinkingServer {
262
440
  };
263
441
  }
264
442
  }
443
+ getInterleavedState(input) {
444
+ const lastThought = this.thoughtHistory[this.thoughtHistory.length - 2]; // -2 because current is already added
445
+ if (!lastThought)
446
+ return 'START';
447
+ if (lastThought.thoughtType === 'execution') {
448
+ return input.thoughtType === 'observation'
449
+ ? 'INTERLEAVED_CORRECT'
450
+ : 'INTERLEAVED_EXPECTED_OBSERVATION';
451
+ }
452
+ if (lastThought.thoughtType === 'observation') {
453
+ return 'POST_OBSERVATION';
454
+ }
455
+ return 'THINKING';
456
+ }
265
457
  generateMermaid() {
266
458
  let diagram = 'graph TD\n';
267
- this.thoughtHistory.forEach((t, i) => {
459
+ const recentThoughts = this.thoughtHistory.slice(-15); // Last 15 for readability
460
+ recentThoughts.forEach((t, i) => {
268
461
  const id = `T${t.thoughtNumber}`;
269
- const label = `${t.thoughtNumber}[${t.thoughtType || 'thought'}]`;
462
+ const typeIcon = {
463
+ 'analysis': '🔍',
464
+ 'planning': '📋',
465
+ 'execution': '⚡',
466
+ 'observation': '👁️',
467
+ 'hypothesis': '💡',
468
+ 'reflexion': '🔄',
469
+ 'solution': '✅',
470
+ 'generation': '💭',
471
+ 'evaluation': '⚖️',
472
+ 'selection': '🎯'
473
+ }[t.thoughtType || 'analysis'] || '💭';
474
+ const label = `${typeIcon} ${t.thoughtNumber}`;
270
475
  diagram += ` ${id}("${label}")\n`;
271
476
  if (i > 0) {
272
- const prevId = `T${this.thoughtHistory[i - 1].thoughtNumber}`;
477
+ const prevT = recentThoughts[i - 1];
478
+ const prevId = `T${prevT.thoughtNumber}`;
273
479
  if (t.branchFromThought) {
274
480
  diagram += ` T${t.branchFromThought} -->|branch: ${t.branchId}| ${id}\n`;
275
481
  }
276
482
  else {
277
- diagram += ` ${prevId} --> ${id}\n`;
483
+ const edgeLabel = t.thoughtType === 'observation' ? '|result|' : '';
484
+ diagram += ` ${prevId} -->${edgeLabel} ${id}\n`;
278
485
  }
279
486
  }
280
487
  });
281
488
  return diagram;
282
489
  }
490
+ // Public method to start a new block explicitly
491
+ startNewBlock(blockId, topic) {
492
+ if (this.currentBlockId) {
493
+ const oldBlock = this.blocks.find(b => b.id === this.currentBlockId);
494
+ if (oldBlock) {
495
+ oldBlock.status = 'completed';
496
+ }
497
+ }
498
+ this.currentBlockId = blockId;
499
+ this.blocks.push({
500
+ id: blockId,
501
+ topic: topic,
502
+ status: 'active',
503
+ thoughts: [],
504
+ createdAt: new Date().toISOString(),
505
+ updatedAt: new Date().toISOString()
506
+ });
507
+ }
508
+ // Get current block info
509
+ getCurrentBlock() {
510
+ if (!this.currentBlockId)
511
+ return null;
512
+ return this.blocks.find(b => b.id === this.currentBlockId) || null;
513
+ }
514
+ // Get all blocks summary
515
+ getBlocksSummary() {
516
+ return this.blocks.map(b => ({
517
+ id: b.id,
518
+ topic: b.topic,
519
+ status: b.status,
520
+ thoughtCount: b.thoughts.length
521
+ }));
522
+ }
283
523
  }
@@ -0,0 +1,25 @@
1
+ export type Priority = 'low' | 'medium' | 'high' | 'critical';
2
+ export interface Note {
3
+ id: string;
4
+ title: string;
5
+ content: string;
6
+ tags: string[];
7
+ priority: Priority;
8
+ expiresAt?: string;
9
+ createdAt: string;
10
+ updatedAt: string;
11
+ }
12
+ export declare class NotesManager {
13
+ private filePath;
14
+ private notes;
15
+ private loaded;
16
+ private mutex;
17
+ constructor(storagePath?: string);
18
+ private load;
19
+ private save;
20
+ addNote(title: string, content: string, tags?: string[], priority?: Priority, expiresAt?: string): Promise<Note>;
21
+ listNotes(tag?: string, includeExpired?: boolean): Promise<Note[]>;
22
+ searchNotes(query: string): Promise<Note[]>;
23
+ deleteNote(id: string): Promise<boolean>;
24
+ updateNote(id: string, updates: Partial<Pick<Note, 'title' | 'content' | 'tags'>>): Promise<Note | null>;
25
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { CodeDatabase } from '../codestore.js';
3
+ export declare function registerCodeDbTools(server: McpServer, db: CodeDatabase): void;
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ProjectKnowledgeGraph } from '../graph.js';
3
+ export declare function registerCodingTools(server: McpServer, graph: ProjectKnowledgeGraph): void;
@@ -31,9 +31,46 @@ export function registerCodingTools(server, graph) {
31
31
  doc += `- ${d.path} (Symbols: ${d.symbols.join(', ')})\n`;
32
32
  });
33
33
  doc += `\nDEPENDENTS (Files that use this file):\n`;
34
- context.dependents.forEach(d => {
35
- doc += `- ${d.path} (Symbols: ${d.symbols.join(', ')})\n`;
36
- });
34
+ // Helper to find usages (defined inline to capture context/scope)
35
+ const findUsages = async (fileToSearch, targetPath, symbols) => {
36
+ try {
37
+ const content = await fs.readFile(fileToSearch, 'utf-8');
38
+ const lines = content.split('\n');
39
+ const matches = [];
40
+ const baseName = path.basename(targetPath, path.extname(targetPath));
41
+ // Escape regex special chars
42
+ const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
43
+ const symbolRegexes = symbols.map(s => new RegExp(`\\b${escapeRegExp(s)}\\b`));
44
+ lines.forEach((line, index) => {
45
+ // Check for import of the file itself (simple heuristic)
46
+ if ((line.includes('import') || line.includes('require')) && line.includes(baseName)) {
47
+ matches.push(`Line ${index + 1}: ${line.trim()}`);
48
+ return;
49
+ }
50
+ // Check for symbols
51
+ for (const regex of symbolRegexes) {
52
+ if (regex.test(line)) {
53
+ matches.push(`Line ${index + 1}: ${line.trim()}`);
54
+ break; // One match per line is enough
55
+ }
56
+ }
57
+ });
58
+ return matches;
59
+ }
60
+ catch (e) {
61
+ return [];
62
+ }
63
+ };
64
+ for (const d of context.dependents) {
65
+ doc += `- ${d.path}\n`;
66
+ const usages = await findUsages(d.path, filePath, context.targetFile.symbols);
67
+ if (usages.length > 0) {
68
+ // Limit to top 3 to save context
69
+ usages.slice(0, 3).forEach(u => doc += ` ${u}\n`);
70
+ if (usages.length > 3)
71
+ doc += ` ... (${usages.length - 3} more references)\n`;
72
+ }
73
+ }
37
74
  doc += `\n--- END OF CONTEXT ---`;
38
75
  return {
39
76
  content: [{ type: "text", text: doc }]
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerFileSystemTools(server: McpServer): void;