@gotza02/sequential-thinking 2026.3.10 → 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.
@@ -0,0 +1,12 @@
1
+ import { ThoughtData } from '../lib.js';
2
+ export declare class ContextManager {
3
+ private summaryCache;
4
+ private readonly STOP_WORDS;
5
+ constructor();
6
+ getOptimizedContext(history: ThoughtData[], currentBlockId: string | null): Promise<string>;
7
+ private getBlockSummaries;
8
+ private selectRelevantBlocks;
9
+ private extractKeywords;
10
+ private calculateOverlap;
11
+ private generateSummary;
12
+ }
@@ -0,0 +1,143 @@
1
+ export class ContextManager {
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
+ ]);
11
+ constructor() { }
12
+ async getOptimizedContext(history, currentBlockId) {
13
+ const safeCurrentBlockId = currentBlockId || '';
14
+ const oldBlocksThoughts = history.filter(t => t.blockId !== safeCurrentBlockId);
15
+ const activeBlock = history.filter(t => t.blockId === safeCurrentBlockId);
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
+ }
33
+ // Format active block detailed
34
+ const activeContext = activeBlock.map(t => `[${t.thoughtType?.toUpperCase() || 'INFO'}] #${t.thoughtNumber}: ${t.thought}`).join('\n');
35
+ return `
36
+ === PROJECT HISTORY (SMART CONTEXT) ===
37
+ ${summariesText}
38
+
39
+ === CURRENT FOCUS (DETAILED: ${safeCurrentBlockId || 'GLOBAL'}) ===
40
+ ${activeContext}
41
+ `;
42
+ }
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) {
57
+ if (!blockId)
58
+ continue;
59
+ const { thoughts, maxIndex } = data;
60
+ let summary = "";
61
+ const cached = this.summaryCache.get(blockId);
62
+ if (cached && cached.count === thoughts.length) {
63
+ summary = cached.summary;
64
+ }
65
+ else {
66
+ if (thoughts.length > 2) {
67
+ summary = await this.generateSummary(thoughts);
68
+ this.summaryCache.set(blockId, { summary, count: thoughts.length });
69
+ }
70
+ else {
71
+ summary = thoughts.map(t => t.thought).join(' -> ');
72
+ }
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
+ });
82
+ }
83
+ return results;
84
+ }
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;
99
+ });
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;
135
+ }
136
+ async generateSummary(thoughts) {
137
+ // Mock implementation
138
+ const decisions = thoughts.filter(t => t.thoughtType === 'planning' || t.thoughtType === 'selection').length;
139
+ const executions = thoughts.filter(t => t.thoughtType === 'execution').length;
140
+ const topic = thoughts[0]?.thought.substring(0, 30) || 'Unknown';
141
+ return `Topic: "${topic}...". Processed ${thoughts.length} steps (${decisions} plans, ${executions} actions). Outcome: Handed off or completed.`;
142
+ }
143
+ }
@@ -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
@@ -38,6 +38,8 @@ export declare class SequentialThinkingServer {
38
38
  private saveMutex;
39
39
  private consecutiveStallCount;
40
40
  private confidenceScore;
41
+ private contextManager;
42
+ private ruleManager;
41
43
  constructor(storagePath?: string, delayMs?: number);
42
44
  private loadHistory;
43
45
  private attemptRecovery;
@@ -70,5 +72,6 @@ export declare class SequentialThinkingServer {
70
72
  thoughtCount: number;
71
73
  }[];
72
74
  getHistoryLength(): number;
75
+ private learnFromFailures;
73
76
  }
74
77
  export {};
package/dist/lib.js CHANGED
@@ -3,6 +3,8 @@ import * as fs from 'fs/promises';
3
3
  import { existsSync, readFileSync } from 'fs';
4
4
  import * as path from 'path';
5
5
  import { AsyncMutex } from './utils.js';
