@gotza02/sequential-thinking 2026.3.11 → 2026.3.12

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,9 +1,12 @@
1
1
  import { ThoughtData } from '../lib.js';
2
2
  export declare class ContextManager {
3
3
  private summaryCache;
4
+ private readonly STOP_WORDS;
4
5
  constructor();
5
6
  getOptimizedContext(history: ThoughtData[], currentBlockId: string | null): Promise<string>;
6
- private compressOldBlocks;
7
- private groupByBlock;
7
+ private getBlockSummaries;
8
+ private selectRelevantBlocks;
9
+ private extractKeywords;
10
+ private calculateOverlap;
8
11
  private generateSummary;
9
12
  }
@@ -1,56 +1,137 @@
1
1
  export class ContextManager {
2
2
  summaryCache = new Map();
3
+ STOP_WORDS = new Set([
4
+ 'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by',
5
+ 'is', 'are', 'was', 'were', 'be', 'been', 'being',
6
+ 'have', 'has', 'had', 'do', 'does', 'did',
7
+ 'it', 'this', 'that', 'these', 'those',
8
+ 'i', 'you', 'he', 'she', 'we', 'they',
9
+ 'from', 'as', 'what', 'which', 'when', 'where', 'how', 'why'
10
+ ]);
3
11
  constructor() { }
4
12
  async getOptimizedContext(history, currentBlockId) {
5
- // If no current block, treat everything as old or just show all?
6
- // If null, we might be in setup mode. Let's assume empty current block.
7
13
  const safeCurrentBlockId = currentBlockId || '';
8
- const oldBlocks = history.filter(t => t.blockId !== safeCurrentBlockId);
14
+ const oldBlocksThoughts = history.filter(t => t.blockId !== safeCurrentBlockId);
9
15
  const activeBlock = history.filter(t => t.blockId === safeCurrentBlockId);
10
- const summaries = await this.compressOldBlocks(oldBlocks);
16
+ // 1. Get all summaries (with caching)
17
+ const allSummaries = await this.getBlockSummaries(oldBlocksThoughts);
18
+ // 2. Select relevant summaries (Smart Context)
19
+ // Query = Active block content + Current Topic (if any)
20
+ const currentTopic = activeBlock.length > 0 ? activeBlock[0].thought.substring(0, 50) : '';
21
+ const queryText = currentTopic + " " + activeBlock.map(t => t.thought).join(' ');
22
+ const selectedSummaries = this.selectRelevantBlocks(allSummaries, queryText);
23
+ // 3. Format Output
24
+ let summariesText = "";
25
+ if (selectedSummaries.length === 0 && allSummaries.length > 0) {
26
+ // Fallback: If no relevant found (rare), show last 2
27
+ const recent = allSummaries.sort((a, b) => b.lastUpdated - a.lastUpdated).slice(0, 2);
28
+ summariesText = recent.map(s => `[Block: ${s.blockId}] Summary: ${s.summary}`).join('\n');
29
+ }
30
+ else {
31
+ summariesText = selectedSummaries.map(s => `[Block: ${s.blockId}] Summary: ${s.summary}`).join('\n');
32
+ }
11
33
  // Format active block detailed
12
34
  const activeContext = activeBlock.map(t => `[${t.thoughtType?.toUpperCase() || 'INFO'}] #${t.thoughtNumber}: ${t.thought}`).join('\n');
13
35
  return `
14
- === PROJECT HISTORY (SUMMARIZED) ===
15
- ${summaries}
36
+ === PROJECT HISTORY (SMART CONTEXT) ===
37
+ ${summariesText}
16
38
 
17
39
  === CURRENT FOCUS (DETAILED: ${safeCurrentBlockId || 'GLOBAL'}) ===
18
40
  ${activeContext}
19
41
  `;
20
42
  }
21
- async compressOldBlocks(logs) {
22
- const groups = this.groupByBlock(logs);
23
- let result = '';
24
- for (const [blockId, thoughts] of groups) {
25
- // Skip blocks with undefined or empty blockId if they sneak in
43
+ async getBlockSummaries(logs) {
44
+ // 1. Group by block (maintaining order)
45
+ const groups = new Map();
46
+ logs.forEach((log, index) => {
47
+ const bid = log.blockId || 'default';
48
+ if (!groups.has(bid)) {
49
+ groups.set(bid, { thoughts: [], maxIndex: -1 });
50
+ }
51
+ const group = groups.get(bid);
52
+ group.thoughts.push(log);
53
+ group.maxIndex = index; // Always updates to the latest index encountered
54
+ });
55
+ const results = [];
56
+ for (const [blockId, data] of groups) {
26
57
  if (!blockId)
27
58
  continue;
59
+ const { thoughts, maxIndex } = data;
60
+ let summary = "";
28
61
  const cached = this.summaryCache.get(blockId);
29
62
  if (cached && cached.count === thoughts.length) {
30
- result += `[Block: ${blockId}] Summary: ${cached.summary}\n`;
63
+ summary = cached.summary;
31
64
  }
32
65
  else {
33
66
  if (thoughts.length > 2) {
34
- const summary = await this.generateSummary(thoughts);
67
+ summary = await this.generateSummary(thoughts);
35
68
  this.summaryCache.set(blockId, { summary, count: thoughts.length });
36
- result += `[Block: ${blockId}] Summary: ${summary}\n`;
37
69
  }
38
70
  else {
39
- result += `[Block: ${blockId}] ${thoughts.map(t => t.thought).join(' -> ')}\n`;
71
+ summary = thoughts.map(t => t.thought).join(' -> ');
40
72
  }
41
73
  }
74
+ // Extract topic from first thought or blockId
75
+ const topic = thoughts[0]?.thought.substring(0, 50) || blockId;
76
+ results.push({
77
+ blockId,
78
+ summary,
79
+ topic,
80
+ lastUpdated: maxIndex // Use the array index as the source of truth for recency
81
+ });
42
82
  }
43
- return result;
83
+ return results;
44
84
  }
45
- groupByBlock(logs) {
46
- const map = new Map();
47
- logs.forEach(log => {
48
- const bid = log.blockId || 'default';
49
- const items = map.get(bid) || [];
50
- items.push(log);
51
- map.set(bid, items);
85
+ selectRelevantBlocks(candidates, query) {
86
+ if (candidates.length === 0)
87
+ return [];
88
+ const queryKeywords = this.extractKeywords(query);
89
+ const scored = candidates.map(block => {
90
+ const blockKeywords = this.extractKeywords(block.topic + " " + block.summary);
91
+ const score = this.calculateOverlap(queryKeywords, blockKeywords);
92
+ return { block, score };
93
+ });
94
+ // Sort by score descending, then recency
95
+ scored.sort((a, b) => {
96
+ if (a.score !== b.score)
97
+ return b.score - a.score;
98
+ return b.block.lastUpdated - a.block.lastUpdated;
52
99
  });
53
- return map;
100
+ // Always include the immediate previous block (for continuity)
101
+ // Find the block with the highest lastUpdated
102
+ const lastBlock = candidates.reduce((prev, current) => (prev.lastUpdated > current.lastUpdated) ? prev : current);
103
+ const selected = new Set();
104
+ selected.add(lastBlock); // Always add last block
105
+ // Add Top N relevant blocks
106
+ for (const item of scored) {
107
+ if (selected.size >= 4)
108
+ break; // Max 4 blocks context
109
+ if (item.score > 0) { // Only if there is some relevance
110
+ selected.add(item.block);
111
+ }
112
+ }
113
+ // Return sorted by recency (oldest to newest) for logical flow
114
+ return Array.from(selected).sort((a, b) => a.lastUpdated - b.lastUpdated);
115
+ }
116
+ extractKeywords(text) {
117
+ const tokens = text.toLowerCase()
118
+ .replace(/[^a-z0-9\s]/g, '') // Remove symbols
119
+ .split(/\s+/);
120
+ const keywords = new Set();
121
+ for (const t of tokens) {
122
+ if (t.length > 2 && !this.STOP_WORDS.has(t)) {
123
+ keywords.add(t);
124
+ }
125
+ }
126
+ return keywords;
127
+ }
128
+ calculateOverlap(setA, setB) {
129
+ let intersection = 0;
130
+ for (const elem of setA) {
131
+ if (setB.has(elem))
132
+ intersection++;
133
+ }
134
+ return intersection;
54
135
  }
55
136
  async generateSummary(thoughts) {
56
137
  // Mock implementation
@@ -0,0 +1,10 @@
1
+ export declare class RuleManager {
2
+ private rules;
3
+ private storagePath;
4
+ constructor(storagePath?: string);
5
+ private loadRules;
6
+ private saveRules;
7
+ addRule(trigger: string, warning: string): Promise<void>;
8
+ checkRules(execution: string): string[];
9
+ getRulesSummary(): string;
10
+ }
@@ -0,0 +1,62 @@
1
+ import * as fs from 'fs/promises';
2
+ import { existsSync } from 'fs';
3
+ import * as path from 'path';
4
+ export class RuleManager {
5
+ rules = [];
6
+ storagePath;
7
+ constructor(storagePath = 'learned_rules.json') {
8
+ this.storagePath = path.resolve(storagePath);
9
+ this.loadRules();
10
+ }
11
+ async loadRules() {
12
+ try {
13
+ if (existsSync(this.storagePath)) {
14
+ const data = await fs.readFile(this.storagePath, 'utf-8');
15
+ this.rules = JSON.parse(data);
16
+ }
17
+ }
18
+ catch (error) {
19
+ console.error('[RuleManager] Error loading rules:', error);
20
+ }
21
+ }
22
+ async saveRules() {
23
+ try {
24
+ await fs.writeFile(this.storagePath, JSON.stringify(this.rules, null, 2), 'utf-8');
25
+ }
26
+ catch (error) {
27
+ console.error('[RuleManager] Error saving rules:', error);
28
+ }
29
+ }
30
+ async addRule(trigger, warning) {
31
+ const existing = this.rules.find(r => r.trigger === trigger);
32
+ if (existing) {
33
+ existing.failCount++;
34
+ existing.warning = warning; // Update with latest context
35
+ }
36
+ else {
37
+ this.rules.push({
38
+ trigger,
39
+ warning,
40
+ createdAt: new Date().toISOString(),
41
+ failCount: 1
42
+ });
43
+ }
44
+ await this.saveRules();
45
+ }
46
+ checkRules(execution) {
47
+ const warnings = [];
48
+ const lowerExec = execution.toLowerCase();
49
+ for (const rule of this.rules) {
50
+ // Simple keyword check or exact match
51
+ if (lowerExec.includes(rule.trigger.toLowerCase())) {
52
+ warnings.push(`🛡️ IMMUNE SYSTEM WARNING: This action previously failed. Lesson: "${rule.warning}"`);
53
+ }
54
+ }
55
+ return warnings;
56
+ }
57
+ getRulesSummary() {
58
+ if (this.rules.length === 0)
59
+ return "No rules learned yet.";
60
+ return this.rules.map(r => `- [${r.trigger}] ${r.warning}`).join('\n');
61
+ }
62
+ }
@@ -0,0 +1,2 @@
1
+ import * as http from 'http';
2
+ export declare function startDashboard(port: number, historyPath: string): Promise<http.Server>;
@@ -0,0 +1,67 @@
1
+ import * as http from 'http';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ export function startDashboard(port, historyPath) {
8
+ return new Promise((resolve, reject) => {
9
+ const server = http.createServer((req, res) => {
10
+ // Handle CORS
11
+ res.setHeader('Access-Control-Allow-Origin', '*');
12
+ res.setHeader('Access-Control-Allow-Methods', 'GET');
13
+ if (req.url === '/') {
14
+ // Serve index.html
15
+ const indexPath = path.join(__dirname, 'index.html');
16
+ fs.readFile(indexPath, (err, data) => {
17
+ if (err) {
18
+ res.writeHead(500);
19
+ res.end('Error loading dashboard');
20
+ return;
21
+ }
22
+ res.writeHead(200, { 'Content-Type': 'text/html' });
23
+ res.end(data);
24
+ });
25
+ }
26
+ else if (req.url === '/api/history') {
27
+ // Serve thoughts_history.json
28
+ fs.readFile(historyPath, (err, data) => {
29
+ if (err) {
30
+ // Try .tmp if main file is locked/missing (rare fallback)
31
+ fs.readFile(historyPath + '.tmp', (err2, data2) => {
32
+ if (err2) {
33
+ res.writeHead(500);
34
+ res.end(JSON.stringify({ error: 'History not found' }));
35
+ return;
36
+ }
37
+ res.writeHead(200, { 'Content-Type': 'application/json' });
38
+ res.end(data2);
39
+ });
40
+ return;
41
+ }
42
+ res.writeHead(200, { 'Content-Type': 'application/json' });
43
+ res.end(data);
44
+ });
45
+ }
46
+ else {
47
+ res.writeHead(404);
48
+ res.end('Not found');
49
+ }
50
+ });
51
+ server.on('error', (e) => {
52
+ if (e.code === 'EADDRINUSE') {
53
+ console.error(`[Dashboard] Port ${port} is busy. Trying ${port + 1}...`);
54
+ // Recursive retry
55
+ startDashboard(port + 1, historyPath).then(resolve).catch(reject);
56
+ }
57
+ else {
58
+ console.error('[Dashboard] Server error:', e);
59
+ reject(e);
60
+ }
61
+ });
62
+ server.listen(port, () => {
63
+ console.error(`[Dashboard] 📊 Dashboard running at http://localhost:${port}`);
64
+ resolve(server);
65
+ });
66
+ });
67
+ }
package/dist/index.js CHANGED
@@ -19,6 +19,7 @@ import { registerCodingTools } from './tools/coding.js';
19
19
  import { registerCodeDbTools } from './tools/codestore.js';
20
20
  import { registerHumanTools } from './tools/human.js';
21
21
  import { registerSportsTools } from './tools/sports.js';
22
+ import { startDashboard } from './dashboard/server.js';
22
23
  const __filename = fileURLToPath(import.meta.url);
23
24
  const __dirname = dirname(__filename);
24
25
  const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
@@ -27,6 +28,11 @@ const server = new McpServer({
27
28
  version: pkg.version,
28
29
  });
29
30
  const thinkingServer = new SequentialThinkingServer(process.env.THOUGHTS_STORAGE_PATH || 'thoughts_history.json', parseInt(process.env.THOUGHT_DELAY_MS || '0', 10));
31
+ // Start Dashboard
32
+ const historyPath = process.env.THOUGHTS_STORAGE_PATH || 'thoughts_history.json';
33
+ startDashboard(3001, historyPath).catch(err => {
34
+ console.error("[Dashboard] Failed to start:", err);
35
+ });
30
36
  const knowledgeGraph = new ProjectKnowledgeGraph();
31
37
  const memoryGraph = new KnowledgeGraphManager(process.env.MEMORY_GRAPH_PATH || 'knowledge_graph.json');
32
38
  const notesManager = new NotesManager(process.env.NOTES_STORAGE_PATH || 'project_notes.json');
package/dist/lib.d.ts CHANGED
@@ -39,6 +39,7 @@ export declare class SequentialThinkingServer {
39
39
  private consecutiveStallCount;
40
40
  private confidenceScore;
41
41
  private contextManager;
42
+ private ruleManager;
42
43
  constructor(storagePath?: string, delayMs?: number);
43
44
  private loadHistory;
44
45
  private attemptRecovery;
@@ -71,5 +72,6 @@ export declare class SequentialThinkingServer {
71
72
  thoughtCount: number;
72
73
  }[];
73
74
  getHistoryLength(): number;
75
+ private learnFromFailures;
74
76
  }
75
77
  export {};
package/dist/lib.js CHANGED
@@ -4,6 +4,7 @@ import { existsSync, readFileSync } from 'fs';
4
4
  import * as path from 'path';
5
5
  import { AsyncMutex } from './utils.js';
6
6
  import { ContextManager } from './core/ContextManager.js';
7
+ import { RuleManager } from './core/RuleManager.js';
7
8
  export class SequentialThinkingServer {
8
9
  thoughtHistory = [];
9
10
  blocks = [];
@@ -17,6 +18,7 @@ export class SequentialThinkingServer {
17
18
  consecutiveStallCount = 0; // Track consecutive stalls/loops
18
19
  confidenceScore = 100; // Meta-Cognition Score (0-100)
19
20
  contextManager = new ContextManager();
21
+ ruleManager = new RuleManager();
20
22
  constructor(storagePath = 'thoughts_history.json', delayMs = 0) {
21
23
  this.disableThoughtLogging = (process.env.DISABLE_THOUGHT_LOGGING || "").toLowerCase() === "true";
22
24
  this.storagePath = path.resolve(storagePath);
@@ -523,6 +525,7 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
523
525
  const oldBlock = this.blocks.find(b => b.id === this.currentBlockId);
524
526
  if (oldBlock) {
525
527
  oldBlock.status = 'completed';
528
+ await this.learnFromFailures(oldBlock.id);
526
529
  // Auto-Summary Trigger
527
530
  if (oldBlock.thoughts.length > 5) {
528
531
  warnings.push(`📜 CONTEXT MANAGEMENT: Previous block '${oldBlock.topic.substring(0, 30)}...' was long. Please use 'summarize_history' to compress it.`);
@@ -560,10 +563,16 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
560
563
  }
561
564
  }
562
565
  // Rule 2: Repeating Action Detection
563
- if (input.thoughtType === 'execution' &&
564
- recentInBlock.some(t => t.thoughtType === 'execution' && t.thought === input.thought)) {
565
- 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.`);
566
- isStallingOrLooping = true;
566
+ if (input.thoughtType === 'execution') {
567
+ // Check Immune System Rules
568
+ const ruleWarnings = this.ruleManager.checkRules(input.thought);
569
+ ruleWarnings.forEach(w => warnings.push(w));
570
+ if (ruleWarnings.length > 0)
571
+ this.confidenceScore -= 10;
572
+ if (recentInBlock.some(t => t.thoughtType === 'execution' && t.thought === input.thought)) {
573
+ 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.`);
574
+ isStallingOrLooping = true;
575
+ }
567
576
  }
568
577
  // Rule 3: Missing Observation
569
578
  if (lastThought &&
@@ -646,6 +655,7 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
646
655
  .filter(t => t.thoughtType === 'execution')
647
656
  .map(t => t.thought);
648
657
  const activeBlock = this.blocks.find(b => b.id === input.blockId);
658
+ await this.learnFromFailures(input.blockId || "");
649
659
  await this.saveSolution(activeBlock?.topic || 'Untitled', input.thought, solutionSteps);
650
660
  }
651
661
  }
@@ -662,7 +672,10 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
662
672
  // --- 📊 Update Confidence Score ---
663
673
  if (warnings.length > 0)
664
674
  this.confidenceScore -= (5 * warnings.length);
665
- if (input.thoughtType === 'observation') {
675
+ if (input.thoughtType === 'reflexion') {
676
+ this.confidenceScore = Math.min(100, this.confidenceScore + 10);
677
+ }
678
+ else if (input.thoughtType === 'observation') {
666
679
  const isError = input.toolResult?.toLowerCase().includes('error');
667
680
  if (isError)
668
681
  this.confidenceScore -= 10;
@@ -805,4 +818,18 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
805
818
  getHistoryLength() {
806
819
  return this.thoughtHistory.length;
807
820
  }
821
+ async learnFromFailures(blockId) {
822
+ const blockThoughts = this.thoughtHistory.filter(t => t.blockId === blockId);
823
+ for (let i = 0; i < blockThoughts.length - 1; i++) {
824
+ const current = blockThoughts[i];
825
+ const next = blockThoughts[i + 1];
826
+ if (current.thoughtType === 'execution' && next.thoughtType === 'observation') {
827
+ const res = next.toolResult?.toLowerCase() || "";
828
+ // Failure keywords
829
+ if (res.includes("error") || res.includes("fail") || res.includes("exception") || res.includes("enoent")) {
830
+ await this.ruleManager.addRule(current.thought, res.substring(0, 100).replace(/\n/g, ' '));
831
+ }
832
+ }
833
+ }
834
+ }
808
835
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotza02/sequential-thinking",
3
- "version": "2026.3.11",
3
+ "version": "2026.3.12",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },