@gotza02/sequential-thinking 2026.1.20 → 2026.1.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.
package/README.md CHANGED
@@ -14,6 +14,16 @@ An MCP server implementation that provides a tool for dynamic and reflective pro
14
14
  - **Self-Reflexion**: Review and correct previous thoughts (`reflexion`) to improve accuracy.
15
15
  - **Dynamic Adjustment**: Adjust the total number of thoughts dynamically as understanding deepens.
16
16
 
17
+ ## System Instruction for AI
18
+
19
+ When connected to this server, you should follow these guidelines to maximize problem-solving effectiveness:
20
+
21
+ 1. **Iterative Thinking**: Use the `sequentialthinking` tool for every complex task. Do not try to provide a final answer immediately. Instead, break the problem into steps, generate hypotheses, and evaluate them.
22
+ 2. **Context Discovery**: Before modifying code, always use `build_project_graph` followed by `get_project_graph_summary` to understand the project architecture. Use `get_file_relationships` to see how specific files are connected.
23
+ 3. **Fact Verification**: Use `web_search` to verify current facts, library versions, or documentation. Use `fetch` to retrieve raw data from external sources when needed.
24
+ 4. **Local Execution**: Use `shell_execute` to run tests, linters, or build commands to verify your changes. Always read files using `read_file` before attempting to write or modify them.
25
+ 5. **Persistence**: The thinking process is saved automatically. You can resume previous sessions by reviewing the `thoughts_history.json` file if needed.
26
+
17
27
  ## Tool
18
28
 
19
29
  ### sequentialthinking
@@ -130,6 +140,18 @@ npm run build
130
140
  npm test
131
141
  ```
132
142
 
143
+ ## Recent Updates (v2026.1.21)
144
+
145
+ - **Performance & Accuracy**:
146
+ - Replaced Regex-based code analysis with **TypeScript Compiler API (AST)** for 100% accurate import/export detection.
147
+ - Improved `web_search` robustness and error handling.
148
+ - **Persistence**:
149
+ - Implemented **File-based Persistence** for the thinking process. Your thoughts are now saved to `thoughts_history.json` automatically.
150
+ - Switched to **Asynchronous File I/O** to prevent server blocking.
151
+ - **Bug Fixes**:
152
+ - Fixed duplicate import entries in the project graph.
153
+ - Resolved memory growth issues in long-running sessions.
154
+
133
155
  ## Recent Updates (v2026.1.20)
134
156
 
135
157
  - **New Features**:
package/dist/graph.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as fs from 'fs/promises';
2
2
  import * as path from 'path';
3
+ import ts from 'typescript';
3
4
  export class ProjectKnowledgeGraph {
4
5
  nodes = new Map();
5
6
  rootDir = '';
@@ -45,30 +46,48 @@ export class ProjectKnowledgeGraph {
45
46
  }
46
47
  async parseFile(filePath) {
47
48
  try {
48
- let content = await fs.readFile(filePath, 'utf-8');
49
- // Remove comments before parsing imports
50
- // 1. Remove block comments /* ... */
51
- content = content.replace(/\/\*[\s\S]*?\*\//g, '');
52
- // 2. Remove line comments // ...
53
- content = content.replace(/\/\/.*$/gm, '');
54
- const importRegex = /import\s+(?:[\w\s{},*]+from\s+)?['"]([^'"]+)['"]/g;
55
- const dynamicImportRegex = /import\(['"]([^'"]+)['"]\)/g;
56
- const requireRegex = /require\(['"]([^'"]+)['"]\)/g;
57
- const allMatches = [
58
- ...content.matchAll(importRegex),
59
- ...content.matchAll(dynamicImportRegex),
60
- ...content.matchAll(requireRegex)
61
- ];
49
+ const content = await fs.readFile(filePath, 'utf-8');
50
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
51
+ const imports = [];
52
+ const visit = (node) => {
53
+ // 1. Static imports: import ... from '...'
54
+ if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
55
+ if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
56
+ imports.push(node.moduleSpecifier.text);
57
+ }
58
+ }
59
+ // 2. Dynamic imports: import('...')
60
+ else if (ts.isCallExpression(node)) {
61
+ if (node.expression.kind === ts.SyntaxKind.ImportKeyword && node.arguments.length > 0) {
62
+ const arg = node.arguments[0];
63
+ if (ts.isStringLiteral(arg)) {
64
+ imports.push(arg.text);
65
+ }
66
+ }
67
+ // 3. CommonJS: require('...')
68
+ else if (ts.isIdentifier(node.expression) && node.expression.text === 'require' && node.arguments.length > 0) {
69
+ const arg = node.arguments[0];
70
+ if (ts.isStringLiteral(arg)) {
71
+ imports.push(arg.text);
72
+ }
73
+ }
74
+ }
75
+ ts.forEachChild(node, visit);
76
+ };
77
+ visit(sourceFile);
62
78
  const currentNode = this.nodes.get(filePath);
63
79
  if (!currentNode)
64
80
  return;
65
- for (const match of allMatches) {
66
- const importPath = match[1];
81
+ for (const importPath of imports) {
67
82
  if (importPath.startsWith('.')) {
68
83
  const resolvedPath = await this.resolvePath(path.dirname(filePath), importPath);
69
84
  if (resolvedPath && this.nodes.has(resolvedPath)) {
70
- currentNode.imports.push(resolvedPath);
71
- this.nodes.get(resolvedPath)?.importedBy.push(filePath);
85
+ if (!currentNode.imports.includes(resolvedPath)) {
86
+ currentNode.imports.push(resolvedPath);
87
+ }
88
+ if (!this.nodes.get(resolvedPath)?.importedBy.includes(filePath)) {
89
+ this.nodes.get(resolvedPath)?.importedBy.push(filePath);
90
+ }
72
91
  }
73
92
  }
74
93
  }
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ const server = new McpServer({
12
12
  name: "sequential-thinking-server",
13
13
  version: "2026.1.18",
14
14
  });
15
- const thinkingServer = new SequentialThinkingServer();
15
+ const thinkingServer = new SequentialThinkingServer(process.env.THOUGHTS_STORAGE_PATH || 'thoughts_history.json');
16
16
  const knowledgeGraph = new ProjectKnowledgeGraph();
17
17
  // --- Sequential Thinking Tool ---
18
18
  server.tool("sequentialthinking", `A detailed tool for dynamic and reflective problem-solving through thoughts.
@@ -92,7 +92,7 @@ You should:
92
92
  options: z.array(z.string()).optional().describe("List of options generated"),
93
93
  selectedOption: z.string().optional().describe("The option selected")
94
94
  }, async (args) => {
95
- const result = thinkingServer.processThought(args);
95
+ const result = await thinkingServer.processThought(args);
96
96
  return {
97
97
  content: result.content,
98
98
  isError: result.isError
package/dist/lib.js CHANGED
@@ -1,10 +1,53 @@
1
1
  import chalk from 'chalk';
2
+ import * as fs from 'fs/promises';
3
+ import { existsSync, readFileSync } from 'fs';
4
+ import * as path from 'path';
2
5
  export class SequentialThinkingServer {
3
6
  thoughtHistory = [];
4
7
  branches = {};
5
8
  disableThoughtLogging;
6
- constructor() {
9
+ storagePath;
10
+ constructor(storagePath = 'thoughts_history.json') {
7
11
  this.disableThoughtLogging = (process.env.DISABLE_THOUGHT_LOGGING || "").toLowerCase() === "true";
12
+ this.storagePath = path.resolve(storagePath);
13
+ this.loadHistory();
14
+ }
15
+ loadHistory() {
16
+ try {
17
+ if (existsSync(this.storagePath)) {
18
+ const data = readFileSync(this.storagePath, 'utf-8');
19
+ const history = JSON.parse(data);
20
+ if (Array.isArray(history)) {
21
+ this.thoughtHistory = []; // Reset to avoid duplicates
22
+ this.branches = {};
23
+ history.forEach(thought => this.addToMemory(thought));
24
+ }
25
+ }
26
+ }
27
+ catch (error) {
28
+ console.error(`Error loading history from ${this.storagePath}:`, error);
29
+ }
30
+ }
31
+ async saveHistory() {
32
+ try {
33
+ await fs.writeFile(this.storagePath, JSON.stringify(this.thoughtHistory, null, 2), 'utf-8');
34
+ }
35
+ catch (error) {
36
+ console.error(`Error saving history to ${this.storagePath}:`, error);
37
+ }
38
+ }
39
+ addToMemory(input) {
40
+ if (input.thoughtNumber > input.totalThoughts) {
41
+ input.totalThoughts = input.thoughtNumber;
42
+ }
43
+ this.thoughtHistory.push(input);
44
+ if (input.branchFromThought && input.branchId) {
45
+ const branchKey = `${input.branchFromThought}-${input.branchId}`;
46
+ if (!this.branches[branchKey]) {
47
+ this.branches[branchKey] = [];
48
+ }
49
+ this.branches[branchKey].push(input);
50
+ }
8
51
  }
9
52
  formatThought(thoughtData) {
10
53
  const { thoughtNumber, totalThoughts, thought, isRevision, revisesThought, branchFromThought, branchId, thoughtType, score, options, selectedOption } = thoughtData;
@@ -52,19 +95,10 @@ export class SequentialThinkingServer {
52
95
  │ ${thought.padEnd(borderLength - 2)} │${extraContent}
53
96
  └${border}┘`;
54
97
  }
55
- processThought(input) {
98
+ async processThought(input) {
56
99
  try {
57
- if (input.thoughtNumber > input.totalThoughts) {
58
- input.totalThoughts = input.thoughtNumber;
59
- }
60
- this.thoughtHistory.push(input);
61
- if (input.branchFromThought && input.branchId) {
62
- const branchKey = `${input.branchFromThought}-${input.branchId}`;
63
- if (!this.branches[branchKey]) {
64
- this.branches[branchKey] = [];
65
- }
66
- this.branches[branchKey].push(input);
67
- }
100
+ this.addToMemory(input);
101
+ await this.saveHistory();
68
102
  if (!this.disableThoughtLogging) {
69
103
  const formattedThought = this.formatThought(input);
70
104
  console.error(formattedThought);
@@ -1,11 +1,21 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
1
+ import { describe, it, expect, beforeEach, afterAll } from 'vitest';
2
2
  import { SequentialThinkingServer } from './lib.js';
3
+ import * as fs from 'fs';
3
4
  describe('SequentialThinkingServer', () => {
4
5
  let server;
6
+ const testStoragePath = 'test_thoughts.json';
5
7
  beforeEach(() => {
6
- server = new SequentialThinkingServer();
8
+ if (fs.existsSync(testStoragePath)) {
9
+ fs.unlinkSync(testStoragePath);
10
+ }
11
+ server = new SequentialThinkingServer(testStoragePath);
7
12
  });
8
- it('should process a basic linear thought', () => {
13
+ afterAll(() => {
14
+ if (fs.existsSync(testStoragePath)) {
15
+ fs.unlinkSync(testStoragePath);
16
+ }
17
+ });
18
+ it('should process a basic linear thought', async () => {
9
19
  const input = {
10
20
  thought: "First step",
11
21
  thoughtNumber: 1,
@@ -13,15 +23,15 @@ describe('SequentialThinkingServer', () => {
13
23
  nextThoughtNeeded: true,
14
24
  thoughtType: 'analysis'
15
25
  };
16
- const result = server.processThought(input);
26
+ const result = await server.processThought(input);
17
27
  expect(result.isError).toBeUndefined();
18
28
  const content = JSON.parse(result.content[0].text);
19
29
  expect(content.thoughtNumber).toBe(1);
20
30
  expect(content.thoughtHistoryLength).toBe(1);
21
31
  });
22
- it('should handle branching correctly', () => {
32
+ it('should handle branching correctly', async () => {
23
33
  // Initial thought
24
- server.processThought({
34
+ await server.processThought({
25
35
  thought: "Root thought",
26
36
  thoughtNumber: 1,
27
37
  totalThoughts: 3,
@@ -37,7 +47,7 @@ describe('SequentialThinkingServer', () => {
37
47
  branchId: "branch-A",
38
48
  thoughtType: 'generation'
39
49
  };
40
- const result1 = server.processThought(branch1Input);
50
+ const result1 = await server.processThought(branch1Input);
41
51
  const content1 = JSON.parse(result1.content[0].text);
42
52
  expect(content1.branches).toContain("1-branch-A");
43
53
  // Branch 2
@@ -50,12 +60,12 @@ describe('SequentialThinkingServer', () => {
50
60
  branchId: "branch-B",
51
61
  thoughtType: 'generation'
52
62
  };
53
- const result2 = server.processThought(branch2Input);
63
+ const result2 = await server.processThought(branch2Input);
54
64
  const content2 = JSON.parse(result2.content[0].text);
55
65
  expect(content2.branches).toContain("1-branch-B");
56
66
  expect(content2.branches.length).toBe(2);
57
67
  });
58
- it('should handle evaluation with scores', () => {
68
+ it('should handle evaluation with scores', async () => {
59
69
  const input = {
60
70
  thought: "Evaluating option X",
61
71
  thoughtNumber: 3,
@@ -65,20 +75,20 @@ describe('SequentialThinkingServer', () => {
65
75
  score: 8,
66
76
  options: ['Option X', 'Option Y']
67
77
  };
68
- const result = server.processThought(input);
78
+ const result = await server.processThought(input);
69
79
  expect(result.isError).toBeUndefined();
70
80
  // Since we don't return the score in the simple JSON response (only in logs or history),
71
81
  // we mainly check that it doesn't crash and processes correctly.
72
82
  // If we exposed history in the response, we could check that too.
73
83
  });
74
- it('should adjust totalThoughts if thoughtNumber exceeds it', () => {
84
+ it('should adjust totalThoughts if thoughtNumber exceeds it', async () => {
75
85
  const input = {
76
86
  thought: "Unexpected long process",
77
87
  thoughtNumber: 6,
78
88
  totalThoughts: 5,
79
89
  nextThoughtNeeded: true
80
90
  };
81
- const result = server.processThought(input);
91
+ const result = await server.processThought(input);
82
92
  const content = JSON.parse(result.content[0].text);
83
93
  expect(content.totalThoughts).toBe(6);
84
94
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotza02/sequential-thinking",
3
- "version": "2026.1.20",
3
+ "version": "2026.1.21",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -30,6 +30,7 @@
30
30
  "dependencies": {
31
31
  "@modelcontextprotocol/sdk": "^1.24.0",
32
32
  "chalk": "^5.3.0",
33
+ "typescript": "^5.3.3",
33
34
  "yargs": "^17.7.2"
34
35
  },
35
36
  "devDependencies": {
@@ -37,7 +38,6 @@
37
38
  "@types/yargs": "^17.0.32",
38
39
  "@vitest/coverage-v8": "^2.1.8",
39
40
  "shx": "^0.3.4",
40
- "typescript": "^5.3.3",
41
41
  "vitest": "^2.1.8"
42
42
  }
43
43
  }