6
+ import { ContextManager } from './core/ContextManager.js';
7
+ import { RuleManager } from './core/RuleManager.js';
6
8
  export class SequentialThinkingServer {
7
9
  thoughtHistory = [];
8
10
  blocks = [];
@@ -15,6 +17,8 @@ export class SequentialThinkingServer {
15
17
  saveMutex = new AsyncMutex();
16
18
  consecutiveStallCount = 0; // Track consecutive stalls/loops
17
19
  confidenceScore = 100; // Meta-Cognition Score (0-100)
20
+ contextManager = new ContextManager();
21
+ ruleManager = new RuleManager();
18
22
  constructor(storagePath = 'thoughts_history.json', delayMs = 0) {
19
23
  this.disableThoughtLogging = (process.env.DISABLE_THOUGHT_LOGGING || "").toLowerCase() === "true";
20
24
  this.storagePath = path.resolve(storagePath);
@@ -439,9 +443,9 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
439
443
  let feedbackExtension = "";
440
444
  // --- šŸ”„ FEATURE 0: Smart Branching Reward (Reset Confidence on Pivot) ---
441
445
  if (input.branchFromThought) {
442
- if (this.confidenceScore < 90) {
443
- this.confidenceScore = 90;
444
- feedbackExtension += `\n🌿 BRANCH DETECTED: Confidence restored to 90. Good decision to pivot.`;
446
+ if (this.confidenceScore < 75) {
447
+ this.confidenceScore = 75;
448
+ feedbackExtension += `\n🌿 BRANCH DETECTED: Confidence restored to 75. Good decision to pivot.`;
445
449
  }
446
450
  }
447
451
  // --- 🧠 FEATURE 1: Semantic Thought Recall ---
@@ -521,6 +525,7 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
521
525
  const oldBlock = this.blocks.find(b => b.id === this.currentBlockId);
522
526
  if (oldBlock) {
523
527
  oldBlock.status = 'completed';
528
+ await this.learnFromFailures(oldBlock.id);
524
529
  // Auto-Summary Trigger
525
530
  if (oldBlock.thoughts.length > 5) {
526
531
  warnings.push(`šŸ“œ CONTEXT MANAGEMENT: Previous block '${oldBlock.topic.substring(0, 30)}...' was long. Please use 'summarize_history' to compress it.`);
@@ -558,10 +563,16 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
558
563
  }
559
564
  }
560
565
  // Rule 2: Repeating Action Detection
561
- if (input.thoughtType === 'execution' &&
562
- recentInBlock.some(t => t.thoughtType === 'execution' && t.thought === input.thought)) {
563
- 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.`);
564
- 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
+ }
565
576
  }
566
577
  // Rule 3: Missing Observation
567
578
  if (lastThought &&
@@ -644,6 +655,7 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
644
655
  .filter(t => t.thoughtType === 'execution')
645
656
  .map(t => t.thought);
646
657
  const activeBlock = this.blocks.find(b => b.id === input.blockId);
658
+ await this.learnFromFailures(input.blockId || "");
647
659
  await this.saveSolution(activeBlock?.topic || 'Untitled', input.thought, solutionSteps);
648
660
  }
649
661
  }
@@ -660,7 +672,10 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
660
672
  // --- šŸ“Š Update Confidence Score ---
661
673
  if (warnings.length > 0)
662
674
  this.confidenceScore -= (5 * warnings.length);
663
- 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') {
664
679
  const isError = input.toolResult?.toLowerCase().includes('error');
665
680
  if (isError)
666
681
  this.confidenceScore -= 10;
@@ -681,6 +696,7 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
681
696
  // D. Generate Contextual Output
682
697
  const currentBlock = this.blocks.find(b => b.id === input.blockId);
683
698
  const mermaid = this.generateMermaid();
699
+ const optimizedContext = await this.contextManager.getOptimizedContext(this.thoughtHistory, input.blockId || null);
684
700
  if (feedbackExtension)
685
701
  warnings.push(feedbackExtension);
686
702
  return {
@@ -694,6 +710,7 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
694
710
  blockContext: currentBlock
695
711
  ? `Block '${currentBlock.topic.substring(0, 30)}' has ${currentBlock.thoughts.length} thoughts.`
696
712
  : 'No active block',
713
+ optimizedContext,
697
714
  branches: Object.keys(this.branches),
698
715
  thoughtHistoryLength: this.thoughtHistory.length,
699
716
  feedback: warnings.length > 0 ? warnings : "Flow Healthy āœ…",
@@ -801,4 +818,18 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
801
818
  getHistoryLength() {
802
819
  return this.thoughtHistory.length;
803
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
+ }
804
835
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotza02/sequential-thinking",
3
- "version": "2026.3.10",
3
+ "version": "2026.3.12",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },